{"id":281,"date":"2025-09-10T01:21:23","date_gmt":"2025-09-09T23:21:23","guid":{"rendered":"https:\/\/www.pjevents.ch\/?page_id=281"},"modified":"2025-11-05T12:24:07","modified_gmt":"2025-11-05T11:24:07","slug":"gallerie","status":"publish","type":"page","link":"https:\/\/www.pjevents.ch\/en\/gallerie\/","title":{"rendered":"Gallery"},"content":{"rendered":"\n\n\n\n<section class=\"pj-gallery\" itemscope itemtype=\"https:\/\/schema.org\/CollectionPage\" aria-labelledby=\"gal-title\">\n  <header class=\"gal-header\">\n    <h1 id=\"gal-title\">Video Gallery<\/h1>\n    <p>Here you\u2019ll find <strong>event clips<\/strong>. Fixture videos are available on the respective product pages.<\/p>\n  <\/header>\n\n  <section id=\"gal-events\" class=\"gal-panel\" role=\"region\" aria-labelledby=\"gal-title\">\n    <div class=\"gal-grid\">\n\n      <article class=\"vid-card\" id=\"event-eglisau1\" data-name=\"Eglisau 1\"\n               data-src=\"http:\/\/www.pjevents.ch\/wp-content\/uploads\/2025\/09\/Eglisau-1.mp4\">\n        <button class=\"vid-thumb\" type=\"button\" aria-label=\"Video abspielen: Eglisau 1\">\n          <span class=\"play\">\u25b6<\/span>\n        <\/button>\n        <footer class=\"vid-caption\">\n          <strong>Eglisau 1<\/strong>\n        <\/footer>\n      <\/article>\n\n      <article class=\"vid-card\" id=\"event-eglisau2\" data-name=\"Eglisau 2\"\n               data-src=\"http:\/\/www.pjevents.ch\/wp-content\/uploads\/2025\/09\/Grono-Clean.mp4\">\n        <button class=\"vid-thumb\" type=\"button\" aria-label=\"Video abspielen: Grono 2.0\">\n          <span class=\"play\">\u25b6<\/span>\n        <\/button>\n        <footer class=\"vid-caption\">\n          <strong>Grono 2.0<\/strong>\n        <\/footer>\n      <\/article>\n\n      <article class=\"vid-card\" id=\"event-eglisau3\" data-name=\"Eglisau 3\"\n               data-src=\"http:\/\/www.pjevents.ch\/wp-content\/uploads\/2025\/09\/3Rave-Front.mp4\">\n        <button class=\"vid-thumb\" type=\"button\" aria-label=\"Video abspielen: Dave Rave 2.4\">\n          <span class=\"play\">\u25b6<\/span>\n        <\/button>\n        <footer class=\"vid-caption\">\n          <strong>Dave Rave 2.4<\/strong>\n        <\/footer>\n      <\/article>\n\n      <article class=\"vid-card\" id=\"event-eglisau4\" data-name=\"Eglisau 4\"\n               data-src=\"http:\/\/www.pjevents.ch\/wp-content\/uploads\/2025\/09\/3Rave.mp4\">\n        <button class=\"vid-thumb\" type=\"button\" aria-label=\"Video abspielen: Dave Rave 2.4\">\n          <span class=\"play\">\u25b6<\/span>\n        <\/button>\n        <footer class=\"vid-caption\">\n          <strong>Dave Rave 2.4<\/strong>\n        <\/footer>\n      <\/article>\n\n      <article class=\"vid-card\" id=\"event-eglisau5\" data-name=\"Eglisau 5\"\n               data-src=\"http:\/\/www.pjevents.ch\/wp-content\/uploads\/2025\/11\/IMG_4484.mov\">\n        <button class=\"vid-thumb\" type=\"button\" aria-label=\"Video abspielen: Dave Rave 2.9\">\n          <span class=\"play\">\u25b6<\/span>\n        <\/button>\n        <footer class=\"vid-caption\">\n          <strong>Dave Rave 2.9<\/strong>\n        <\/footer>\n      <\/article>\n\n    <\/div>\n  <\/section>\n<\/section>\n\n\n<style>\n  .pj-gallery { max-width: 1200px; margin: 0 auto; padding: 20px; }\n  .gal-header h1 { margin: 0 0 .4em; }\n  .gal-header p { margin: 0 0 1rem; }\n\n  .gal-grid { display: grid; gap: 14px; grid-template-columns: repeat(2, 1fr); }\n  @media (min-width: 780px){ .gal-grid { grid-template-columns: repeat(3, 1fr); } }\n  @media (min-width: 1080px){ .gal-grid { grid-template-columns: repeat(4, 1fr); } }\n\n  .vid-card { border: 1px solid currentColor; border-radius: 12px; overflow: hidden; }\n  @supports (color: color-mix(in srgb, black, white)) {\n    .vid-card { border-color: color-mix(in srgb, currentColor 28%, transparent); }\n  }\n\n  .vid-thumb { position: relative; display: block; width: 100%; aspect-ratio: 16\/9; padding: 0; border: 0; background: transparent; cursor: pointer; }\n  .vid-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; filter: brightness(.9); transition: transform .25s ease, filter .25s ease; }\n  .vid-thumb:hover img { transform: scale(1.02); filter: brightness(1); }\n  .vid-thumb .play { position: absolute; inset: 0; display: grid; place-items: center; font-size: 2.2rem; line-height: 1; color: white; text-shadow: 0 2px 8px rgba(0,0,0,.5); }\n\n  .vid-thumb:not(:has(img)) {\n    background: radial-gradient(60% 60% at 50% 45%, rgba(0,0,0,.35), rgba(0,0,0,.7)),\n                linear-gradient(135deg, #1a1a1a, #2a2a2a);\n  }\n\n  .vid-caption { display: flex; align-items: baseline; justify-content: flex-start; gap: 8px; padding: 10px 12px; }\n  .vid-caption strong { font-weight: 700; }\n<\/style>\n\n<script>\n(function(){\n  const root = document.querySelector('.pj-gallery');\n  if(!root) return;\n\n  const THUMB_TIME_SEC = 3;          \/\/ Sekunde, von der der Frame genommen wird\n  const THUMB_QUALITY  = 0.8;        \/\/ 0..1 f\u00fcr JPEG-Qualit\u00e4t\n  const CACHE_PREFIX   = 'pjthumb:';\n\n  const getAllVideos = () => Array.from(root.querySelectorAll('video'));\n  const pauseAll = (except=null) => {\n    getAllVideos().forEach(v => { if(v !== except){ v.pause(); } });\n  };\n\n  \/\/ === Thumbnail-Cache ===\n  function cacheKey(src){ return CACHE_PREFIX + src; }\n  function getCachedThumb(src){\n    try { return localStorage.getItem(cacheKey(src)) || null; } catch(e){ return null; }\n  }\n  function setCachedThumb(src, dataURL){\n    try { localStorage.setItem(cacheKey(src), dataURL); } catch(e){}\n  }\n\n  \/\/ === Thumbnail erzeugen (Canvas aus Videoframe) ===\n  async function generateThumbFromVideo(src, timeSec=THUMB_TIME_SEC){\n    return new Promise((resolve, reject) => {\n      const v = document.createElement('video');\n      v.crossOrigin = 'anonymous';           \/\/ ben\u00f6tigt CORS \u201eanonymous\u201c, wenn Domain\/CDN anders ist\n      v.preload = 'metadata';\n      v.muted = true;\n      v.src = src;\n\n      const onFail = (err) => { cleanup(); reject(err || new Error('thumb-fail')); };\n      const cleanup = () => {\n        v.removeEventListener('loadedmetadata', onLoadedMeta);\n        v.removeEventListener('seeked', onSeeked);\n        v.removeEventListener('error', onFail);\n      };\n\n      const onLoadedMeta = () => {\n        \/\/ Sicherheitsnetz: wenn Video k\u00fcrzer ist, nimm letzte Sekunde\n        const t = Math.min(Math.max(0.5, timeSec), (v.duration || timeSec) - 0.2);\n        \/\/ Manche Browser brauchen kleinen Timeout vor dem Seek\n        setTimeout(() => { v.currentTime = isFinite(t) ? t : timeSec; }, 50);\n      };\n\n      const onSeeked = () => {\n        try{\n          const w = v.videoWidth || 1280;\n          const h = v.videoHeight || 720;\n          const canvas = document.createElement('canvas');\n          canvas.width = w;\n          canvas.height = h;\n          const ctx = canvas.getContext('2d');\n          ctx.drawImage(v, 0, 0, w, h);\n          \/\/ JPEG ist kompakter als PNG, reicht i. d. R. v\u00f6llig\n          const dataURL = canvas.toDataURL('image\/jpeg', THUMB_QUALITY);\n          cleanup();\n          resolve(dataURL);\n        }catch(e){ onFail(e); }\n      };\n\n      v.addEventListener('loadedmetadata', onLoadedMeta, { once:true });\n      v.addEventListener('seeked', onSeeked, { once:true });\n      v.addEventListener('error', onFail, { once:true });\n      \/\/ iOS Safari l\u00e4dt metadata teils erst nach Append \u2192 als Offscreen-Element anh\u00e4ngen\n      v.style.position = 'fixed'; v.style.left = '-99999px'; v.style.top = '-99999px';\n      document.body.appendChild(v);\n      \/\/ aufr\u00e4umen nach Resolve\/Reject\n      const stopObs = (resOrRej) => resOrRej.finally(()=>{ try{ v.remove(); }catch(_){} });\n      stopObs(Promise.resolve()); \/\/ no-op, nur zur Klarheit\n    });\n  }\n\n  \/\/ === Thumbnail in die Karte einsetzen ===\n  async function ensureThumb(card){\n    const btn = card.querySelector('.vid-thumb');\n    if(!btn || btn.querySelector('img')) return;\n\n    const src = card.dataset.src;\n    if(!src) return;\n\n    \/\/ Cache zuerst\n    const cached = getCachedThumb(src);\n    if(cached){\n      insertThumb(btn, cached, card.dataset.name);\n      return;\n    }\n\n    try{\n      const dataURL = await generateThumbFromVideo(src, THUMB_TIME_SEC);\n      if(dataURL){\n        setCachedThumb(src, dataURL);\n        insertThumb(btn, dataURL, card.dataset.name);\n      }\n    }catch(e){\n      \/\/ Fallback: nichts tun (dein CSS zeigt dann den Gradient-Placeholder)\n    }\n  }\n\n  function insertThumb(btn, dataURL, name){\n    const img = document.createElement('img');\n    img.src = dataURL;\n    img.alt = (name || 'Video') + ' \u2013 Vorschaubild';\n    img.loading = 'lazy';\n    img.decoding = 'async';\n    btn.insertBefore(img, btn.firstChild);\n  }\n\n  \/\/ === Video abspielen (ersetzt Button \u2192 Video); setzt poster wenn vorhanden\/erzeugt ===\n  function makeSelfHostedVideo(card){\n    const src = card.dataset.src;\n    if(!src) return;\n\n    pauseAll();\n\n    const video = document.createElement('video');\n    video.controls = true;\n    video.autoplay = true;\n    video.playsInline = true;\n    video.preload = 'none';\n    video.style.width = '100%';\n    video.style.height = '100%';\n\n    \/\/ Poster: benutze existierendes <img> oder Cache\n    const btn = card.querySelector('.vid-thumb');\n    const img = btn ? btn.querySelector('img') : null;\n    if(img && img.src) {\n      video.poster = img.src;\n    } else {\n      const cached = getCachedThumb(src);\n      if(cached) video.poster = cached;\n    }\n\n    const source = document.createElement('source');\n    source.src = src;\n    const ext = src.split('?')[0].split('#')[0].split('.').pop().toLowerCase();\n    source.type = (ext === 'webm') ? 'video\/webm' : 'video\/mp4';\n    video.appendChild(source);\n    video.appendChild(document.createTextNode('Dein Browser unterst\u00fctzt das Video-Element nicht.'));\n\n    if(!btn) return;\n    const wrap = document.createElement('div');\n    wrap.style.position = 'relative';\n    wrap.style.width = '100%';\n    wrap.style.aspectRatio = '16\/9';\n    wrap.appendChild(video);\n    btn.replaceWith(wrap);\n\n    video.play().catch(()=>{});\n    video.addEventListener('play', ()=> pauseAll(video));\n  }\n\n  \/\/ Thumbs beim Sichtbarwerden generieren (IntersectionObserver \u2192 performant)\n  const cards = Array.from(root.querySelectorAll('.vid-card'));\n  if('IntersectionObserver' in window){\n    const io = new IntersectionObserver((entries) => {\n      entries.forEach(e => {\n        if(e.isIntersecting) {\n          ensureThumb(e.target);\n          io.unobserve(e.target);\n        }\n      });\n    }, { rootMargin: '200px 0px' });\n    cards.forEach(c => io.observe(c));\n  } else {\n    \/\/ Fallback ohne IO\n    cards.forEach(c => ensureThumb(c));\n  }\n\n  \/\/ Klick zum Abspielen\n  root.addEventListener('click', (e)=>{\n    const btn = e.target.closest('.vid-thumb');\n    if(!btn) return;\n    const card = btn.closest('.vid-card');\n    if(card) makeSelfHostedVideo(card);\n  });\n})();\n<\/script>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-281","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.pjevents.ch\/en\/wp-json\/wp\/v2\/pages\/281","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pjevents.ch\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.pjevents.ch\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.pjevents.ch\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pjevents.ch\/en\/wp-json\/wp\/v2\/comments?post=281"}],"version-history":[{"count":13,"href":"https:\/\/www.pjevents.ch\/en\/wp-json\/wp\/v2\/pages\/281\/revisions"}],"predecessor-version":[{"id":1751,"href":"https:\/\/www.pjevents.ch\/en\/wp-json\/wp\/v2\/pages\/281\/revisions\/1751"}],"wp:attachment":[{"href":"https:\/\/www.pjevents.ch\/en\/wp-json\/wp\/v2\/media?parent=281"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}