252 lines
14 KiB
JavaScript
252 lines
14 KiB
JavaScript
/*! WEBklar Neuigkeiten-Pinnwand: Einbau-Skript fuer Kundenwebsites.
|
|
* <div data-webklar-news="SITEKEY" data-api="https://project.webklar.com">statischer Leerzustand</div>
|
|
* Keine Abhaengigkeiten, keine Cookies, kein Storage. Kundentext nur ueber textContent. */
|
|
(function () {
|
|
'use strict'
|
|
var SELF = document.currentScript
|
|
var STYLE_ID = 'wkn-style'
|
|
var MONTHS = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
|
|
var ROT = [-1.6, 1.2, -0.8, 1.8, -1.3, 0.9]
|
|
var PAPERS = { sun: 1, sky: 1, coral: 1, white: 1 }
|
|
|
|
var CSS = [
|
|
'.wkn-root{container:wkn-root/inline-size;display:block}',
|
|
':where(.wkn-root){--wkn-board:#C08A52;--wkn-frame:#A8733F;--wkn-font-display:"Fredoka",system-ui,sans-serif;--wkn-font-body:"Nunito",system-ui,sans-serif;--wkn-paper-sun:#FFF6D6;--wkn-paper-sky:#DDEEFB;--wkn-paper-coral:#FFE1DA;--wkn-paper-white:#fff;--wkn-ink:#232A33;--wkn-ink-soft:#5B6673;font-family:var(--wkn-font-body)}',
|
|
'.wkn-root *,.wkn-root *::before,.wkn-root *::after{box-sizing:border-box}',
|
|
'.wkn-board{position:relative;isolation:isolate;overflow:hidden;border-radius:28px;padding:40px 32px 48px;background-color:var(--wkn-board);',
|
|
'background-image:url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%27260%27 height=%27260%27%3E%3Cfilter id=%27a%27%3E%3CfeTurbulence type=%27fractalNoise%27 baseFrequency=%271.1%27 numOctaves=%272%27 seed=%273%27/%3E%3CfeColorMatrix values=%270 0 0 0 .29 0 0 0 0 .16 0 0 0 0 .06 0 0 0 -2.4 1.25%27/%3E%3C/filter%3E%3Crect width=%27100%25%27 height=%27100%25%27 filter=%27url(%23a)%27/%3E%3C/svg%3E"),url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%27220%27 height=%27220%27%3E%3Cfilter id=%27b%27%3E%3CfeTurbulence type=%27fractalNoise%27 baseFrequency=%27.85%27 numOctaves=%272%27 seed=%2711%27/%3E%3CfeColorMatrix values=%270 0 0 0 .95 0 0 0 0 .8 0 0 0 0 .58 0 0 0 2.6 -1.55%27/%3E%3C/filter%3E%3Crect width=%27100%25%27 height=%27100%25%27 filter=%27url(%23b)%27/%3E%3C/svg%3E"),url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%27600%27 height=%27600%27%3E%3Cfilter id=%27c%27%3E%3CfeTurbulence type=%27fractalNoise%27 baseFrequency=%27.012%27 numOctaves=%273%27 seed=%277%27/%3E%3CfeColorMatrix values=%270 0 0 0 .45 0 0 0 0 .27 0 0 0 0 .12 0 0 0 1.4 -.45%27/%3E%3C/filter%3E%3Crect width=%27100%25%27 height=%27100%25%27 filter=%27url(%23c)%27/%3E%3C/svg%3E");',
|
|
'border:12px solid var(--wkn-frame);box-shadow:inset 0 0 0 1px rgba(80,45,15,.35),inset 0 6px 14px rgba(60,32,10,.35),0 0 0 1px rgba(90,55,25,.5),0 12px 30px rgba(35,52,74,.18)}',
|
|
'.wkn-grid{display:grid;gap:32px 24px;grid-template-columns:repeat(3,minmax(0,1fr));align-items:start}',
|
|
'.wkn-grid>:first-child{grid-column:span 2}',
|
|
'.wkn-pin{position:relative;transform:rotate(var(--wkn-r,0deg));transition:transform .25s ease;color:var(--wkn-ink)}',
|
|
'.wkn-pin:hover{transform:rotate(0deg) translateY(-4px)}',
|
|
'.wkn-pin::before{content:"";position:absolute;top:-12px;left:50%;width:84px;height:24px;z-index:2;transform:translateX(-50%) rotate(calc(var(--wkn-r,0deg) * -1.5 - 2deg));background:rgba(255,255,255,.62);box-shadow:0 1px 2px rgba(35,52,74,.12);border-left:2px dotted rgba(35,52,74,.06);border-right:2px dotted rgba(35,52,74,.06)}',
|
|
'.wkn-date{white-space:nowrap;font:700 12px/1 var(--wkn-font-body);letter-spacing:.08em;text-transform:uppercase;color:var(--wkn-ink-soft)}',
|
|
'.wkn-sig{font:600 15px/1 var(--wkn-font-display);color:var(--wkn-ink-soft);margin-top:14px}',
|
|
'.wkn-new{position:absolute;top:-14px;right:-10px;z-index:3;transform:rotate(8deg);background:#E4573D;color:#fff;font:700 12px/1 var(--wkn-font-body);letter-spacing:.04em;padding:7px 12px;border-radius:999px;box-shadow:0 3px 8px rgba(35,52,74,.25)}',
|
|
'.wkn-note{padding:28px 24px 22px;border-radius:6px 6px 18px 6px;box-shadow:0 8px 18px rgba(35,52,74,.14);background:var(--wkn-paper,#fff)}',
|
|
'.wkn-note h3{font:600 20px/1.25 var(--wkn-font-display);color:var(--wkn-ink);margin:8px 0}',
|
|
'.wkn-note p{font:400 16px/1.5 var(--wkn-font-body);color:var(--wkn-ink);margin:0;white-space:pre-line;overflow-wrap:break-word;hyphens:auto}',
|
|
'.wkn-lead .wkn-note h3,.wkn-lead.wkn-note h3{font-size:28px}',
|
|
'.wkn-paper-sun{--wkn-paper:var(--wkn-paper-sun)}.wkn-paper-sky{--wkn-paper:var(--wkn-paper-sky)}.wkn-paper-coral{--wkn-paper:var(--wkn-paper-coral)}.wkn-paper-white{--wkn-paper:var(--wkn-paper-white)}',
|
|
'.wkn-polaroid{margin:0;background:#fff;padding:12px 12px 16px;border-radius:4px;box-shadow:0 10px 22px rgba(35,52,74,.16)}',
|
|
'.wkn-polaroid img{display:block;width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;border-radius:2px;background:#eef0f3}',
|
|
'.wkn-polaroid figcaption{display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-top:12px}',
|
|
'.wkn-polaroid figcaption b{font:600 17px/1.25 var(--wkn-font-display);color:var(--wkn-ink);overflow-wrap:break-word;hyphens:auto}',
|
|
'.wkn-combo{container:wkn-card/inline-size}',
|
|
'.wkn-combo-in{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);align-items:center}',
|
|
'.wkn-combo .wkn-polaroid{transform:rotate(-2deg)}',
|
|
'.wkn-combo .wkn-note{margin-left:-28px;transform:rotate(2.5deg);z-index:1}',
|
|
'.wkn-combo::before{left:28%}',
|
|
'.wkn-more[hidden]{display:none}',
|
|
'.wkn-more{display:flex;justify-content:center;margin-top:40px}',
|
|
'.wkn-btn{font:700 16px/1 var(--wkn-font-body);color:var(--wkn-ink);background:#fff;border:0;border-radius:999px;padding:14px 24px;cursor:pointer;box-shadow:0 3px 8px rgba(35,52,74,.25)}',
|
|
'.wkn-btn:hover{transform:translateY(-1px)}.wkn-btn:focus-visible{outline:3px solid var(--wkn-ink);outline-offset:2px}.wkn-btn[disabled]{opacity:.6;cursor:default}',
|
|
'.wkn-swipehint{display:none;width:max-content;margin:8px auto 0;justify-content:center;align-items:center;gap:6px;font:400 13px/1.2 var(--wkn-font-body);color:var(--wkn-ink);background:rgba(255,255,255,.85);padding:5px 12px;border-radius:999px}',
|
|
'.wkn-dots{display:flex;gap:6px;margin-right:8px}',
|
|
'.wkn-dots i{width:7px;height:7px;border-radius:50%;background:rgba(35,42,51,.2);transition:width .2s}',
|
|
'.wkn-dots i.wkn-on{background:var(--wkn-ink);width:18px;border-radius:999px}',
|
|
'@container wkn-root (max-width:700px){',
|
|
'.wkn-board{padding:32px 0;border-radius:20px}',
|
|
'.wkn-grid{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;gap:18px;padding:28px 20px 12px;scrollbar-width:none}',
|
|
'.wkn-grid::-webkit-scrollbar{display:none}',
|
|
'.wkn-grid>*{flex:0 0 76%;scroll-snap-align:center}',
|
|
'.wkn-grid>:first-child{grid-column:auto}',
|
|
|
|
'.wkn-lead .wkn-note h3,.wkn-lead.wkn-note h3{font-size:20px}',
|
|
'.wkn-more{margin-top:24px;padding-inline:20px}.wkn-more .wkn-btn{width:100%}',
|
|
'.wkn-swipehint{display:flex}',
|
|
'}',
|
|
'@container wkn-card (max-width:340px){.wkn-combo-in{grid-template-columns:minmax(0,1fr)}.wkn-combo .wkn-note{margin:-26px 12px 0 26px}}',
|
|
'@media (prefers-reduced-motion:reduce){.wkn-pin,.wkn-btn,.wkn-dots i{transition:none}}'
|
|
].join('\n')
|
|
|
|
function injectStyle () {
|
|
if (document.getElementById(STYLE_ID)) return
|
|
var st = document.createElement('style')
|
|
st.id = STYLE_ID
|
|
st.textContent = CSS
|
|
document.head.appendChild(st)
|
|
}
|
|
|
|
function el (tag, cls, text) {
|
|
var e = document.createElement(tag)
|
|
if (cls) e.className = cls
|
|
if (text != null) e.textContent = text
|
|
return e
|
|
}
|
|
|
|
// 'YYYY-MM-DD' (bereits Berliner Kalendertag) -> '29. Sep'
|
|
function fmtDate (d) {
|
|
var m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(d || ''))
|
|
if (!m) return ''
|
|
return parseInt(m[3], 10) + '. ' + MONTHS[parseInt(m[2], 10) - 1]
|
|
}
|
|
|
|
function absUrl (api, p) {
|
|
return typeof p === 'string' && p.charAt(0) === '/' ? api + p : ''
|
|
}
|
|
|
|
function buildNote (p, extraCls) {
|
|
var paper = PAPERS[p.paper] ? p.paper : 'sun'
|
|
var n = el('div', 'wkn-note wkn-paper-' + paper + (extraCls || ''))
|
|
n.appendChild(el('span', 'wkn-date', fmtDate(p.date)))
|
|
if (p.title) n.appendChild(el('h3', '', p.title))
|
|
n.appendChild(el('p', '', p.text || ''))
|
|
if (p.signature) n.appendChild(el('div', 'wkn-sig', p.signature))
|
|
return n
|
|
}
|
|
|
|
function buildPhoto (p, api, withCaption) {
|
|
var fig = el('figure', 'wkn-polaroid')
|
|
if (p.image) {
|
|
var img = document.createElement('img')
|
|
var iw = Number.isFinite(+p.image.w) && +p.image.w > 0 ? Math.round(+p.image.w) : 1600
|
|
var ih = Number.isFinite(+p.image.h) && +p.image.h > 0 ? Math.round(+p.image.h) : 1200
|
|
var small = absUrl(api, p.image.srcSmall)
|
|
var full = absUrl(api, p.image.src)
|
|
img.src = small || full
|
|
if (small && full) {
|
|
img.srcset = small + ' 480w, ' + full + ' ' + iw + 'w'
|
|
img.sizes = '(max-width:700px) 80vw, 480px'
|
|
}
|
|
img.width = iw
|
|
img.height = ih
|
|
img.alt = p.title || ''
|
|
img.loading = 'lazy'
|
|
img.decoding = 'async'
|
|
fig.appendChild(img)
|
|
}
|
|
if (withCaption) {
|
|
var cap = el('figcaption')
|
|
if (p.title) cap.appendChild(el('b', '', p.title))
|
|
cap.appendChild(el('span', 'wkn-date', fmtDate(p.date)))
|
|
fig.appendChild(cap)
|
|
}
|
|
return fig
|
|
}
|
|
|
|
function buildPin (p, index, isFirst, api, signature) {
|
|
var cls = 'wkn-pin' + (isFirst ? ' wkn-lead' : '')
|
|
var r = ROT[index % ROT.length]
|
|
var item
|
|
p = { id: p.id, type: p.type, title: p.title, text: p.text, paper: p.paper, date: p.date, image: p.image, signature: signature }
|
|
if (p.type === 'photo' && p.image) {
|
|
item = buildPhoto(p, api, true)
|
|
item.className += ' ' + cls
|
|
item.style.setProperty('--wkn-r', r + 'deg')
|
|
} else if (p.type === 'combo' && p.image) {
|
|
item = el('article', cls + ' wkn-combo')
|
|
item.style.setProperty('--wkn-r', '0deg')
|
|
var inner = el('div', 'wkn-combo-in')
|
|
inner.appendChild(buildPhoto(p, api, false))
|
|
inner.appendChild(buildNote(p))
|
|
item.appendChild(inner)
|
|
} else {
|
|
item = el('article', cls)
|
|
item.style.setProperty('--wkn-r', r + 'deg')
|
|
var note = buildNote(p)
|
|
// Zettel-Klassen direkt auf die Pin-Huelle, damit Polster/Schatten nur einmal greifen
|
|
item.className += ' ' + note.className
|
|
while (note.firstChild) item.appendChild(note.firstChild)
|
|
}
|
|
if (isFirst) item.appendChild(el('span', 'wkn-new', 'Neu'))
|
|
return item
|
|
}
|
|
|
|
function init (root) {
|
|
var key = root.getAttribute('data-webklar-news')
|
|
if (!key || root.getAttribute('data-wkn-init')) return
|
|
root.setAttribute('data-wkn-init', '1')
|
|
var apiAttr = root.getAttribute('data-api')
|
|
var api = String(apiAttr || '').replace(/\/+$/, '')
|
|
if (!api && SELF && SELF.src) api = new URL(SELF.src).origin
|
|
var signature = root.getAttribute('data-signature') || ''
|
|
var state = { page: 1, shown: 0, ui: null, loading: false }
|
|
|
|
function fetchPage (page) {
|
|
return fetch(api + '/api/public/news/' + encodeURIComponent(key) + '?page=' + page, { headers: { Accept: 'application/json' } })
|
|
.then(function (r) { if (!r.ok) throw new Error('http ' + r.status); return r.json() })
|
|
.then(function (d) {
|
|
if (!d || !Array.isArray(d.posts)) throw new Error('format')
|
|
return d
|
|
})
|
|
}
|
|
|
|
function updateDots () {
|
|
var ui = state.ui
|
|
if (!ui) return
|
|
var kids = ui.grid.children
|
|
var center = ui.grid.scrollLeft + ui.grid.clientWidth / 2
|
|
var best = 0, bestDist = Infinity
|
|
for (var i = 0; i < kids.length; i++) {
|
|
var c = kids[i].offsetLeft + kids[i].offsetWidth / 2
|
|
if (Math.abs(c - center) < bestDist) { bestDist = Math.abs(c - center); best = i }
|
|
}
|
|
for (var j = 0; j < ui.dots.children.length; j++) ui.dots.children[j].className = j === best ? 'wkn-on' : ''
|
|
}
|
|
|
|
function append (posts) {
|
|
var ui = state.ui
|
|
posts.forEach(function (p) {
|
|
var idx = state.shown
|
|
ui.grid.appendChild(buildPin(p, idx, idx === 0, api, signature))
|
|
ui.dots.appendChild(document.createElement('i'))
|
|
state.shown++
|
|
})
|
|
updateDots()
|
|
}
|
|
|
|
function setMore (hasMore) {
|
|
var ui = state.ui
|
|
ui.more.hidden = !hasMore
|
|
ui.btn.disabled = false
|
|
}
|
|
|
|
function build (d) {
|
|
root.classList.add('wkn-root')
|
|
root.textContent = ''
|
|
var board = el('div', 'wkn-board')
|
|
var grid = el('div', 'wkn-grid')
|
|
var hint = el('div', 'wkn-swipehint')
|
|
var dots = el('span', 'wkn-dots')
|
|
hint.appendChild(dots)
|
|
hint.appendChild(document.createTextNode('wischen für mehr'))
|
|
var more = el('div', 'wkn-more')
|
|
var btn = el('button', 'wkn-btn', 'Ältere Neuigkeiten ansehen')
|
|
btn.type = 'button'
|
|
more.appendChild(btn)
|
|
board.appendChild(grid)
|
|
board.appendChild(hint)
|
|
board.appendChild(more)
|
|
root.appendChild(board)
|
|
state.ui = { grid: grid, dots: dots, more: more, btn: btn }
|
|
grid.addEventListener('scroll', updateDots, { passive: true })
|
|
btn.addEventListener('click', function () {
|
|
if (state.loading) return
|
|
state.loading = true
|
|
btn.disabled = true
|
|
fetchPage(state.page + 1).then(function (nd) {
|
|
state.page++
|
|
append(nd.posts)
|
|
setMore(!!nd.hasMore && nd.posts.length > 0)
|
|
}).catch(function () { btn.disabled = false }).then(function () { state.loading = false })
|
|
})
|
|
append(d.posts)
|
|
setMore(!!d.hasMore)
|
|
}
|
|
|
|
injectStyle()
|
|
fetchPage(1).then(function (d) {
|
|
if (d.posts.length > 0) build(d)
|
|
}).catch(function () { /* Fehler: statischer Leerzustand der Seite bleibt */ })
|
|
}
|
|
|
|
function run () {
|
|
var nodes = document.querySelectorAll('[data-webklar-news]')
|
|
for (var i = 0; i < nodes.length; i++) init(nodes[i])
|
|
}
|
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run)
|
|
else run()
|
|
})()
|