| 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. */
|
|---|
| 49 | html { 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 */
|
|---|
| 66 | body.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>
|
|---|