source: Klonkt/src/views/pages/post.ejs@ e91849c

main
Last change on this file since e91849c was e91849c, checked in by Robin Genis <roboburr@…>, 3 months ago

ux(fedi): owner replies to a fediverse comment directly (no 'your server' detour)

On your own site, the reply button under an inbound fediverse comment showed
the visitor flow (asks for 'your server' and bounces to authorize_interaction).
For the site owner that is pointless — you ARE the server. Owners now get an
inline reply box that posts straight to /posts/:slug/fedi-reply (deliverReply as
the site); visitors keep the remote-interaction popover. Exposes the interaction
id on inbound nodes + threads postSlug through fedi-node.

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

  • Property mode set to 100644
File size: 21.2 KB
Line 
1<%
2// In hub mode the site is accessible under /user/<slug>; all site-scoped links
3// (tag, edit, delete, comments) must carry that prefix otherwise they fall back
4// to the primary site → 404 / wrong site. In solo mode _base = ''.
5const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
6%>
7<article class="container post-page">
8
9 <%# Navigation across ALL posts — ABOVE the post (shared partial). %>
10 <%- include('../partials/post-nav', { newerPost: newerPost, olderPost: olderPost }) %>
11
12 <% if (post.cover_image_url) { %>
13 <figure class="post-cover">
14 <img src="<%= post.cover_image_url %>" alt="">
15 </figure>
16 <% } %>
17
18 <header class="post-header">
19 <h1 class="post-title"><%= post.title %></h1>
20 <div class="post-meta">
21 <a class="author" href="/users/<%= encodeURIComponent(post.author_username) %>"
22 hx-get="/users/<%= encodeURIComponent(post.author_username) %>?partial=1"
23 hx-target="#pcms-main" hx-swap="innerHTML"
24 hx-push-url="/users/<%= encodeURIComponent(post.author_username) %>"
25 hx-indicator="#pcms-loading"><%= post.author_username %></a>
26 <span class="meta-sep">·</span>
27 <time datetime="<%= post.published_at || post.created_at %>"><%= formatDate(post.published_at || post.created_at) %></time>
28 <% if (post.status !== 'published') { %>
29 <span class="meta-sep">·</span>
30 <span class="status-badge status-<%= post.status %>"><%= post.status %></span>
31 <% } %>
32 </div>
33 </header>
34
35 <div class="post-content">
36 <%- post.content_html %>
37 </div>
38
39 <% if (post.tags && post.tags.length > 0) { %>
40 <div class="post-tags">
41 <% post.tags.forEach(t => { %>
42 <a class="tag" href="<%= _base %>/tag/<%= encodeURIComponent(t) %>"
43 hx-get="<%= _base %>/tag/<%= encodeURIComponent(t) %>?partial=1"
44 hx-target="#pcms-main" hx-swap="innerHTML"
45 hx-push-url="<%= _base %>/tag/<%= encodeURIComponent(t) %>"
46 hx-indicator="#pcms-loading">#<%= t %></a>
47 <% }); %>
48 </div>
49 <% } %>
50
51 <!-- Inline admin actions: only visible if user has permission -->
52 <% if (user && (permissions.canEditPost(user, post, site) || permissions.canDeletePost(user, post, site))) { %>
53 <aside class="post-actions">
54 <% if (permissions.canEditPost(user, post, site)) { %>
55 <a href="<%= _base %>/posts/<%= post.slug %>/edit" class="btn"
56 hx-get="<%= _base %>/posts/<%= post.slug %>/edit?partial=1" hx-target="#pcms-main"
57 hx-push-url="<%= _base %>/posts/<%= post.slug %>/edit" hx-indicator="#pcms-loading">
58 ✏️ Edit
59 </a>
60 <% } %>
61 <% if (permissions.canDeletePost(user, post, site)) { %>
62 <form method="post" action="<%= _base %>/posts/<%= post.slug %>/delete" style="display:inline" onsubmit="return confirm('Delete this post?')">
63 <button type="submit" class="btn btn-danger">🗑 Delete</button>
64 </form>
65 <% } %>
66 </aside>
67 <% } %>
68
69 <!-- Fediverse interactions — single place: ⭐ like (clickable) + 🔁/💬 counts + reply -->
70 <% var _postAbsUrl = (typeof ogOrigin !== 'undefined' && ogOrigin ? ogOrigin : '') + (typeof _base !== 'undefined' && _base ? _base : '') + '/' + post.slug; %>
71 <% var _fedi = (typeof fediverse !== 'undefined' && fediverse) ? fediverse : { thread: [], likeCount: 0, announceCount: 0 }; %>
72 <% if (post.status === 'published') { %>
73 <section class="post-fediverse post-comments" id="fediverse">
74 <h2 class="comments-heading"><%= t('fedi.heading') %></h2>
75 <div class="fedi-stats">
76 <button type="button" class="fedi-stat fedi-stat-btn fedi-remote-reply-btn" data-fedi-uri="<%= _postAbsUrl %>" data-fedi-ph="<%= t('fedi.remote_ph') %>" title="<%= t('like.fedi_title') %>">
77 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
78 <span><%= _fedi.likeCount %></span>
79 </button>
80 <span class="fedi-stat" title="<%= t('fedi.boosts') %>">
81 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
82 <span><%= _fedi.announceCount %></span>
83 </span>
84 <span class="fedi-stat" title="<%= t('fedi.replies') %>">
85 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
86 <span><%= (_fedi.thread || []).length %></span>
87 </span>
88 </div>
89 <% if (_fedi.thread && _fedi.thread.length) { %>
90 <ol class="comments-list">
91 <% _fedi.thread.forEach(function(n){ %>
92 <li class="comment"><%- include('../partials/fedi-node', { n: n, t: t, canManageSite: (typeof canManageSite !== 'undefined' ? canManageSite : false), _base: _base, siteAvatar: (typeof siteAvatar !== 'undefined' ? siteAvatar : null), formatDateTime: formatDateTime, postSlug: post.slug }) %></li>
93 <% }); %>
94 </ol>
95 <% } %>
96 <div class="post-fediverse-cta">
97 <button type="button" class="btn fedi-remote-reply-btn" data-fedi-uri="<%= _postAbsUrl %>" data-fedi-ph="<%= t('fedi.remote_ph') %>">
98 <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg>
99 <%= t('fedi.remote_reply') %>
100 </button>
101 </div>
102 </section>
103 <% } %>
104 <script>
105 (function(){
106 if (window.__fediRemoteWired) return; window.__fediRemoteWired = true;
107 var current = null, currentBtn = null;
108 function close(){ if (current) { current.remove(); current = null; currentBtn = null; } }
109 function go(raw, uri){
110 var d = (raw||'').trim().replace(/^@?[^@\s]*@/, '').replace(/^https?:\/\//i, '').replace(/\/.*$/, '').trim();
111 if (d) location.href = 'https://' + d + '/authorize_interaction?uri=' + encodeURIComponent(uri||'');
112 }
113 function place(f, b){
114 var r = b.getBoundingClientRect();
115 f.style.top = (r.bottom + window.scrollY + 6) + 'px';
116 f.style.left = Math.max(8, Math.min(r.left + window.scrollX, window.scrollX + window.innerWidth - 340)) + 'px';
117 }
118 document.addEventListener('click', function(e){
119 if (e.target.closest && e.target.closest('.fedi-remote-cancel')) { close(); return; }
120 if (current && e.target.closest && e.target.closest('.fedi-remote-form')) return; // click inside → keep
121 var b = e.target.closest && e.target.closest('.fedi-remote-reply-btn');
122 if (b) {
123 e.preventDefault();
124 if (currentBtn === b) { close(); return; } // toggle off
125 close();
126 var f = document.createElement('form');
127 f.className = 'fedi-remote-form';
128 f.dataset.uri = b.getAttribute('data-fedi-uri') || '';
129 f.innerHTML = '<input type="text" autocomplete="off" spellcheck="false">'
130 + '<button type="submit" class="btn btn-primary fedi-remote-go" aria-label="ok">&rarr;</button>'
131 + '<button type="button" class="fedi-remote-cancel" aria-label="x">&times;</button>';
132 f.querySelector('input').placeholder = b.getAttribute('data-fedi-ph') || 'mastodon.social';
133 document.body.appendChild(f); // floating popover → no layout reflow
134 place(f, b); current = f; currentBtn = b;
135 f.querySelector('input').focus();
136 return;
137 }
138 if (current) close(); // click anywhere else closes it
139 });
140 document.addEventListener('submit', function(e){
141 var f = e.target.closest && e.target.closest('.fedi-remote-form');
142 if (!f) return; e.preventDefault();
143 go(f.querySelector('input').value, f.dataset.uri);
144 });
145 document.addEventListener('keydown', function(e){ if (e.key === 'Escape') close(); });
146 window.addEventListener('scroll', close, true);
147 })();
148 </script>
149
150
151 <%# ── Related posts (3-card grid) ─────────────────────────────
152 Always shown if at least one related post exists. Selection logic
153 lives server-side: same-tag posts ranked by overlap, falling back
154 to most-recent if there's no tag overlap. %>
155 <% if (relatedPosts && relatedPosts.length > 0) { %>
156 <section class="post-related" aria-labelledby="related-heading">
157 <h2 class="post-related-heading" id="related-heading"><%= t('related.title') %></h2>
158 <ul class="post-related-grid">
159 <% relatedPosts.forEach(function(rp) { %>
160 <li class="post-related-card">
161 <a href="<%= rp._urlBase || '' %>/<%= rp.slug %>"<% if (!rp._urlBase) { %>
162 hx-get="/<%= rp.slug %>?partial=1" hx-target="#pcms-main"
163 hx-push-url="/<%= rp.slug %>" hx-indicator="#pcms-loading"<% } %>>
164 <% if (rp.cover_image_url) { %>
165 <span class="post-related-cover"
166 style="background-image: url('<%= rp.cover_image_url %>')"
167 aria-hidden="true"></span>
168 <% } else { %>
169 <span class="post-related-cover post-related-cover--empty" aria-hidden="true"></span>
170 <% } %>
171 <span class="post-related-meta">
172 <span class="post-related-date"><%= formatDate(rp.published_at) %></span>
173 <span class="post-related-title"><%= rp.title %></span>
174 </span>
175 </a>
176 </li>
177 <% }); %>
178 </ul>
179 </section>
180 <% } %>
181
182</article>
183<style>
184.post-page { max-width: 720px; margin: 0.5rem auto 2rem; padding: 0 1rem; }
185.post-cover { margin: 0 0 2rem; border-radius: 8px; overflow: hidden; }
186.post-cover img { width: 100%; height: auto; display: block; }
187.post-header { margin-bottom: 2rem; }
188.post-title { font-family: var(--font-display, serif); font-size: 2.5rem; line-height: 1.1; margin: 0 0 1rem; }
189@media (max-width: 600px) { .post-title { font-size: 1.85rem; } }
190.post-meta { font-size: 0.9rem; color: var(--ink-muted); }
191.author { font-weight: 500; color: var(--ink-soft); text-decoration: none; }
192.author:hover { color: var(--accent); }
193.post-content { font-family: var(--font-body, serif); font-size: 1.1rem; line-height: 1.7; color: var(--ink); }
194.post-content p { margin: 1.2em 0; }
195.like-btn {
196 display: inline-flex; align-items: center; gap: 0.45rem;
197 padding: 0.4rem 0.85rem; border: 1px solid var(--rule); border-radius: 999px;
198 background: var(--paper-2); color: var(--ink); font: inherit; font-size: 0.95rem;
199 cursor: pointer; text-decoration: none; transition: border-color 120ms, color 120ms, background 120ms;
200}
201.like-btn:hover { border-color: var(--accent); }
202.like-btn .like-heart { font-size: 1.1rem; line-height: 1; }
203.like-btn.is-liked { border-color: var(--accent); color: var(--accent); }
204.like-btn.is-liked .like-heart { color: var(--accent); }
205.like-btn .like-count { font-variant-numeric: tabular-nums; }
206.post-content h2 { font-family: var(--font-display, serif); font-size: 1.6rem; margin: 2rem 0 1rem; }
207.post-content h3 { font-family: var(--font-display, serif); font-size: 1.3rem; margin: 1.5rem 0 0.75rem; }
208.post-content a { color: var(--accent); }
209.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; }
210.post-content pre { background: var(--paper-2); padding: 1rem; border-radius: 6px; overflow-x: auto; }
211.post-content blockquote { border-left: 3px solid var(--accent); padding-left: 1rem; margin: 1.5rem 0; color: var(--ink-soft); font-style: italic; }
212.post-content img { max-width: 100%; height: auto; border-radius: 6px; }
213.post-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 2rem 0; }
214.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; }
215.tag:hover { background: var(--accent); color: white; }
216.post-actions { display: flex; gap: 0.5rem; margin: 2rem 0; padding: 1rem; background: var(--paper-2); border-radius: 6px; flex-wrap: wrap; }
217
218/* ============================================================
219 Comments
220 ============================================================ */
221.post-comments {
222 margin-top: 3rem;
223 padding-top: 2rem;
224 border-top: 1px solid var(--rule);
225}
226.comments-heading {
227 font-family: var(--font-display, serif);
228 font-size: 1.5rem;
229 margin: 0 0 1.5rem;
230}
231.comments-empty { color: var(--ink-muted, var(--ink-soft)); margin: 0 0 1.5rem; }
232.comments-pending-flash {
233 background: rgba(40, 160, 90, 0.15);
234 color: #2a9d5e;
235 border: 1px solid rgba(40, 160, 90, 0.3);
236 padding: 0.75rem 1rem;
237 border-radius: 6px;
238 margin-bottom: 1rem;
239 font-size: 0.9rem;
240}
241.comments-empty a { color: var(--accent); }
242
243.comments-list, .comment-replies {
244 list-style: none;
245 padding: 0;
246 margin: 0 0 2rem;
247}
248
249.comment {
250 display: flex;
251 gap: 0.85rem;
252 padding: 1rem 0;
253 border-top: 1px solid var(--rule);
254}
255.comments-list > .comment:first-child { border-top: 0; padding-top: 0; }
256
257.comment-avatar {
258 flex-shrink: 0;
259 width: 40px;
260 height: 40px;
261 border-radius: 50%;
262 background: var(--paper-2);
263 border: 1px solid var(--rule);
264 overflow: hidden;
265 display: inline-flex;
266 align-items: center;
267 justify-content: center;
268}
269.comment-avatar img { width: 100%; height: 100%; object-fit: cover; }
270.comment-avatar span {
271 font-family: var(--font-display, serif);
272 font-weight: 700;
273 color: var(--accent);
274}
275
276.comment-body { flex: 1; min-width: 0; }
277.comment-meta {
278 display: flex;
279 gap: 0.5rem;
280 align-items: baseline;
281 flex-wrap: wrap;
282 row-gap: 0.1rem;
283 font-size: 0.85rem;
284 margin-bottom: 0.3rem;
285}
286.comment-author { color: var(--ink); font-weight: 600; text-decoration: none; }
287.comment-author:hover { color: var(--accent); }
288.comment-time { color: var(--ink-muted, var(--ink-soft)); }
289.comment-meta .fedi-handle { overflow-wrap: anywhere; }
290@media (max-width: 560px) {
291 .comment-meta .comment-time { flex-basis: 100%; font-size: 0.78rem; color: var(--ink-faint, var(--ink-muted)); }
292}
293.comment-content {
294 white-space: pre-wrap;
295 word-wrap: break-word;
296 color: var(--ink);
297 line-height: 1.5;
298 font-size: 0.95rem;
299}
300
301.comment-actions {
302 display: flex;
303 gap: 0.5rem;
304 margin-top: 0.4rem;
305}
306.comment-reply-btn, .comment-delete-btn {
307 background: none;
308 border: 0;
309 color: var(--ink-muted, var(--ink-soft));
310 font-size: 0.8rem;
311 cursor: pointer;
312 padding: 0.2rem 0;
313 font-family: inherit;
314 text-transform: uppercase;
315 letter-spacing: 0.04em;
316}
317.comment-reply-btn:hover { color: var(--accent); }
318.comment-delete-btn:hover { color: #c33; }
319
320.comment-replies {
321 margin-top: 1rem;
322 margin-left: 0;
323 border-left: 2px solid var(--rule);
324 padding-left: 1rem;
325}
326.comment-reply { padding: 0.75rem 0; }
327.comment-replies > .comment-reply:first-child { border-top: 0; padding-top: 0; }
328.comment-reply .comment-avatar { width: 32px; height: 32px; }
329.comment-reply .comment-avatar span { font-size: 0.85rem; }
330
331.comment-form, .comment-reply-form {
332 display: flex;
333 flex-direction: column;
334 gap: 0.6rem;
335 background: var(--paper-2);
336 border: 1px solid var(--rule);
337 border-radius: 8px;
338 padding: 1rem;
339 margin-top: 1rem;
340}
341.comment-form-label { display: flex; flex-direction: column; gap: 0.4rem; }
342.comment-form-label > span { font-size: 0.85rem; color: var(--ink-muted, var(--ink-soft)); }
343.comment-form textarea, .comment-reply-form textarea {
344 width: 100%;
345 resize: vertical;
346 min-height: 70px;
347 padding: 0.65rem 0.85rem;
348 border: 1px solid var(--rule);
349 border-radius: 5px;
350 background: var(--paper);
351 color: var(--ink);
352 font-family: inherit;
353 font-size: 0.95rem;
354 line-height: 1.4;
355 box-sizing: border-box;
356}
357.comment-form button { align-self: flex-start; }
358.comment-reply-form-actions { display: flex; gap: 0.5rem; }
359/* Show the reply form only AFTER clicking REPLY. The display:flex above would
360 otherwise override the [hidden] attribute → form was always open. */
361.comment-reply-form[hidden] { display: none; }
362.fedi-owner-reply summary { list-style: none; cursor: pointer; }
363.fedi-owner-reply summary::-webkit-details-marker { display: none; }
364.fedi-owner-reply[open] summary { margin-bottom: .45rem; }
365/* DELETE button is inside a <form>; display:contents makes it a direct flex-sibling
366 of REPLY → pixel-perfect alignment + equal gap. */
367.comment-actions form { display: contents; }
368
369.comments-login-cta { margin-top: 1.5rem; text-align: center; }
370
371/* ─── Related posts (3-card grid above pinned-nav) ─────────────── */
372.post-related {
373 margin: 3rem auto 1.5rem;
374 /* Break out of the 720px article column so the cards have room to breathe.
375 Cap at 1100 to match other wide content. */
376 max-width: 1100px;
377 padding: 0 1rem;
378}
379.post-related-heading {
380 font-family: var(--font-ui, system-ui), sans-serif;
381 font-size: 0.75rem;
382 font-weight: 600;
383 text-transform: uppercase;
384 letter-spacing: 0.15em;
385 color: var(--ink-muted, var(--ink-soft));
386 margin: 0 0 1rem;
387}
388.post-related-grid {
389 list-style: none;
390 padding: 0; margin: 0;
391 display: grid;
392 grid-template-columns: repeat(3, minmax(0, 1fr));
393 gap: 1rem;
394}
395@media (max-width: 880px) { .post-related-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
396@media (max-width: 540px) { .post-related-grid { grid-template-columns: 1fr; } }
397
398.post-related-card {
399 background: var(--paper);
400 border: 1px solid var(--rule);
401 border-radius: 10px;
402 overflow: hidden;
403 transition: border-color 150ms, transform 150ms;
404}
405.post-related-card:hover { border-color: var(--accent); transform: translateY(-2px); }
406.post-related-card a {
407 display: block;
408 color: inherit;
409 text-decoration: none;
410}
411.post-related-cover {
412 display: block;
413 aspect-ratio: 16 / 10;
414 background: var(--paper-2) center/cover no-repeat;
415}
416.post-related-cover--empty {
417 background-image: linear-gradient(135deg,
418 color-mix(in srgb, var(--accent) 12%, var(--paper-2)),
419 var(--paper-2));
420}
421.post-related-meta {
422 display: flex; flex-direction: column; gap: 0.4rem;
423 padding: 0.85rem 1rem 1.1rem;
424}
425.post-related-date {
426 font-size: 0.78rem;
427 color: var(--ink-muted, var(--ink-soft));
428 font-variant-numeric: tabular-nums;
429}
430.post-related-title {
431 font-family: var(--font-display, serif);
432 font-size: 1.1rem;
433 font-weight: 600;
434 color: var(--ink);
435 line-height: 1.25;
436}
437
438/* ─── Pinned navigation cards (v9 style) ───────────────────────── */
439.post-pinned-nav {
440 display: grid;
441 grid-template-columns: 1fr 1fr;
442 gap: 1rem;
443 margin: 1.5rem auto 2rem;
444 max-width: 1100px;
445 padding: 0 1rem;
446}
447@media (max-width: 600px) {
448 .post-pinned-nav { grid-template-columns: 1fr; }
449}
450
451.post-pinned-card {
452 display: flex; align-items: center; gap: 1rem;
453 padding: 1rem 1.25rem;
454 border: 1.5px solid color-mix(in srgb, var(--accent) 50%, transparent);
455 background: color-mix(in srgb, var(--accent) 6%, var(--paper));
456 border-radius: 10px;
457 text-decoration: none;
458 color: var(--ink);
459 transition: border-color 150ms, background 150ms, transform 150ms;
460 min-height: 88px;
461}
462.post-pinned-card:hover {
463 border-color: var(--accent);
464 background: color-mix(in srgb, var(--accent) 11%, var(--paper));
465 transform: translateY(-2px);
466}
467.post-pinned-arrow {
468 font-size: 1.6rem;
469 color: var(--accent);
470 flex-shrink: 0;
471 line-height: 1;
472}
473.post-pinned-card--next {
474 /* "Volgende" card aligns text to the right with the arrow on the right
475 side, mirroring v9 — the arrow is on the OPPOSITE end from the body. */
476 flex-direction: row-reverse;
477 text-align: right;
478}
479.post-pinned-body {
480 display: flex; flex-direction: column; gap: 0.25rem;
481 flex: 1; min-width: 0;
482}
483.post-pinned-label {
484 font-family: var(--font-ui, system-ui), sans-serif;
485 font-size: 0.7rem; font-weight: 600;
486 text-transform: uppercase;
487 letter-spacing: 0.1em;
488 color: var(--accent);
489}
490.post-pinned-title {
491 font-family: var(--font-display, serif);
492 font-size: 1.05rem; font-weight: 600;
493 color: var(--ink);
494 line-height: 1.25;
495 overflow: hidden;
496 text-overflow: ellipsis;
497 display: -webkit-box;
498 -webkit-line-clamp: 2;
499 -webkit-box-orient: vertical;
500}
501.post-pinned-cta {
502 font-size: 0.78rem;
503 color: var(--accent);
504 font-weight: 500;
505}
506
507/* Edge-of-stack indicator: dashed border, muted styling. */
508.post-pinned-card--edge {
509 flex-direction: column;
510 align-items: flex-start;
511 justify-content: center;
512 gap: 0.35rem;
513 border-style: dashed;
514 background: transparent;
515 color: var(--ink-muted, var(--ink-soft));
516 cursor: default;
517}
518.post-pinned-card--edge:hover {
519 /* Edge cards aren't clickable — undo the lift effect. */
520 transform: none;
521 border-color: color-mix(in srgb, var(--accent) 50%, transparent);
522 background: transparent;
523}
524.post-pinned-edge-label {
525 font-family: var(--font-ui, system-ui), sans-serif;
526 font-size: 0.72rem;
527 font-weight: 600;
528 text-transform: uppercase;
529 letter-spacing: 0.15em;
530 color: var(--accent);
531}
532.post-pinned-edge-title {
533 font-family: var(--font-display, serif);
534 font-size: 1.05rem;
535 font-weight: 600;
536 color: var(--ink);
537}
538</style>
Note: See TracBrowser for help on using the repository browser.