source: Klonkt/src/views/pages/post.ejs@ 7bc636b

main
Last change on this file since 7bc636b was 7bc636b, checked in by Robin <robin@…>, 4 months ago

Initial commit — PrutFolio v1 source (pulled from Hetzner /srv/prutfolio)

  • Property mode set to 100644
File size: 24.5 KB
Line 
1<article class="container post-page">
2 <% if (post.cover_image_url) { %>
3 <figure class="post-cover">
4 <img src="<%= post.cover_image_url %>" alt="">
5 </figure>
6 <% } %>
7
8 <header class="post-header">
9 <h1 class="post-title"><%= post.title %></h1>
10 <div class="post-meta">
11 <a class="author" href="/users/<%= encodeURIComponent(post.author_username) %>"
12 hx-get="/users/<%= encodeURIComponent(post.author_username) %>?partial=1"
13 hx-target="#pcms-main" hx-swap="innerHTML"
14 hx-push-url="/users/<%= encodeURIComponent(post.author_username) %>"
15 hx-indicator="#pcms-loading"><%= post.author_username %></a>
16 <span class="meta-sep">·</span>
17 <time datetime="<%= post.published_at || post.created_at %>"><%= formatDate(post.published_at || post.created_at) %></time>
18 <% if (post.status !== 'published') { %>
19 <span class="meta-sep">·</span>
20 <span class="status-badge status-<%= post.status %>"><%= post.status %></span>
21 <% } %>
22 </div>
23 </header>
24
25 <div class="post-content">
26 <%- post.content_html %>
27 </div>
28
29 <% if (post.tags && post.tags.length > 0) { %>
30 <div class="post-tags">
31 <% post.tags.forEach(t => { %>
32 <a class="tag" href="/tag/<%= encodeURIComponent(t) %>"
33 hx-get="/tag/<%= encodeURIComponent(t) %>?partial=1"
34 hx-target="#pcms-main" hx-swap="innerHTML"
35 hx-push-url="/tag/<%= encodeURIComponent(t) %>"
36 hx-indicator="#pcms-loading">#<%= t %></a>
37 <% }); %>
38 </div>
39 <% } %>
40
41 <!-- Inline admin actions: only visible if user has permission -->
42 <% if (user && (permissions.canEditPost(user, post, site) || permissions.canDeletePost(user, post, site))) { %>
43 <aside class="post-actions">
44 <% if (permissions.canEditPost(user, post, site)) { %>
45 <a href="/posts/<%= post.slug %>/edit" class="btn"
46 hx-get="/posts/<%= post.slug %>/edit?partial=1" hx-target="#pcms-main"
47 hx-push-url="/posts/<%= post.slug %>/edit" hx-indicator="#pcms-loading">
48 ✏️ Edit
49 </a>
50 <% } %>
51 <% if (permissions.canDeletePost(user, post, site)) { %>
52 <form method="post" action="/posts/<%= post.slug %>/delete" style="display:inline" onsubmit="return confirm('Delete this post?')">
53 <button type="submit" class="btn btn-danger">🗑 Delete</button>
54 </form>
55 <% } %>
56 </aside>
57 <% } %>
58
59 <!-- Comments -->
60 <section class="post-comments" id="comments">
61 <h2 class="comments-heading">
62 <%= totalComments %> comment<%= totalComments === 1 ? '' : 's' %>
63 </h2>
64
65 <% if (typeof currentPath === 'string' && currentPath && currentPath.indexOf('/' + post.slug) === 0) { %>
66 <% /* If the URL has ?pending=1, the previous submit landed in the queue */ %>
67 <% } %>
68 <script>
69 (function() {
70 if (location.search.indexOf('pending=1') !== -1) {
71 var s = document.createElement('div');
72 s.className = 'comments-pending-flash';
73 s.textContent = 'Your comment is awaiting moderation. It will appear once an admin approves it.';
74 document.currentScript.parentNode.insertBefore(s, document.currentScript);
75 }
76 })();
77 </script>
78
79 <% if (!comments || !comments.length) { %>
80 <p class="comments-empty">No comments yet.<% if (!user) { %> <a href="/auth/login?next=<%= encodeURIComponent('/' + post.slug + '#comments') %>">Log in</a> to start the conversation.<% } %></p>
81 <% } else { %>
82 <ol class="comments-list">
83 <% comments.forEach(function(c) { %>
84 <li class="comment" id="comment-<%= c.id %>">
85 <div class="comment-avatar">
86 <% if (c.author_avatar) { %>
87 <img src="<%= c.author_avatar %>" alt="">
88 <% } else { %>
89 <span><%= c.author_username.charAt(0).toUpperCase() %></span>
90 <% } %>
91 </div>
92 <div class="comment-body">
93 <div class="comment-meta">
94 <a class="comment-author" href="/users/<%= encodeURIComponent(c.author_username) %>"><%= c.author_username %></a>
95 <span class="comment-time" title="<%= c.created_at %>"><%= formatDateTime(c.created_at) %></span>
96 </div>
97 <div class="comment-content"><%= c.content %></div>
98 <div class="comment-actions">
99 <% if (user) { %>
100 <button type="button" class="comment-reply-btn" data-reply-to="<%= c.id %>">Reply</button>
101 <% } %>
102 <% if (user && permissions.canDeleteComment(user, c, site)) { %>
103 <form method="post" action="/comments/<%= c.id %>/delete" style="display:inline" onsubmit="return confirm('Delete this comment?')">
104 <button type="submit" class="comment-delete-btn">Delete</button>
105 </form>
106 <% } %>
107 </div>
108
109 <!-- Inline reply form (hidden by default) -->
110 <% if (user) { %>
111 <form method="post" action="/comments" class="comment-reply-form" hidden data-reply-form-for="<%= c.id %>">
112 <input type="hidden" name="post_slug" value="<%= post.slug %>">
113 <input type="hidden" name="parent_comment_id" value="<%= c.id %>">
114 <textarea name="content" rows="3" maxlength="4000" placeholder="Reply to <%= c.author_username %>…" required></textarea>
115 <div class="comment-reply-form-actions">
116 <button type="submit" class="btn btn-primary">Reply</button>
117 <button type="button" class="btn comment-cancel-reply">Cancel</button>
118 </div>
119 </form>
120 <% } %>
121
122 <!-- Replies (1 level deep) -->
123 <% if (c.replies && c.replies.length) { %>
124 <ol class="comment-replies">
125 <% c.replies.forEach(function(r) { %>
126 <li class="comment comment-reply" id="comment-<%= r.id %>">
127 <div class="comment-avatar">
128 <% if (r.author_avatar) { %>
129 <img src="<%= r.author_avatar %>" alt="">
130 <% } else { %>
131 <span><%= r.author_username.charAt(0).toUpperCase() %></span>
132 <% } %>
133 </div>
134 <div class="comment-body">
135 <div class="comment-meta">
136 <a class="comment-author" href="/users/<%= encodeURIComponent(r.author_username) %>"><%= r.author_username %></a>
137 <span class="comment-time"><%= formatDateTime(r.created_at) %></span>
138 </div>
139 <div class="comment-content"><%= r.content %></div>
140 <div class="comment-actions">
141 <% if (user && permissions.canDeleteComment(user, r, site)) { %>
142 <form method="post" action="/comments/<%= r.id %>/delete" style="display:inline" onsubmit="return confirm('Delete this comment?')">
143 <button type="submit" class="comment-delete-btn">Delete</button>
144 </form>
145 <% } %>
146 </div>
147 </div>
148 </li>
149 <% }); %>
150 </ol>
151 <% } %>
152 </div>
153 </li>
154 <% }); %>
155 </ol>
156 <% } %>
157
158 <!-- Top-level comment form (only for logged-in users for now) -->
159 <% if (user) { %>
160 <form method="post" action="/comments" class="comment-form">
161 <input type="hidden" name="post_slug" value="<%= post.slug %>">
162 <label class="comment-form-label">
163 <span>Add a comment as <strong><%= user.username %></strong></span>
164 <textarea name="content" rows="3" maxlength="4000" placeholder="Share your thoughts…" required></textarea>
165 </label>
166 <button type="submit" class="btn btn-primary">Post comment</button>
167 </form>
168 <% } else { %>
169 <p class="comments-login-cta">
170 <a href="/auth/login?next=<%= encodeURIComponent('/' + post.slug + '#comments') %>" class="btn">Log in to comment</a>
171 </p>
172 <% } %>
173 </section>
174
175 <!-- Reply form toggle -->
176 <script>
177 (function() {
178 document.querySelectorAll('.comment-reply-btn').forEach(function(btn) {
179 btn.addEventListener('click', function() {
180 var id = btn.dataset.replyTo;
181 var form = document.querySelector('[data-reply-form-for="' + id + '"]');
182 if (form) {
183 form.hidden = !form.hidden;
184 if (!form.hidden) form.querySelector('textarea').focus();
185 }
186 });
187 });
188 document.querySelectorAll('.comment-cancel-reply').forEach(function(btn) {
189 btn.addEventListener('click', function() {
190 btn.closest('.comment-reply-form').hidden = true;
191 });
192 });
193 })();
194 </script>
195
196 <%# ── Related posts (3-card grid) ─────────────────────────────
197 Always shown if at least one related post exists. Selection logic
198 lives server-side: same-tag posts ranked by overlap, falling back
199 to most-recent if there's no tag overlap. %>
200 <% if (relatedPosts && relatedPosts.length > 0) { %>
201 <section class="post-related" aria-labelledby="related-heading">
202 <h2 class="post-related-heading" id="related-heading">Gerelateerde posts</h2>
203 <ul class="post-related-grid">
204 <% relatedPosts.forEach(function(rp) { %>
205 <li class="post-related-card">
206 <a href="/<%= rp.slug %>"
207 hx-get="/<%= rp.slug %>?partial=1" hx-target="#pcms-main"
208 hx-push-url="/<%= rp.slug %>" hx-indicator="#pcms-loading">
209 <% if (rp.cover_image_url) { %>
210 <span class="post-related-cover"
211 style="background-image: url('<%= rp.cover_image_url %>')"
212 aria-hidden="true"></span>
213 <% } else { %>
214 <span class="post-related-cover post-related-cover--empty" aria-hidden="true"></span>
215 <% } %>
216 <span class="post-related-meta">
217 <span class="post-related-date"><%= formatDate(rp.published_at) %></span>
218 <span class="post-related-title"><%= rp.title %></span>
219 </span>
220 </a>
221 </li>
222 <% }); %>
223 </ul>
224 </section>
225 <% } %>
226
227 <%# ── Pinned navigation (only when current post is pinned) ───
228 Two card-style links: "Vorige vastgepinde" / "Volgende vastgepinde".
229 Edge-of-stack states show "← BOVENAAN — Nieuwste post" or the equivalent
230 for the bottom. Layout mirrors v9 exactly. %>
231 <% if (post.pinned) { %>
232 <nav class="post-pinned-nav" aria-label="Pinned navigation">
233 <%# LEFT card: post one step UP the pinned stack (towards #1).
234 Uses prevPinnedPost which now means "smaller rank". %>
235 <% if (prevPinnedPost) { %>
236 <a class="post-pinned-card post-pinned-card--prev" href="/<%= prevPinnedPost.slug %>"
237 hx-get="/<%= prevPinnedPost.slug %>?partial=1" hx-target="#pcms-main"
238 hx-push-url="/<%= prevPinnedPost.slug %>" hx-indicator="#pcms-loading">
239 <span class="post-pinned-arrow" aria-hidden="true">←</span>
240 <span class="post-pinned-body">
241 <span class="post-pinned-label">📌 Vorige vastgepinde</span>
242 <span class="post-pinned-title"><%= prevPinnedPost.title %></span>
243 <span class="post-pinned-cta">Lees deze post →</span>
244 </span>
245 </a>
246 <% } else if (pinnedTopOfStack) { %>
247 <span class="post-pinned-card post-pinned-card--edge">
248 <span class="post-pinned-edge-label">— BOVENAAN —</span>
249 <span class="post-pinned-edge-title">Nieuwste post</span>
250 </span>
251 <% } %>
252
253 <%# RIGHT card: post one step DOWN the pinned stack (away from #1).
254 Uses nextPinnedPost which now means "larger rank". %>
255 <% if (nextPinnedPost) { %>
256 <a class="post-pinned-card post-pinned-card--next" href="/<%= nextPinnedPost.slug %>"
257 hx-get="/<%= nextPinnedPost.slug %>?partial=1" hx-target="#pcms-main"
258 hx-push-url="/<%= nextPinnedPost.slug %>" hx-indicator="#pcms-loading">
259 <span class="post-pinned-arrow" aria-hidden="true">→</span>
260 <span class="post-pinned-body">
261 <span class="post-pinned-label">📌 Volgende vastgepinde</span>
262 <span class="post-pinned-title"><%= nextPinnedPost.title %></span>
263 <span class="post-pinned-cta">← Lees deze post</span>
264 </span>
265 </a>
266 <% } else if (pinnedBottomOfStack) { %>
267 <span class="post-pinned-card post-pinned-card--edge">
268 <span class="post-pinned-edge-label">— ONDERAAN —</span>
269 <span class="post-pinned-edge-title">Oudste post</span>
270 </span>
271 <% } %>
272 </nav>
273 <% } %>
274
275 <%# ── Chronological prev/next (kept for non-pinned posts) ──────
276 For pinned posts the pinned-nav above replaces this. Non-pinned
277 regular posts still get the simple older/newer pair. %>
278 <% if (!post.pinned && (prevPost || nextPost)) { %>
279 <nav class="post-nav">
280 <% if (prevPost) { %>
281 <a class="post-nav-prev" href="/<%= prevPost.slug %>"
282 hx-get="/<%= prevPost.slug %>?partial=1" hx-target="#pcms-main"
283 hx-push-url="/<%= prevPost.slug %>" hx-indicator="#pcms-loading">
284 <span class="post-nav-label">← Older</span>
285 <span class="post-nav-title"><%= prevPost.title %></span>
286 </a>
287 <% } else { %><span></span><% } %>
288 <% if (nextPost) { %>
289 <a class="post-nav-next" href="/<%= nextPost.slug %>"
290 hx-get="/<%= nextPost.slug %>?partial=1" hx-target="#pcms-main"
291 hx-push-url="/<%= nextPost.slug %>" hx-indicator="#pcms-loading">
292 <span class="post-nav-label">Newer →</span>
293 <span class="post-nav-title"><%= nextPost.title %></span>
294 </a>
295 <% } %>
296 </nav>
297 <% } %>
298</article>
299<style>
300.post-page { max-width: 720px; margin: 2rem auto; padding: 0 1rem; }
301.post-cover { margin: 0 0 2rem; border-radius: 8px; overflow: hidden; }
302.post-cover img { width: 100%; height: auto; display: block; }
303.post-header { margin-bottom: 2rem; }
304.post-title { font-family: var(--font-display, serif); font-size: 2.5rem; line-height: 1.1; margin: 0 0 1rem; }
305@media (max-width: 600px) { .post-title { font-size: 1.85rem; } }
306.post-meta { font-size: 0.9rem; color: var(--ink-muted); }
307.author { font-weight: 500; color: var(--ink-soft); text-decoration: none; }
308.author:hover { color: var(--accent); }
309.post-content { font-family: var(--font-body, serif); font-size: 1.1rem; line-height: 1.7; color: var(--ink); }
310.post-content p { margin: 1.2em 0; }
311.post-content h2 { font-family: var(--font-display, serif); font-size: 1.6rem; margin: 2rem 0 1rem; }
312.post-content h3 { font-family: var(--font-display, serif); font-size: 1.3rem; margin: 1.5rem 0 0.75rem; }
313.post-content a { color: var(--accent); }
314.post-content code { background: var(--paper-2); padding: 0.1em 0.4em; border-radius: 3px; font-family: var(--font-mono, monospace); font-size: 0.9em; }
315.post-content pre { background: var(--paper-2); padding: 1rem; border-radius: 6px; overflow-x: auto; }
316.post-content blockquote { border-left: 3px solid var(--accent); padding-left: 1rem; margin: 1.5rem 0; color: var(--ink-soft); font-style: italic; }
317.post-content img { max-width: 100%; height: auto; border-radius: 6px; }
318.post-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 2rem 0; }
319.tag { background: var(--paper-2); color: var(--ink-soft); padding: 0.2rem 0.6rem; border-radius: 999px; font-size: 0.85rem; text-decoration: none; transition: background 120ms, color 120ms; }
320.tag:hover { background: var(--accent); color: white; }
321.post-actions { display: flex; gap: 0.5rem; margin: 2rem 0; padding: 1rem; background: var(--paper-2); border-radius: 6px; flex-wrap: wrap; }
322.post-nav { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--rule); }
323.post-nav-prev, .post-nav-next { display: flex; flex-direction: column; padding: 1rem; background: var(--paper-2); border-radius: 6px; text-decoration: none; color: var(--ink); }
324.post-nav-next { text-align: right; align-items: flex-end; }
325.post-nav-label { font-size: 0.8rem; color: var(--ink-muted); margin-bottom: 0.25rem; }
326.post-nav-title { font-weight: 500; }
327@media (max-width: 600px) { .post-nav { grid-template-columns: 1fr; } }
328
329/* ============================================================
330 Comments
331 ============================================================ */
332.post-comments {
333 margin-top: 3rem;
334 padding-top: 2rem;
335 border-top: 1px solid var(--rule);
336}
337.comments-heading {
338 font-family: var(--font-display, serif);
339 font-size: 1.5rem;
340 margin: 0 0 1.5rem;
341}
342.comments-empty { color: var(--ink-muted, var(--ink-soft)); margin: 0 0 1.5rem; }
343.comments-pending-flash {
344 background: rgba(40, 160, 90, 0.15);
345 color: #2a9d5e;
346 border: 1px solid rgba(40, 160, 90, 0.3);
347 padding: 0.75rem 1rem;
348 border-radius: 6px;
349 margin-bottom: 1rem;
350 font-size: 0.9rem;
351}
352.comments-empty a { color: var(--accent); }
353
354.comments-list, .comment-replies {
355 list-style: none;
356 padding: 0;
357 margin: 0 0 2rem;
358}
359
360.comment {
361 display: flex;
362 gap: 0.85rem;
363 padding: 1rem 0;
364 border-top: 1px solid var(--rule);
365}
366.comments-list > .comment:first-child { border-top: 0; padding-top: 0; }
367
368.comment-avatar {
369 flex-shrink: 0;
370 width: 40px;
371 height: 40px;
372 border-radius: 50%;
373 background: var(--paper-2);
374 border: 1px solid var(--rule);
375 overflow: hidden;
376 display: inline-flex;
377 align-items: center;
378 justify-content: center;
379}
380.comment-avatar img { width: 100%; height: 100%; object-fit: cover; }
381.comment-avatar span {
382 font-family: var(--font-display, serif);
383 font-weight: 700;
384 color: var(--accent);
385}
386
387.comment-body { flex: 1; min-width: 0; }
388.comment-meta {
389 display: flex;
390 gap: 0.5rem;
391 align-items: baseline;
392 font-size: 0.85rem;
393 margin-bottom: 0.3rem;
394}
395.comment-author { color: var(--ink); font-weight: 600; text-decoration: none; }
396.comment-author:hover { color: var(--accent); }
397.comment-time { color: var(--ink-muted, var(--ink-soft)); }
398.comment-content {
399 white-space: pre-wrap;
400 word-wrap: break-word;
401 color: var(--ink);
402 line-height: 1.5;
403 font-size: 0.95rem;
404}
405
406.comment-actions {
407 display: flex;
408 gap: 0.5rem;
409 margin-top: 0.4rem;
410}
411.comment-reply-btn, .comment-delete-btn {
412 background: none;
413 border: 0;
414 color: var(--ink-muted, var(--ink-soft));
415 font-size: 0.8rem;
416 cursor: pointer;
417 padding: 0.2rem 0;
418 font-family: inherit;
419 text-transform: uppercase;
420 letter-spacing: 0.04em;
421}
422.comment-reply-btn:hover { color: var(--accent); }
423.comment-delete-btn:hover { color: #c33; }
424
425.comment-replies {
426 margin-top: 1rem;
427 margin-left: 0;
428 border-left: 2px solid var(--rule);
429 padding-left: 1rem;
430}
431.comment-reply { padding: 0.75rem 0; }
432.comment-replies > .comment-reply:first-child { border-top: 0; padding-top: 0; }
433.comment-reply .comment-avatar { width: 32px; height: 32px; }
434.comment-reply .comment-avatar span { font-size: 0.85rem; }
435
436.comment-form, .comment-reply-form {
437 display: flex;
438 flex-direction: column;
439 gap: 0.6rem;
440 background: var(--paper-2);
441 border: 1px solid var(--rule);
442 border-radius: 8px;
443 padding: 1rem;
444 margin-top: 1rem;
445}
446.comment-form-label { display: flex; flex-direction: column; gap: 0.4rem; }
447.comment-form-label > span { font-size: 0.85rem; color: var(--ink-muted, var(--ink-soft)); }
448.comment-form textarea, .comment-reply-form textarea {
449 width: 100%;
450 resize: vertical;
451 min-height: 70px;
452 padding: 0.65rem 0.85rem;
453 border: 1px solid var(--rule);
454 border-radius: 5px;
455 background: var(--paper);
456 color: var(--ink);
457 font-family: inherit;
458 font-size: 0.95rem;
459 line-height: 1.4;
460 box-sizing: border-box;
461}
462.comment-form button { align-self: flex-start; }
463.comment-reply-form-actions { display: flex; gap: 0.5rem; }
464
465.comments-login-cta { margin-top: 1.5rem; text-align: center; }
466
467/* ─── Related posts (3-card grid above pinned-nav) ─────────────── */
468.post-related {
469 margin: 3rem auto 1.5rem;
470 /* Break out of the 720px article column so the cards have room to breathe.
471 Cap at 1100 to match other wide content. */
472 max-width: 1100px;
473 padding: 0 1rem;
474}
475.post-related-heading {
476 font-family: var(--font-ui, system-ui), sans-serif;
477 font-size: 0.75rem;
478 font-weight: 600;
479 text-transform: uppercase;
480 letter-spacing: 0.15em;
481 color: var(--ink-muted, var(--ink-soft));
482 margin: 0 0 1rem;
483}
484.post-related-grid {
485 list-style: none;
486 padding: 0; margin: 0;
487 display: grid;
488 grid-template-columns: repeat(3, minmax(0, 1fr));
489 gap: 1rem;
490}
491@media (max-width: 880px) { .post-related-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
492@media (max-width: 540px) { .post-related-grid { grid-template-columns: 1fr; } }
493
494.post-related-card {
495 background: var(--paper);
496 border: 1px solid var(--rule);
497 border-radius: 10px;
498 overflow: hidden;
499 transition: border-color 150ms, transform 150ms;
500}
501.post-related-card:hover { border-color: var(--accent); transform: translateY(-2px); }
502.post-related-card a {
503 display: block;
504 color: inherit;
505 text-decoration: none;
506}
507.post-related-cover {
508 display: block;
509 aspect-ratio: 16 / 10;
510 background: var(--paper-2) center/cover no-repeat;
511}
512.post-related-cover--empty {
513 background-image: linear-gradient(135deg,
514 color-mix(in srgb, var(--accent) 12%, var(--paper-2)),
515 var(--paper-2));
516}
517.post-related-meta {
518 display: flex; flex-direction: column; gap: 0.4rem;
519 padding: 0.85rem 1rem 1.1rem;
520}
521.post-related-date {
522 font-size: 0.78rem;
523 color: var(--ink-muted, var(--ink-soft));
524 font-variant-numeric: tabular-nums;
525}
526.post-related-title {
527 font-family: var(--font-display, serif);
528 font-size: 1.1rem;
529 font-weight: 600;
530 color: var(--ink);
531 line-height: 1.25;
532}
533
534/* ─── Pinned navigation cards (v9 style) ───────────────────────── */
535.post-pinned-nav {
536 display: grid;
537 grid-template-columns: 1fr 1fr;
538 gap: 1rem;
539 margin: 1.5rem auto 2rem;
540 max-width: 1100px;
541 padding: 0 1rem;
542}
543@media (max-width: 600px) {
544 .post-pinned-nav { grid-template-columns: 1fr; }
545}
546
547.post-pinned-card {
548 display: flex; align-items: center; gap: 1rem;
549 padding: 1rem 1.25rem;
550 border: 1.5px solid color-mix(in srgb, var(--accent) 50%, transparent);
551 background: color-mix(in srgb, var(--accent) 6%, var(--paper));
552 border-radius: 10px;
553 text-decoration: none;
554 color: var(--ink);
555 transition: border-color 150ms, background 150ms, transform 150ms;
556 min-height: 88px;
557}
558.post-pinned-card:hover {
559 border-color: var(--accent);
560 background: color-mix(in srgb, var(--accent) 11%, var(--paper));
561 transform: translateY(-2px);
562}
563.post-pinned-arrow {
564 font-size: 1.6rem;
565 color: var(--accent);
566 flex-shrink: 0;
567 line-height: 1;
568}
569.post-pinned-card--next {
570 /* "Volgende" card aligns text to the right with the arrow on the right
571 side, mirroring v9 — the arrow is on the OPPOSITE end from the body. */
572 flex-direction: row-reverse;
573 text-align: right;
574}
575.post-pinned-body {
576 display: flex; flex-direction: column; gap: 0.25rem;
577 flex: 1; min-width: 0;
578}
579.post-pinned-label {
580 font-family: var(--font-ui, system-ui), sans-serif;
581 font-size: 0.7rem; font-weight: 600;
582 text-transform: uppercase;
583 letter-spacing: 0.1em;
584 color: var(--accent);
585}
586.post-pinned-title {
587 font-family: var(--font-display, serif);
588 font-size: 1.05rem; font-weight: 600;
589 color: var(--ink);
590 line-height: 1.25;
591 overflow: hidden;
592 text-overflow: ellipsis;
593 display: -webkit-box;
594 -webkit-line-clamp: 2;
595 -webkit-box-orient: vertical;
596}
597.post-pinned-cta {
598 font-size: 0.78rem;
599 color: var(--accent);
600 font-weight: 500;
601}
602
603/* Edge-of-stack indicator: dashed border, muted styling. */
604.post-pinned-card--edge {
605 flex-direction: column;
606 align-items: flex-start;
607 justify-content: center;
608 gap: 0.35rem;
609 border-style: dashed;
610 background: transparent;
611 color: var(--ink-muted, var(--ink-soft));
612 cursor: default;
613}
614.post-pinned-card--edge:hover {
615 /* Edge cards aren't clickable — undo the lift effect. */
616 transform: none;
617 border-color: color-mix(in srgb, var(--accent) 50%, transparent);
618 background: transparent;
619}
620.post-pinned-edge-label {
621 font-family: var(--font-ui, system-ui), sans-serif;
622 font-size: 0.72rem;
623 font-weight: 600;
624 text-transform: uppercase;
625 letter-spacing: 0.15em;
626 color: var(--accent);
627}
628.post-pinned-edge-title {
629 font-family: var(--font-display, serif);
630 font-size: 1.05rem;
631 font-weight: 600;
632 color: var(--ink);
633}
634</style>
Note: See TracBrowser for help on using the repository browser.