world-game/src/views/Article.svelte

290 lines
7.3 KiB
Svelte
Raw Normal View History

<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 {
2026-03-21 13:38:42 +01:00
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);
2026-03-21 13:38:42 +01:00
line-height: 1.4;
}
.article-intro {
2026-03-21 13:38:42 +01:00
font-family: "Danzza Light", sans-serif;
font-size: var(--font-size-paragraph-mobile);
2026-03-21 13:38:42 +01:00
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>