source: Klonkt/src/views/partials/shared-styles.ejs@ 743bb81

main
Last change on this file since 743bb81 was 9e27d64, checked in by roboburr <roboburr@…>, 3 months ago

auth: password admin + per-instance Google for listeners (no broker)

Robin's choice: every self-hoster has their own password admin account,
and can optionally let listeners log in to comment using their OWN Google
client. No central broker (that would tie every customer site to Robin's
Google Cloud -> systemic risk on abuse).

  • Admin = username/password (bcrypt). First-time setup via /auth/register (only when there are 0 users); closed afterwards. No public registration.
  • Forgot password: /auth/reset-request -> email (if SMTP configured) with reset link; CLI break-glass npm run reset-admin always works (no email needed).
  • Change password (logged in) restored in /account.
  • Google = per-instance own credentials, OPTIONAL, listeners only -> always role member, never admin (god/admin email is rejected; google_sub mismatch too).
  • config/google.js back to direct Google OAuth; config/mailer.js new (nodemailer).
  • jose removed from deps; nodemailer added.

Security review (workflow) incorporated:

  • Reset token no longer in production logs (dev only).
  • Reset link from PUBLIC_BASE_URL instead of X-Forwarded-Host (host poisoning).
  • Reset tokens stored SHA-256-hashed in the DB.
  • Same-origin check on all state-modifying POSTs (CSRF layer on top of sameSite-lax).
  • Login always runs one bcrypt comparison (no timing enumeration).

Co-Authored-By: Claude <noreply@…>

  • Property mode set to 100644
File size: 6.3 KB
Line 
1<style>
2/* ─────────────────────────────────────────────────
3 View switcher (timeline ↔ grid + grid-cols picker)
4 ───────────────────────────────────────────────── */
5.view-switcher-wrap {
6 border-bottom: 1px solid var(--rule);
7 background: var(--paper);
8 /* Breathing room: profile-header has padding-bottom 1.5rem already, so a
9 small additional margin-top gives the switcher its own visual layer. */
10 margin-top: 0.5rem;
11}
12.view-switcher-wrap .container {
13 display: flex; align-items: center; justify-content: center; gap: 0.75rem;
14 /* Slimmer bar: was 0.4rem (≈12px total chrome above pill); now 0.25rem. */
15 padding: 0.25rem 1rem;
16 max-width: 1200px; margin: 0 auto;
17 flex-wrap: wrap;
18}
19.view-switcher {
20 display: inline-flex;
21 background: var(--paper-2);
22 border: 1px solid var(--rule);
23 border-radius: 999px;
24 padding: 0.15rem;
25}
26.view-switch-btn {
27 display: inline-flex; align-items: center; gap: 0.35rem;
28 padding: 0.35rem 0.85rem;
29 border: 0; background: transparent; color: var(--ink-muted, var(--ink-soft));
30 cursor: pointer; border-radius: 999px;
31 font-size: 0.85rem; font-family: inherit;
32 transition: background 120ms, color 120ms;
33}
34.view-switch-btn:hover { color: var(--ink); }
35.view-switch-btn[aria-selected="true"] { background: var(--paper); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.06); }
36
37.grid-cols-picker {
38 display: none; /* desktop-only, see media query */
39 gap: 0.2rem;
40}
41.grid-cols-btn {
42 width: 28px; height: 28px;
43 border: 1px solid var(--rule);
44 background: var(--paper-2);
45 color: var(--ink-muted, var(--ink-soft));
46 border-radius: 4px;
47 font-size: 0.8rem; font-weight: 600;
48 cursor: pointer;
49 font-variant-numeric: tabular-nums;
50}
51.grid-cols-btn:hover { color: var(--ink); border-color: var(--accent); }
52.grid-cols-btn.is-active { background: var(--accent); color: white; border-color: var(--accent); }
53
54/* Show grid-cols picker only on grid view + desktop */
55@media (min-width: 720px) {
56 body[data-feed-view="grid"] .grid-cols-picker { display: inline-flex; }
57}
58
59/* Switcher is hidden only on admin pages — admin has a dedicated layout
60 and its own back-button flow where Tijdlijn/Grid is irrelevant. On
61 every other page (home, post, tag, archive, search, account) it stays
62 visible so the user keeps a consistent return-to-feed control. */
63.on-admin .view-switcher-wrap { display: none; }
64
65/* Spacing between switcher-bar and the page content below.
66 .feed-timeline (used on home + archive + post-list pages) gets a top
67 margin so the first post doesn't collide with the switcher's border.
68 .feed-grid already has its own top spacing from grid-tile padding. */
69.view-switcher-wrap + #pcms-main .feed-timeline,
70.view-switcher-wrap + #pcms-main .container:first-child {
71 margin-top: 1.5rem;
72}
73
74/* Feed layout switching is in the v9 style.css (style.css):
75 .feed-timeline always rendered, .feed-grid hidden unless on-home + grid-mode. */
76
77/* ─────────────────────────────────────────────────
78 PWA install button (lives in topnav, hidden by default)
79 ───────────────────────────────────────────────── */
80#pwa-install-btn { color: var(--accent); }
81#pwa-install-btn:hover { background: var(--paper-2); }
82
83/* Shared button + form styles (used across pages) */
84.btn {
85 display: inline-flex;
86 align-items: center;
87 gap: 0.4rem;
88 padding: 0.55rem 1rem;
89 border: 1px solid var(--rule);
90 background: var(--paper-2);
91 color: var(--ink);
92 font-family: var(--font-ui, system-ui), sans-serif;
93 font-size: 0.9rem;
94 font-weight: 500;
95 text-decoration: none;
96 border-radius: 5px;
97 cursor: pointer;
98 transition: background 150ms, border-color 150ms;
99}
100.btn:hover { border-color: var(--accent); }
101.btn-primary { background: var(--accent); color: white; border-color: var(--accent); }
102.btn-primary:hover { opacity: 0.92; border-color: var(--accent); }
103.btn-success { background: #16a34a; color: white; border-color: #16a34a; }
104.btn-danger { background: #dc2626; color: white; border-color: #dc2626; }
105
106.alert { padding: 0.75rem 1rem; border-radius: 5px; margin-bottom: 1rem; }
107.alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
108.alert-success { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
109
110.auth-page { max-width: 420px; margin: 3rem auto; padding: 0 1rem; }
111.auth-page h1 { font-family: var(--font-display, serif); text-align: center; margin: 0 0 1.5rem; }
112.auth-form { display: flex; flex-direction: column; gap: 1rem; }
113.auth-form label { display: flex; flex-direction: column; gap: 0.3rem; }
114.auth-form label > span { font-size: 0.85rem; font-weight: 600; color: var(--ink-soft); }
115.auth-form input { padding: 0.6rem 0.75rem; border: 1px solid var(--rule); border-radius: 4px; background: var(--paper); color: var(--ink); font-size: 1rem; }
116.auth-form .btn { justify-content: center; padding: 0.7rem; font-size: 1rem; margin-top: 0.5rem; }
117.auth-link { text-align: center; color: var(--ink-muted); font-size: 0.9rem; margin: 1rem 0 0; }
118.auth-link a { color: var(--accent); }
119.auth-google { align-items: stretch; }
120.auth-intro { text-align: center; color: var(--ink-muted); font-size: 0.95rem; margin: 0 0 1rem; }
121.btn-google {
122 justify-content: center; gap: 0.6rem; padding: 0.7rem; font-size: 1rem;
123 background: #fff; color: #1f1f1f; border: 1px solid #dadce0;
124}
125.btn-google:hover { background: #f7f8f8; border-color: #dadce0; }
126.btn-google .g-icon { flex-shrink: 0; }
127.auth-divider { display: flex; align-items: center; gap: 0.75rem; margin: 1.25rem 0; color: var(--ink-muted); font-size: 0.85rem; }
128.auth-divider::before, .auth-divider::after { content: ''; flex: 1; height: 1px; background: var(--rule); }
129
130/* HTMX loading indicator */
131.pcms-loading {
132 display: none;
133 position: fixed;
134 top: 60px;
135 right: 1rem;
136 width: 24px;
137 height: 24px;
138 border: 3px solid var(--paper-2);
139 border-top-color: var(--accent);
140 border-radius: 50%;
141 animation: spin 0.8s linear infinite;
142 z-index: 1000;
143}
144.htmx-request .pcms-loading { display: block; }
145@keyframes spin { to { transform: rotate(360deg); } }
146</style>
Note: See TracBrowser for help on using the repository browser.