| 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 | /* No extra margin-top: this centres the pill between the profile-header
|
|---|
| 9 | rule (above) and the own border (below). Equal space above/below the
|
|---|
| 10 | pill comes from the symmetric container padding below. The margin-bottom
|
|---|
| 11 | (1.5rem in style.css) keeps breathing room toward the feed. */
|
|---|
| 12 | margin-top: 0;
|
|---|
| 13 | }
|
|---|
| 14 | .view-switcher-wrap .container {
|
|---|
| 15 | display: flex; align-items: center; justify-content: center; gap: 0.75rem;
|
|---|
| 16 | /* Symmetric padding â equal space above and below the pill (â8px),
|
|---|
| 17 | centred between the two dividing lines. */
|
|---|
| 18 | padding: 0.5rem 1rem;
|
|---|
| 19 | max-width: 1200px; margin: 0 auto;
|
|---|
| 20 | flex-wrap: wrap;
|
|---|
| 21 | }
|
|---|
| 22 | .view-switcher {
|
|---|
| 23 | display: inline-flex;
|
|---|
| 24 | background: var(--paper-2);
|
|---|
| 25 | border: 1px solid var(--rule);
|
|---|
| 26 | border-radius: 999px;
|
|---|
| 27 | padding: 0.15rem;
|
|---|
| 28 | }
|
|---|
| 29 | .view-switch-btn {
|
|---|
| 30 | display: inline-flex; align-items: center; gap: 0.35rem;
|
|---|
| 31 | padding: 0.35rem 0.85rem;
|
|---|
| 32 | border: 0; background: transparent; color: var(--ink-muted, var(--ink-soft));
|
|---|
| 33 | cursor: pointer; border-radius: 999px;
|
|---|
| 34 | font-size: 0.85rem; font-family: inherit;
|
|---|
| 35 | transition: background 120ms, color 120ms;
|
|---|
| 36 | }
|
|---|
| 37 | .view-switch-btn:hover { color: var(--ink); }
|
|---|
| 38 | /* Active = white (light background, dark text). Via aria-selected (element
|
|---|
| 39 | attribute, set by syncAria + on htmx:afterSettle) â htmx-robust. On non-
|
|---|
| 40 | feed pages syncAria sets aria-selected=false â both grey. */
|
|---|
| 41 | .view-switch-btn[aria-selected="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
|
|---|
| 42 |
|
|---|
| 43 | /* Agenda = its OWN pill (separate from Timeline/Grid, so the feed-toggle is never
|
|---|
| 44 | disturbed). Same look as the view-switcher pill. Visible in all modes. */
|
|---|
| 45 | .agenda-pill { display: inline-flex; align-items: center; } /* no box â standalone icon+text link */
|
|---|
| 46 | .vs-agenda {
|
|---|
| 47 | display: inline-flex; align-items: center; gap: 0.4rem;
|
|---|
| 48 | padding: 0.35rem 0.5rem;
|
|---|
| 49 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 50 | text-decoration: none;
|
|---|
| 51 | font-size: 0.9rem; font-family: inherit;
|
|---|
| 52 | transition: color 120ms;
|
|---|
| 53 | }
|
|---|
| 54 | .vs-agenda:hover { color: var(--ink); }
|
|---|
| 55 | /* Active (on the agenda page) = bright/white. Element-class (in the OOB-rendered
|
|---|
| 56 | chrome) rather than body-class CSS â stays reliable after htmx navigation. */
|
|---|
| 57 | .vs-agenda.is-active { color: var(--ink); font-weight: 600; }
|
|---|
| 58 |
|
|---|
| 59 | /* Mobile: Agenda on its own row at the top, Solo/Circle + Timeline/Grid together
|
|---|
| 60 | on the row below (together ~330px â fits within a phone, instead of the old
|
|---|
| 61 | unbalanced split Agenda+Solo / Timeline). */
|
|---|
| 62 | @media (max-width: 520px) {
|
|---|
| 63 | .view-switcher-wrap .agenda-pill { flex: 0 0 100%; justify-content: center; }
|
|---|
| 64 | }
|
|---|
| 65 |
|
|---|
| 66 | .grid-cols-picker {
|
|---|
| 67 | display: none; /* desktop-only, see media query */
|
|---|
| 68 | gap: 0.2rem;
|
|---|
| 69 | }
|
|---|
| 70 | .grid-cols-btn {
|
|---|
| 71 | width: 28px; height: 28px;
|
|---|
| 72 | border: 1px solid var(--rule);
|
|---|
| 73 | background: var(--paper-2);
|
|---|
| 74 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 75 | border-radius: 4px;
|
|---|
| 76 | font-size: 0.8rem; font-weight: 600;
|
|---|
| 77 | cursor: pointer;
|
|---|
| 78 | font-variant-numeric: tabular-nums;
|
|---|
| 79 | }
|
|---|
| 80 | .grid-cols-btn:hover { color: var(--ink); border-color: var(--accent); }
|
|---|
| 81 | .grid-cols-btn.is-active { background: var(--accent); color: white; border-color: var(--accent); }
|
|---|
| 82 |
|
|---|
| 83 | /* Show grid-cols picker only on grid view + desktop */
|
|---|
| 84 | @media (min-width: 720px) {
|
|---|
| 85 | body[data-feed-view="grid"] .grid-cols-picker { display: inline-flex; }
|
|---|
| 86 | }
|
|---|
| 87 |
|
|---|
| 88 | /* Switcher is hidden only on admin pages â admin has a dedicated layout
|
|---|
| 89 | and its own back-button flow where Tijdlijn/Grid is irrelevant. On
|
|---|
| 90 | every other page (home, post, tag, archive, search, account) it stays
|
|---|
| 91 | visible so the user keeps a consistent return-to-feed control. */
|
|---|
| 92 | .on-admin .view-switcher-wrap { display: none; }
|
|---|
| 93 |
|
|---|
| 94 | /* On a standalone post there is no feed to switch, so no button should look "active"
|
|---|
| 95 | (coloured). The active state lives in style.css on body[data-feed-view]
|
|---|
| 96 | / body[data-grid-cols] (background:var(--ink)); we override that MORE SPECIFICALLY
|
|---|
| 97 | via body.on-post so the buttons become neutral/grey â but remain clickable
|
|---|
| 98 | (the click-handler then navigates to the feed in that view). The
|
|---|
| 99 | body-class switches client-side on HTMX-nav, so this works without a reload too. */
|
|---|
| 100 | body.on-post[data-feed-view="timeline"] .view-switch-btn[data-view="timeline"],
|
|---|
| 101 | body.on-post[data-feed-view="grid"] .view-switch-btn[data-view="grid"],
|
|---|
| 102 | body.on-post[data-grid-cols="2"] .grid-cols-btn[data-cols="2"],
|
|---|
| 103 | body.on-post[data-grid-cols="3"] .grid-cols-btn[data-cols="3"],
|
|---|
| 104 | body.on-post[data-grid-cols="4"] .grid-cols-btn[data-cols="4"] {
|
|---|
| 105 | background: transparent;
|
|---|
| 106 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 107 | border-color: var(--rule);
|
|---|
| 108 | }
|
|---|
| 109 |
|
|---|
| 110 |
|
|---|
| 111 | /* Spacing between switcher-bar and the page content below.
|
|---|
| 112 | .feed-timeline (used on home + archive + post-list pages) gets a top
|
|---|
| 113 | margin so the first post doesn't collide with the switcher's border.
|
|---|
| 114 | .feed-grid already has its own top spacing from grid-tile padding. */
|
|---|
| 115 | .view-switcher-wrap + #pcms-main .feed-timeline,
|
|---|
| 116 | .view-switcher-wrap + #pcms-main .container:first-child {
|
|---|
| 117 | margin-top: 1.5rem;
|
|---|
| 118 | }
|
|---|
| 119 |
|
|---|
| 120 | /* Feed layout switching is in the v9 style.css (style.css):
|
|---|
| 121 | .feed-timeline always rendered, .feed-grid hidden unless on-home + grid-mode. */
|
|---|
| 122 |
|
|---|
| 123 | /* âââââââââââââââââââââââââââââââââââââââââââââââââ
|
|---|
| 124 | PWA install button (lives in topnav, hidden by default)
|
|---|
| 125 | âââââââââââââââââââââââââââââââââââââââââââââââââ */
|
|---|
| 126 | #pwa-install-btn { color: var(--accent); }
|
|---|
| 127 | #pwa-install-btn:hover { background: var(--paper-2); }
|
|---|
| 128 |
|
|---|
| 129 | /* Shared button + form styles (used across pages) */
|
|---|
| 130 | .btn {
|
|---|
| 131 | display: inline-flex;
|
|---|
| 132 | align-items: center;
|
|---|
| 133 | gap: 0.4rem;
|
|---|
| 134 | padding: 0.55rem 1rem;
|
|---|
| 135 | border: 1px solid var(--rule);
|
|---|
| 136 | background: var(--paper-2);
|
|---|
| 137 | color: var(--ink);
|
|---|
| 138 | font-family: var(--font-ui, system-ui), sans-serif;
|
|---|
| 139 | font-size: 0.9rem;
|
|---|
| 140 | font-weight: 500;
|
|---|
| 141 | text-decoration: none;
|
|---|
| 142 | border-radius: 5px;
|
|---|
| 143 | cursor: pointer;
|
|---|
| 144 | transition: background 150ms, border-color 150ms;
|
|---|
| 145 | }
|
|---|
| 146 | .btn:hover { border-color: var(--accent); }
|
|---|
| 147 | .btn-primary { background: var(--accent); color: white; border-color: var(--accent); }
|
|---|
| 148 | .btn-primary:hover { opacity: 0.92; border-color: var(--accent); }
|
|---|
| 149 | .btn-success { background: #16a34a; color: white; border-color: #16a34a; }
|
|---|
| 150 | .btn-danger { background: #dc2626; color: white; border-color: #dc2626; }
|
|---|
| 151 |
|
|---|
| 152 | .alert { padding: 0.75rem 1rem; border-radius: 5px; margin-bottom: 1rem; }
|
|---|
| 153 | .alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
|
|---|
| 154 | .alert-success { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
|
|---|
| 155 |
|
|---|
| 156 | .auth-page { max-width: 420px; margin: 3rem auto; padding: 0 1rem; }
|
|---|
| 157 | .auth-page h1 { font-family: var(--font-display, serif); text-align: center; margin: 0 0 1.5rem; }
|
|---|
| 158 | .auth-form { display: flex; flex-direction: column; gap: 1rem; }
|
|---|
| 159 | .auth-form label { display: flex; flex-direction: column; gap: 0.3rem; }
|
|---|
| 160 | .auth-form label > span { font-size: 0.85rem; font-weight: 600; color: var(--ink-soft); }
|
|---|
| 161 | .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; }
|
|---|
| 162 | .auth-form .btn { justify-content: center; padding: 0.7rem; font-size: 1rem; margin-top: 0.5rem; }
|
|---|
| 163 | .auth-link { text-align: center; color: var(--ink-muted); font-size: 0.9rem; margin: 1rem 0 0; }
|
|---|
| 164 | .auth-link a { color: var(--accent); }
|
|---|
| 165 | .auth-intro { text-align: center; color: var(--ink-muted); font-size: 0.95rem; margin: 0 0 1rem; }
|
|---|
| 166 | .auth-divider { display: flex; align-items: center; gap: 0.75rem; margin: 1.25rem 0; color: var(--ink-muted); font-size: 0.85rem; }
|
|---|
| 167 | .auth-divider::before, .auth-divider::after { content: ''; flex: 1; height: 1px; background: var(--rule); }
|
|---|
| 168 |
|
|---|
| 169 | /* HTMX loading indicator */
|
|---|
| 170 | .pcms-loading {
|
|---|
| 171 | display: none;
|
|---|
| 172 | position: fixed;
|
|---|
| 173 | top: 60px;
|
|---|
| 174 | right: 1rem;
|
|---|
| 175 | width: 24px;
|
|---|
| 176 | height: 24px;
|
|---|
| 177 | border: 3px solid var(--paper-2);
|
|---|
| 178 | border-top-color: var(--accent);
|
|---|
| 179 | border-radius: 50%;
|
|---|
| 180 | animation: spin 0.8s linear infinite;
|
|---|
| 181 | z-index: 1000;
|
|---|
| 182 | }
|
|---|
| 183 | .htmx-request .pcms-loading { display: block; }
|
|---|
| 184 | @keyframes spin { to { transform: rotate(360deg); } }
|
|---|
| 185 |
|
|---|
| 186 | /* Load more (Solo, Cirkel, News, Messages) â page size 72, htmx append. */
|
|---|
| 187 | .load-more-wrap { display: flex; justify-content: center; margin: 1.5rem 0 .5rem; }
|
|---|
| 188 | .load-more-wrap:empty { margin: 0; }
|
|---|
| 189 | .load-more-btn { padding: .6rem 1.4rem; border-radius: 999px; cursor: pointer;
|
|---|
| 190 | font: inherit; font-weight: 600; color: var(--ink, inherit);
|
|---|
| 191 | background: color-mix(in srgb, var(--ink, #000) 5%, transparent);
|
|---|
| 192 | border: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent); }
|
|---|
| 193 | .load-more-btn:hover { background: color-mix(in srgb, var(--ink, #000) 9%, transparent); }
|
|---|
| 194 | .load-more-btn.htmx-request { opacity: .6; pointer-events: none; }
|
|---|
| 195 |
|
|---|
| 196 | /* FEP-9098 custom emojis: small inline images (Shaer parity), rendered in note
|
|---|
| 197 | content and display names across the timeline, messages and threads. Never
|
|---|
| 198 | styled as media (no rounding/background), and never blown up to media width. */
|
|---|
| 199 | img.emoji { height: 1.3em; width: auto; margin: 0 .05em; vertical-align: -0.2em;
|
|---|
| 200 | display: inline-block; border-radius: 0; background: none; box-shadow: none; }
|
|---|
| 201 | .tl-content img.emoji, .tl-quote-body img.emoji, .msg-content img.emoji, .comment-content img.emoji,
|
|---|
| 202 | .tl-author img.emoji, .tl-boost-by img.emoji, .tl-quote-name img.emoji, .msg-who img.emoji, .comment-author img.emoji {
|
|---|
| 203 | max-width: none; height: 1.3em; border-radius: 0; background: none; box-shadow: none; vertical-align: -0.2em; }
|
|---|
| 204 |
|
|---|
| 205 | /* ââ The body of a post ââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|---|
| 206 | Shared with partials/note-body.ejs, so a post looks the same in de Krant, in
|
|---|
| 207 | Berichten and in the Guardian PWA. These used to live in the Krant's own
|
|---|
| 208 | <style> block, which is why a post outside de Krant came out unstyled.
|
|---|
| 209 | Krant-only behaviour (the clamp + "read more", the poll, the action bar)
|
|---|
| 210 | stays in pages/news.ejs. */
|
|---|
| 211 | .tl-content { line-height: 1.55; overflow-wrap: anywhere; }
|
|---|
| 212 | .tl-content p { margin: .4rem 0; } .tl-content p:first-child { margin-top: 0; } .tl-content p:last-child { margin-bottom: 0; }
|
|---|
| 213 | .tl-content a { color: var(--accent, #06c); }
|
|---|
| 214 | .tl-content img { max-width: 100%; height: auto; border-radius: 10px; background: #fff; }
|
|---|
| 215 |
|
|---|
| 216 | /* FEP-044f embedded quote card (mirror of the Shaer QuoteCard). Also carries
|
|---|
| 217 | an external link preview: same card, different origin. */
|
|---|
| 218 | .tl-quote { margin: .75rem 0 0; padding: .6rem .7rem; border-radius: 12px;
|
|---|
| 219 | border: 1px solid color-mix(in srgb, var(--ink, #000) 12%, transparent);
|
|---|
| 220 | background: color-mix(in srgb, var(--ink, #000) 3.5%, transparent); }
|
|---|
| 221 | .tl-quote-head { display: flex; align-items: center; gap: .4rem; margin: 0 0 .35rem; min-width: 0; }
|
|---|
| 222 | .tl-quote-avatar { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; object-fit: cover; background: #fff; }
|
|---|
| 223 | .tl-quote-name { font-weight: 700; font-size: .85rem; color: var(--ink, inherit); white-space: nowrap; }
|
|---|
| 224 | .tl-quote-handle { color: var(--ink-soft, #888); font-size: .78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
|
|---|
| 225 | .tl-quote-body { line-height: 1.5; font-size: .92rem; color: var(--ink-soft, #555);
|
|---|
| 226 | overflow-wrap: anywhere; max-height: 16em; overflow: hidden; }
|
|---|
| 227 | .tl-quote-body p { margin: .3rem 0; } .tl-quote-body p:first-child { margin-top: 0; } .tl-quote-body p:last-child { margin-bottom: 0; }
|
|---|
| 228 | .tl-quote-body a { color: var(--accent, #06c); }
|
|---|
| 229 | .tl-quote-media { display: block; margin: .45rem 0 0; }
|
|---|
| 230 | .tl-quote-media img { max-width: 100%; height: auto; border-radius: 8px; }
|
|---|
| 231 |
|
|---|
| 232 | /* Always show the FULL image at its natural ratio â never cropped, no letterbox. */
|
|---|
| 233 | .tl-media { display: flex; flex-direction: column; gap: .4rem; margin: .75rem 0 0; }
|
|---|
| 234 | .tl-media-img { display: block; border-radius: 12px; overflow: hidden;
|
|---|
| 235 | background-image: linear-gradient(135deg,
|
|---|
| 236 | color-mix(in srgb, var(--accent, #888) 22%, var(--paper-2, var(--paper))) 0%,
|
|---|
| 237 | color-mix(in srgb, var(--accent, #888) 6%, var(--paper-2, var(--paper))) 100%); }
|
|---|
| 238 | .tl-media-img img { width: 100%; height: auto; display: block; background: #fff; transition: opacity .4s ease; }
|
|---|
| 239 | .tl-media-img img.is-loading { opacity: 0; }
|
|---|
| 240 | /* Reserve placeholder space while the (natural-aspect) feed image loads. */
|
|---|
| 241 | .tl-media-img:has(img.is-loading) { min-height: 220px; }
|
|---|
| 242 | .tl-media-video, .tl-media-audio { width: 100%; margin: .75rem 0 0; border-radius: 12px; display: block; }
|
|---|
| 243 | .tl-media-video { max-height: 480px; background: #000; }
|
|---|
| 244 |
|
|---|
| 245 | /* In Berichten a post sits inside a notification row, so it starts tighter and
|
|---|
| 246 | its media stays modest: the row is a pointer to the post, not the post page. */
|
|---|
| 247 | .msg-note .tl-content { line-height: 1.5; }
|
|---|
| 248 | .msg-note .tl-quote, .msg-note .tl-media, .msg-note .tl-media-video, .msg-note .tl-media-audio { margin-top: .5rem; }
|
|---|
| 249 | .msg-note .tl-media-img:has(img.is-loading) { min-height: 120px; }
|
|---|
| 250 | .msg-note .tl-media-video { max-height: 320px; }
|
|---|
| 251 | </style>
|
|---|