source: Klonkt/src/views/partials/post-card.ejs@ b12e20d

main
Last change on this file since b12e20d was 43dbf9c, checked in by roboburr <roboburr@…>, 3 months ago

Circles: /cirkel uses the home view (timeline + grid + switcher)

  • post-card/post-tile external-aware: post.external_url -> external link (target=_blank, no htmx) instead of local /slug. Additive; normal posts unchanged. post-card shows 'via <source>' when external.
  • circle.js maps remote_posts to the local post shape + bodyClass on-cirkel.
  • circle-feed.ejs: feed-timeline (.post-list) + feed-grid (.grid-tiles), same classes as home -> the view-switcher works. shell: on-cirkel = feed-page.

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

  • Property mode set to 100644
File size: 7.2 KB
Line 
1<%
2// Post list item — MOBILE-FIRST.
3//
4// <768px (default):
5// Vertical stack — full-bleed 16:9 cover on top, content below.
6// Whole card is tappable (one big <a> wrapping everything).
7// Active state: scale(0.985) + opacity dim.
8//
9// ≥768px (desktop):
10// v9 pattern — 120px square cover left, content right.
11// Hover state on cover. Tap-target stays large.
12
13const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
14const _external = post.external_url || null;
15const _href = _external || (_base + '/' + post.slug);
16const _ext = !!_external;
17const _src = post.source_name || '';
18const _hasCover = !!post.cover_image_url;
19const _typeLabel = (post.type && post.type !== 'overig' && post.type !== 'post') ? post.type : '';
20const _isPinned = !!post.pinned;
21const _isDraft = post.status && post.status !== 'published';
22
23function _ddmmyyyy(iso) {
24 if (!iso) return '';
25 const d = new Date(iso);
26 if (isNaN(d.getTime())) return '';
27 const pad = n => String(n).padStart(2, '0');
28 return pad(d.getDate()) + '-' + pad(d.getMonth() + 1) + '-' + d.getFullYear();
29}
30
31let _tags = [];
32if (post.tags) {
33 if (Array.isArray(post.tags)) _tags = post.tags;
34 else if (typeof post.tags === 'string') {
35 try {
36 const parsed = JSON.parse(post.tags);
37 _tags = Array.isArray(parsed) ? parsed : post.tags.split(',').map(t => t.trim()).filter(Boolean);
38 } catch (e) {
39 _tags = post.tags.split(',').map(t => t.trim()).filter(Boolean);
40 }
41 }
42}
43%>
44<li class="post-list-item<%= _hasCover ? ' has-cover' : '' %><%= _isPinned ? ' is-pinned' : '' %>">
45
46 <% if (_hasCover) { %>
47 <a class="post-list-cover" href="<%= _href %>"
48 <% if (_ext) { %>target="_blank" rel="noopener"<% } else { %>hx-get="<%= _href %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" hx-push-url="<%= _href %>" hx-indicator="#pcms-loading"<% } %>
49 aria-label="<%= post.title || '(zonder titel)' %>" tabindex="-1">
50 <img src="<%= post.cover_image_url %>" alt="" loading="lazy" decoding="async">
51 </a>
52 <% } %>
53
54 <div class="post-list-body">
55 <div class="post-list-meta mono">
56 <% if (_isPinned) { %><span class="post-list-pin" aria-label="Vastgepind">★</span><% } %>
57 <time datetime="<%= post.published_at || post.created_at %>"><%= _ddmmyyyy(post.published_at || post.created_at) %></time>
58 <% if (_src) { %><span class="post-list-type">&middot; via <%= _src %></span><% } %>
59 <% if (_typeLabel) { %>
60 <span class="post-list-type">· <%= _typeLabel.charAt(0).toUpperCase() + _typeLabel.slice(1) %></span>
61 <% } %>
62 <% if (_isDraft) { %>
63 <span class="post-list-type post-list-draft">· concept</span>
64 <% } %>
65 </div>
66
67 <h3 class="post-list-title">
68 <a href="<%= _href %>"
69 <% if (_ext) { %>target="_blank" rel="noopener"<% } else { %>hx-get="<%= _href %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" hx-push-url="<%= _href %>" hx-indicator="#pcms-loading"<% } %>>
70 <%= post.title || '(zonder titel)' %>
71 </a>
72 </h3>
73
74 <% if (post.excerpt) { %>
75 <p class="post-list-excerpt"><%= post.excerpt %></p>
76 <% } %>
77
78 <% if (_tags.length) { %>
79 <div class="post-list-tags">
80 <% _tags.forEach(function(t) { %>
81 <a href="<%= _base %>/tag/<%= encodeURIComponent(t) %>" class="tag-chip"
82 hx-get="<%= _base %>/tag/<%= encodeURIComponent(t) %>?partial=1" hx-target="#pcms-main"
83 hx-swap="innerHTML" hx-push-url="<%= _base %>/tag/<%= encodeURIComponent(t) %>"
84 hx-indicator="#pcms-loading">#<%= t %></a>
85 <% }); %>
86 </div>
87 <% } %>
88 </div>
89</li>
90
91<style>
92/* ============================================================
93 POST-LIST-ITEM — MOBILE-FIRST OVERRIDE
94 The base v9 styles in /assets/css/style.css use a desktop
95 120×120 grid by default. We flip that: stacked on mobile,
96 side-by-side on ≥768px.
97 ============================================================ */
98
99/* Reset v9's grid on mobile and stack vertically */
100@media (max-width: 767px) {
101 .post-list {
102 gap: 2rem;
103 }
104 .post-list-item {
105 display: flex !important;
106 flex-direction: column;
107 grid-template-columns: none !important;
108 gap: .85rem !important;
109 padding-bottom: 2rem;
110 /* Active feedback for the whole card row */
111 transition: transform 120ms ease, opacity 120ms ease;
112 }
113 .post-list-item:active {
114 transform: scale(0.985);
115 opacity: 0.85;
116 }
117 .post-list-item:last-child {
118 border-bottom: none;
119 padding-bottom: 0;
120 }
121
122 /* Cover: full-bleed of the container, 16:9 ratio, no border-radius
123 on small phones (edge-to-edge feels app-native). 8px radius from 480px+. */
124 .post-list-cover {
125 aspect-ratio: 16 / 9 !important;
126 width: 100% !important;
127 border-radius: 0 !important;
128 /* Negative margin to break out of .container's padding */
129 margin-left: calc(-1 * clamp(1.25rem, 4vw, 2rem));
130 margin-right: calc(-1 * clamp(1.25rem, 4vw, 2rem));
131 width: calc(100% + 2 * clamp(1.25rem, 4vw, 2rem)) !important;
132 background: var(--paper-2);
133 }
134 .post-list-cover img {
135 width: 100%; height: 100%;
136 object-fit: cover;
137 display: block;
138 }
139
140 /* Re-apply rounded corners on slightly larger phones */
141 @media (min-width: 480px) {
142 .post-list-cover {
143 border-radius: var(--radius) !important;
144 margin-left: 0 !important;
145 margin-right: 0 !important;
146 width: 100% !important;
147 }
148 }
149
150 /* Larger title for mobile readability */
151 .post-list-title {
152 font-size: 1.45rem !important;
153 line-height: 1.2 !important;
154 }
155 .post-list-excerpt {
156 font-size: 1rem !important;
157 color: var(--ink-soft) !important;
158 line-height: 1.55 !important;
159 }
160 .post-list-meta {
161 font-size: .8rem !important;
162 gap: .5rem !important;
163 }
164
165 /* Pinned indicator */
166 .post-list-item.is-pinned .post-list-title a {
167 /* No visual change on title — the ★ in meta is enough */
168 }
169
170 /* Tap targets for tags */
171 .post-list-tags { gap: .4rem; }
172 .tag-chip {
173 min-height: 32px;
174 display: inline-flex;
175 align-items: center;
176 padding: .2rem .65rem;
177 }
178}
179
180/* ============================================================
181 DESKTOP — keep v9's 120px-square pattern (style.css does this)
182 We just enforce the layout here for clarity + override safety.
183 ============================================================ */
184@media (min-width: 768px) {
185 .post-list-item.has-cover {
186 display: grid !important;
187 grid-template-columns: 120px 1fr !important;
188 gap: 1.25rem !important;
189 }
190 .post-list-item:not(.has-cover) {
191 grid-template-columns: 1fr !important;
192 }
193 .post-list-cover {
194 aspect-ratio: 1 !important;
195 border-radius: var(--radius) !important;
196 }
197 /* Hover lift on desktop (touch devices use :active above) */
198 @media (hover: hover) {
199 .post-list-cover {
200 transition: transform 200ms ease;
201 }
202 .post-list-item:hover .post-list-cover {
203 transform: scale(1.02);
204 }
205 }
206}
207
208/* ============================================================
209 PIN + DRAFT INDICATORS (shared)
210 ============================================================ */
211.post-list-pin {
212 color: var(--accent);
213 margin-right: .35rem;
214 font-size: .9rem;
215}
216.post-list-draft {
217 color: var(--accent);
218 font-style: italic;
219}
220</style>
Note: See TracBrowser for help on using the repository browser.