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

main
Last change on this file since 7eb35ae was 8e29f77, checked in by roboburr <roboburr@…>, 3 months ago

Post: less blank space above the nav (.post-page margin-top 2rem → 0.5rem)

Now that the compact Newer/Older nav sits at the top, the 2rem top margin of
.post-page was too much (48px gap between chrome and nav). Down to 0.5rem → ~24px.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

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