source: Klonkt/src/views/pages/epk.ejs@ 255e3d3

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

EPK / press kit (premium feature #4): shareable /pers press page per Klonkt site

New premium-gated /pers (solo) or /user/:slug/pers (hub): a clean, public press
kit with hero (photo/title/tagline), short bio, top tracks, recent posts, and a
contact/booking button. Intended to be shared with bookers/press.

  • src/routes/epk.js: GET /pers, premiumUnlocked() gate (non-premium -> next() -> 404), page itself is public (no login). No email leak: contact via explicit setting epk_contact[_<siteId>], bio via epk_bio_<siteId> (fallback site.tagline).
  • src/views/pages/epk.ejs: standalone press-kit view (inline scoped styles, site-accent).
  • server.js: epkRoutes mounted before the postsRoutes catch-all.

First of the 8 premium features. node --check passed; local test suite only fails
on missing better-sqlite3 in this Windows clone (runs on the VPS).

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

  • Property mode set to 100644
File size: 5.1 KB
Line 
1<%
2 // Duur mm:ss
3 function fmtDur(s){ s = parseInt(s,10)||0; var m=Math.floor(s/60), x=s%60; return m+':'+String(x).padStart(2,'0'); }
4 var _accent = (site && site.accent) ? site.accent : '';
5 var _photo = (site && site.profile_photo) ? site.profile_photo : '';
6 var _isMail = epkContact && epkContact.indexOf('@') !== -1 && epkContact.indexOf(' ') === -1;
7 var _isUrl = epkContact && /^https?:\/\//i.test(epkContact);
8%>
9<section class="epk" <%= _accent ? ('style="--epk-accent:' + _accent + '"') : '' %>>
10 <header class="epk-hero">
11 <% if (_photo) { %><div class="epk-photo"><img src="<%= _photo %>" alt="<%= site.title %>"></div><% } %>
12 <div class="epk-hero-text">
13 <div class="epk-kicker">Perskit</div>
14 <h1 class="epk-title"><%= site.title %></h1>
15 <% if (epkBio) { %><p class="epk-bio"><%= epkBio %></p><% } %>
16 <div class="epk-actions">
17 <% if (_isMail) { %>
18 <a class="epk-btn epk-btn-primary" href="mailto:<%= epkContact %>">✉ Contact / boeking</a>
19 <% } else if (_isUrl) { %>
20 <a class="epk-btn epk-btn-primary" href="<%= epkContact %>" target="_blank" rel="noopener">Contact / boeking</a>
21 <% } %>
22 <a class="epk-btn" href="<%= siteUrlBase %>/">Bekijk de site →</a>
23 <% if (_photo) { %><a class="epk-btn" href="<%= _photo %>" download>⬇ Persfoto</a><% } %>
24 </div>
25 </div>
26 </header>
27
28 <% if (epkTracks && epkTracks.length) { %>
29 <div class="epk-block">
30 <h2 class="epk-h2">Muziek</h2>
31 <ul class="epk-tracks">
32 <% epkTracks.forEach(function(t){ %>
33 <li class="epk-track">
34 <% if (t.cover_url) { %><span class="epk-cover" style="background-image:url('<%= t.cover_url %>')"></span><% } else { %><span class="epk-cover epk-cover-empty">♪</span><% } %>
35 <span class="epk-track-meta">
36 <span class="epk-track-title"><%= t.title %></span>
37 <% if (t.artist) { %><span class="epk-track-artist"><%= t.artist %></span><% } %>
38 </span>
39 <% if (t.duration) { %><span class="epk-track-dur"><%= fmtDur(t.duration) %></span><% } %>
40 </li>
41 <% }); %>
42 </ul>
43 </div>
44 <% } %>
45
46 <% if (epkPosts && epkPosts.length) { %>
47 <div class="epk-block">
48 <h2 class="epk-h2">Recent</h2>
49 <ul class="epk-posts">
50 <% epkPosts.forEach(function(p){ %>
51 <li><a href="<%= siteUrlBase %>/<%= p.slug %>"><%= p.title %></a></li>
52 <% }); %>
53 </ul>
54 </div>
55 <% } %>
56
57 <footer class="epk-foot">
58 <% if (_isMail) { %><a href="mailto:<%= epkContact %>"><%= epkContact %></a> · <% } %>
59 <a href="<%= siteUrlBase %>/"><%= site.title %></a>
60 </footer>
61</section>
62
63<style>
64 .epk { max-width: 860px; margin: 0 auto; padding: 28px 18px 64px; --epk-accent: var(--accent, #6b8f71); }
65 .epk-hero { display: flex; gap: 26px; align-items: center; flex-wrap: wrap; margin-bottom: 40px; }
66 .epk-photo { flex: 0 0 auto; }
67 .epk-photo img { width: 190px; height: 190px; object-fit: cover; border-radius: 18px; box-shadow: 0 8px 30px rgba(0,0,0,.18); }
68 .epk-hero-text { flex: 1 1 280px; min-width: 240px; }
69 .epk-kicker { text-transform: uppercase; letter-spacing: .14em; font-size: 12px; font-weight: 700; color: var(--epk-accent); margin-bottom: 6px; }
70 .epk-title { font-size: clamp(30px, 6vw, 52px); line-height: 1.05; margin: 0 0 12px; }
71 .epk-bio { font-size: 16px; line-height: 1.6; opacity: .9; margin: 0 0 18px; max-width: 52ch; }
72 .epk-actions { display: flex; gap: 10px; flex-wrap: wrap; }
73 .epk-btn { display: inline-block; padding: 9px 15px; border-radius: 999px; border: 1px solid rgba(128,128,128,.35); text-decoration: none; font-size: 13.5px; font-weight: 600; color: inherit; }
74 .epk-btn-primary { background: var(--epk-accent); border-color: var(--epk-accent); color: #fff; }
75 .epk-block { margin: 34px 0; }
76 .epk-h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .1em; opacity: .6; margin: 0 0 14px; }
77 .epk-tracks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
78 .epk-track { display: flex; align-items: center; gap: 12px; padding: 8px 10px; border: 1px solid rgba(128,128,128,.18); border-radius: 12px; }
79 .epk-cover { flex: 0 0 auto; width: 42px; height: 42px; border-radius: 8px; background-size: cover; background-position: center; display: grid; place-items: center; }
80 .epk-cover-empty { background: var(--epk-accent); color: #fff; opacity: .85; }
81 .epk-track-meta { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
82 .epk-track-title { font-weight: 600; }
83 .epk-track-artist { font-size: 12.5px; opacity: .65; }
84 .epk-track-dur { font-variant-numeric: tabular-nums; opacity: .6; font-size: 13px; }
85 .epk-posts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
86 .epk-posts a { text-decoration: none; color: inherit; border-bottom: 1px solid transparent; }
87 .epk-posts a:hover { border-bottom-color: var(--epk-accent); }
88 .epk-foot { margin-top: 44px; padding-top: 18px; border-top: 1px solid rgba(128,128,128,.2); font-size: 13px; opacity: .7; }
89 .epk-foot a { color: inherit; }
90</style>
Note: See TracBrowser for help on using the repository browser.