All checks were successful
Deploy / Deploy to Production (push) Successful in 23s
- Fix scroll listener (cleanup, local querySelector, scrollHeight calc) - Fix media query syntax in variables.css (missing space in `and (`) - Use transform: translateY instead of bottom for GPU-accelerated transition - Throttle scroll handler with requestAnimationFrame - Move Footer to App.svelte (global), remove per-view imports refs #51 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
289 lines
7.3 KiB
Svelte
289 lines
7.3 KiB
Svelte
<script>
|
|
/**
|
|
* Vue article — sous-composant de Blog.svelte.
|
|
* Reçoit les données article via props, pas via le slide system.
|
|
*/
|
|
|
|
import { t } from '@i18n'
|
|
import { onMount } from 'svelte'
|
|
import WhitePaperDialog from '@components/WhitePaperDialog.svelte'
|
|
import ShareButtons from '@components/blocks/ShareButtons.svelte'
|
|
import ArticleRelated from '@components/blocks/ArticleRelated.svelte'
|
|
|
|
let { data, onBack } = $props()
|
|
|
|
let activeWhitePaperUri = $state(null)
|
|
|
|
let copySuccess = $state(false)
|
|
let copyTimer = null
|
|
|
|
// Setup click-to-play iframes après le rendu du body HTML
|
|
$effect(() => {
|
|
if (!data.body) return
|
|
|
|
const timer = setTimeout(() => {
|
|
// Boutons livre-blanc → ouvre la dialog
|
|
document.querySelectorAll('.wp-block__btn').forEach(btn => {
|
|
if (btn.dataset.initialized) return
|
|
btn.dataset.initialized = 'true'
|
|
btn.addEventListener('click', () => {
|
|
activeWhitePaperUri = btn.dataset.uri
|
|
})
|
|
})
|
|
|
|
document.querySelectorAll('.iframe-game-container').forEach(container => {
|
|
if (container.dataset.initialized) return
|
|
container.dataset.initialized = 'true'
|
|
|
|
const overlay = container.querySelector('.iframe-click-overlay')
|
|
const iframe = container.querySelector('iframe')
|
|
const deactivateBtn = container.querySelector('.iframe-deactivate-btn')
|
|
|
|
overlay?.addEventListener('click', () => {
|
|
if (overlay.getAttribute('data-state') === 'ended') return
|
|
overlay.style.display = 'none'
|
|
iframe.style.pointerEvents = 'auto'
|
|
container.classList.add('game-active')
|
|
overlay.setAttribute('data-state', 'played')
|
|
container.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
})
|
|
|
|
deactivateBtn?.addEventListener('click', (e) => {
|
|
e.stopPropagation()
|
|
overlay.style.display = 'flex'
|
|
overlay.setAttribute('data-state', 'initial')
|
|
iframe.style.pointerEvents = 'none'
|
|
container.classList.remove('game-active')
|
|
})
|
|
})
|
|
}, 300)
|
|
|
|
return () => clearTimeout(timer)
|
|
})
|
|
|
|
// Écoute les messages postMessage depuis l'iframe du jeu
|
|
onMount(() => {
|
|
const handleIframeMessage = (event) => {
|
|
if (!event.origin.includes('impact.games')) return
|
|
if (event.data.type !== 'GameReleaseFocus') return
|
|
|
|
document.querySelectorAll('.iframe-click-overlay').forEach(overlay => {
|
|
if (overlay.getAttribute('data-state') === 'played') {
|
|
overlay.setAttribute('data-state', 'ended')
|
|
overlay.innerHTML = '<div class="overlay-content"></div>'
|
|
overlay.style.cursor = 'default'
|
|
overlay.style.pointerEvents = 'none'
|
|
}
|
|
overlay.style.display = 'flex'
|
|
})
|
|
|
|
document.querySelectorAll('.iframe-game-container').forEach(container => {
|
|
container.querySelector('iframe').style.pointerEvents = 'none'
|
|
container.classList.remove('game-active')
|
|
})
|
|
}
|
|
|
|
window.addEventListener('message', handleIframeMessage)
|
|
return () => window.removeEventListener('message', handleIframeMessage)
|
|
})
|
|
|
|
function copyLink() {
|
|
navigator.clipboard.writeText(window.location.href)
|
|
copySuccess = true
|
|
clearTimeout(copyTimer)
|
|
copyTimer = setTimeout(() => { copySuccess = false }, 2000)
|
|
}
|
|
|
|
const shareUrl = $derived(encodeURIComponent(window.location.href))
|
|
</script>
|
|
|
|
<div class="article-wrapper">
|
|
<article class="article">
|
|
|
|
<!-- Date + share buttons -->
|
|
<div class="article-topbar">
|
|
<time class="article-date">{t('published_on')} {data.published}</time>
|
|
<div class="article-share">
|
|
{#if copySuccess}
|
|
<span class="copy-toast">{t('link_copied')}</span>
|
|
{/if}
|
|
<ShareButtons {shareUrl} {copyLink} />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Titre -->
|
|
<h1 class="article-title font-face-terminal">{data.title}</h1>
|
|
|
|
<!-- Intro -->
|
|
{#if data.intro}
|
|
<div class="article-intro">{@html data.intro}</div>
|
|
{/if}
|
|
|
|
<!-- Cover -->
|
|
{#if data.cover}
|
|
<div class="article-cover">
|
|
<img src={data.cover} alt={data.title} />
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Body (Kirby blocks → HTML) -->
|
|
{#if data.body}
|
|
<div class="article-body">{@html data.body}</div>
|
|
{/if}
|
|
|
|
<!-- Share section (bas d'article) -->
|
|
<div class="article-share-section">
|
|
<hr class="share-divider" />
|
|
<p class="share-label">{t('share_article')}</p>
|
|
<ShareButtons {shareUrl} {copyLink} centered />
|
|
</div>
|
|
|
|
<!-- Articles recommandés -->
|
|
{#if data.related?.length}
|
|
<ArticleRelated related={data.related} />
|
|
{/if}
|
|
</article>
|
|
</div>
|
|
|
|
<WhitePaperDialog uri={activeWhitePaperUri} onClose={() => activeWhitePaperUri = null} />
|
|
|
|
<style>
|
|
.article-wrapper {
|
|
grid-area: 6 / 1 / span 15 / span 20;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.article {
|
|
height: fit-content;
|
|
padding: 0 20%;
|
|
}
|
|
|
|
/* --- Topbar --- */
|
|
.article-topbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.article-date {
|
|
color: rgba(255, 255, 255, 0.7);
|
|
font-size: var(--font-size-paragraph);
|
|
}
|
|
|
|
/* --- Share (topbar) --- */
|
|
.article-share {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.copy-toast {
|
|
position: absolute;
|
|
right: 0;
|
|
top: -36px;
|
|
background: var(--color-primary);
|
|
color: #1e1938;
|
|
padding: 4px 12px;
|
|
border-radius: 4px;
|
|
font-size: 13px;
|
|
font-weight: bold;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* --- Share section (bas d'article) --- */
|
|
.article-share-section {
|
|
margin: auto;
|
|
text-align: center;
|
|
width: fit-content;
|
|
margin-top: 3rem;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.share-divider {
|
|
border: none;
|
|
border-top: 1px solid white;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
|
|
.share-label {
|
|
font-family: "Danzza Bold", sans-serif;
|
|
font-size: 16px;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
|
|
/* --- Titre --- */
|
|
.article-title {
|
|
font-size: var(--font-size-title-section);
|
|
font-weight: 700;
|
|
font-family: "Danzza", sans-serif;
|
|
text-align: center;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
/* --- Intro --- */
|
|
.article-intro {
|
|
text-align: center;
|
|
font-size: var(--font-size-paragraph);
|
|
line-height: 1.5;
|
|
color: hsla(0,0%,100%,.9);
|
|
margin-bottom: 2.5rem;
|
|
}
|
|
|
|
/* --- Cover --- */
|
|
.article-cover {
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.article-cover img {
|
|
width: 100%;
|
|
height: auto;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
/* --- Body (rich text from Kirby blocks) --- */
|
|
.article-body {
|
|
line-height: 1.8;
|
|
text-align: left;
|
|
margin-bottom: 1.25rem;
|
|
padding: 0 15%;
|
|
}
|
|
|
|
/* --- Mobile --- */
|
|
@media (max-width: 700px) {
|
|
.article {
|
|
padding:0 0 4rem;
|
|
}
|
|
|
|
.article-date {
|
|
font-family: "Danzza Light", sans-serif;
|
|
opacity: .7;
|
|
font-size: var(--font-size-paragraph-mobile);
|
|
text-wrap: nowrap;
|
|
}
|
|
|
|
.article-title {
|
|
font-size: var(--font-size-title-section-mobile);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.article-intro {
|
|
font-family: "Danzza Light", sans-serif;
|
|
font-size: var(--font-size-paragraph-mobile);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.article-body {
|
|
padding: 0;
|
|
}
|
|
}
|
|
|
|
/* --- Tablet --- */
|
|
@media (min-width: 701px) and (max-width: 912px) {
|
|
.article-title {
|
|
font-size: var(--font-size-title-main-tablet);
|
|
}
|
|
}
|
|
</style>
|