source: Klonkt/src/views/partials/footer.ejs@ f7d142f

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

chore(csp): drop the last script unsafe-inline + add Permissions-Policy

Move every inline on* handler to a shared delegated data-* handler, so script-src-attr
can be 'none' instead of 'unsafe-inline'. Add a Permissions-Policy header disabling
camera/microphone/geolocation/Topics (embed features left at default).

  • views/shell.ejs — shared delegated submit/change/click/error handler (data-confirm, data-autosubmit, data-lang-switch, data-selectall, data-back, data-fallback)
  • views/{pages,partials}/*.ejs — 18 inline on* handlers -> data-* attributes (14 files)
  • server.js — scriptSrcAttr 'unsafe-inline' -> 'none'; Permissions-Policy header
  • Property mode set to 100644
File size: 7.3 KB
Line 
1<footer class="site-footer">
2 <% if (typeof footerNewsletter !== 'undefined' && footerNewsletter && (typeof premiumUnlocked === 'undefined' || premiumUnlocked) && typeof site !== 'undefined' && site) { %>
3 <div class="container footer-nl">
4 <span class="footer-nl-label">📬 <%= t('footer.subscribe_cta') %></span>
5 <form method="post" action="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/nieuwsbrief" class="footer-nl-form">
6 <input type="email" name="email" required placeholder="<%= t('common.email_placeholder') %>" autocomplete="email" aria-label="E-mailadres">
7 <button type="submit"><%= t('footer.subscribe') %></button>
8 </form>
9 </div>
10 <% } %>
11 <div class="container site-footer-inner">
12 <div>
13 <span>© <%= new Date().getFullYear() %> <%= (site && site.title) || 'Klonkt' %></span>
14 </div>
15 <%# Install-app button — placed in the footer row (centre), saves a whole extra row.
16 JS detects the platform & opens a modal with PWA install steps. %>
17 <button type="button" class="install-app-btn install-app-btn--footer" data-pcms-install-app>
18 <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path d="M5 20h14v-2H5v2zm7-18L5.33 9h3.84v6h5.66V9h3.84L12 2z" fill="currentColor"/></svg>
19 <span><%= t('footer.install') %></span>
20 </button>
21 <div class="footer-meta">
22 <% if (typeof langs !== 'undefined' && langs && langs.length > 1) { %>
23 <select class="footer-lang" aria-label="<%= t('nav.language') %>"
24 data-lang-switch>
25 <% langs.forEach(function(l){ %><option value="<%= l.code %>"<%= l.active ? ' selected' : '' %>><%= l.name %></option><% }); %>
26 </select>
27 <% } %>
28 <a class="footer-version" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/changelog" title="Bekijk wat er per release veranderd is">Klonkt<%= (typeof appVersion !== 'undefined' && appVersion) ? ' v' + appVersion : '' %></a>
29 <%# Mobile-only theme toggle. On desktop the topnav has its own toggle
30 so we hide this one to avoid two visible toggles at once. The same
31 toggleTheme handler in topnav.ejs picks this button up by id. %>
32 <button type="button" class="footer-theme-toggle" id="theme-toggle-footer"
33 aria-label="Thema wisselen" title="Thema wisselen">
34 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
35 stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
36 <circle cx="12" cy="12" r="4"/>
37 <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
38 </svg>
39 </button>
40 </div>
41 </div>
42
43</footer>
44
45<style>
46/* Make sure the page itself is at least viewport-tall so the flex sticky-
47 footer logic (body display:flex, main flex:1) can push the footer down.
48 100dvh is dynamic viewport height — handles mobile address-bar collapse. */
49html { min-height: 100dvh; }
50
51.site-footer {
52 border-top: 1px solid var(--rule);
53 margin-top: 4rem;
54 padding: 2rem 0;
55 padding-bottom: calc(2rem + env(safe-area-inset-bottom, 0));
56 font-size: 0.85rem;
57 color: var(--ink-muted);
58 background: var(--paper);
59 /* When fixed bars (mini-player / bottom-tab) sit at the bottom of the
60 viewport they overlap the footer at scroll-end. The clearance below
61 mirrors the .pcms-main rule in audio.css — content goes at the top of
62 the footer, the player floats over the empty padding underneath. */
63 transition: padding-bottom .2s ease;
64}
65/* Audio player visible (desktop, no bottom-tab): clear ~80px */
66body.has-audio-player .site-footer {
67 padding-bottom: calc(2rem + 80px + env(safe-area-inset-bottom, 0));
68}
69@media (max-width: 767px) {
70 /* Bottom-tab visible alone: ~56px tab */
71 body.has-bottom-tab .site-footer {
72 padding-bottom: calc(2rem + 56px + env(safe-area-inset-bottom, 0));
73 }
74 /* Bottom-tab + audio-player stacked: ~56px tab + ~72px player */
75 body.has-bottom-tab.has-audio-player .site-footer {
76 padding-bottom: calc(2rem + 56px + 72px + env(safe-area-inset-bottom, 0));
77 }
78}
79.footer-nl {
80 max-width: 800px; margin: 0 auto 1.5rem; padding: 0 1rem;
81 display: flex; align-items: center; justify-content: center; gap: .8rem; flex-wrap: wrap;
82}
83.footer-nl-label { font-weight: 600; color: var(--ink); }
84.footer-nl-form { display: flex; gap: .5rem; flex-wrap: wrap; }
85.footer-nl-form input {
86 padding: .5rem .7rem; border-radius: 8px; border: 1px solid var(--rule);
87 background: var(--paper); color: var(--ink); font-size: .9rem; min-width: 200px;
88}
89.footer-nl-form button {
90 padding: .5rem 1rem; border-radius: 8px; border: none; cursor: pointer;
91 background: var(--accent); color: #fff; font-weight: 600; font-size: .9rem;
92}
93@media (max-width: 600px) { .footer-nl { flex-direction: column; } .footer-nl-form input { min-width: 0; flex: 1; } }
94
95.site-footer-inner {
96 max-width: 800px;
97 margin: 0 auto;
98 padding: 0 1rem;
99 display: flex;
100 justify-content: space-between;
101 align-items: center;
102 flex-wrap: wrap;
103 gap: 1rem;
104}
105.footer-meta {
106 font-size: 0.8rem; opacity: 0.7;
107 display: inline-flex; align-items: center; gap: 0.75rem;
108}
109.footer-lang {
110 background: transparent; color: var(--ink); opacity: 1;
111 border: 1px solid var(--rule); border-radius: 999px;
112 padding: 0.3rem 0.5rem; font-size: 0.78rem; cursor: pointer;
113 font-family: inherit;
114}
115.footer-lang:hover { border-color: var(--accent); }
116/* Make open dropdown options readable (otherwise white-on-white in dark mode). */
117.footer-lang option { background: var(--paper); color: var(--ink); }
118/* On desktop the language picker lives in the header nav → hide the footer version. */
119@media (min-width: 768px) { .footer-lang { display: none; } }
120.footer-version { color: inherit; text-decoration: none; }
121.footer-version:hover { color: var(--accent); text-decoration: underline; }
122.pcms-main { min-height: 50vh; }
123
124/* Mobile theme toggle in footer. Pill-style button matching topnav nav-btn.
125 Hidden on desktop; the topnav already has a toggle there. */
126.footer-theme-toggle {
127 display: none;
128 align-items: center; justify-content: center;
129 width: 36px; height: 36px;
130 padding: 0; margin: 0;
131 background: transparent; color: var(--ink);
132 border: 1px solid var(--rule); border-radius: 999px;
133 cursor: pointer;
134 transition: background-color 120ms, border-color 120ms;
135 -webkit-tap-highlight-color: transparent;
136 opacity: 1; /* override .footer-meta dim */
137}
138.footer-theme-toggle:hover { background: var(--paper-2); border-color: var(--accent); }
139.footer-theme-toggle:active { transform: scale(0.96); }
140.footer-theme-toggle svg { width: 18px; height: 18px; }
141
142@media (max-width: 768px) {
143 .footer-theme-toggle { display: inline-flex; }
144}
145
146/* Compact install button inside the footer row (centre) instead of a large standalone
147 CTA on its own line — saves vertical space. */
148.install-app-btn--footer {
149 flex: 0 0 auto;
150 width: auto !important; /* override the mobile .install-app-btn width:100% */
151 padding: 0.42rem 0.95rem;
152 font-size: 0.82rem;
153 gap: 0.4rem;
154}
155.install-app-btn--footer svg { width: 15px; height: 15px; }
156/* On narrow screens: let the three items centre neatly when they wrap. */
157@media (max-width: 600px) {
158 .site-footer-inner { justify-content: center; text-align: center; }
159}
160</style>
Note: See TracBrowser for help on using the repository browser.