Complète le portage des fonctionnalités du README

- Bloc « À propos » configurable (titre, 2 paragraphes, image légendée)
  + aside hashtags, masqué tant que non défini (ex-MOVEMENT_*)
- JSON-LD CollectionPage (catégories) et BreadcrumbList (vidéo, catégorie)
- Indicateur visuel de perte de connexion (online/offline)
- Verrou maintenance par compte à rebours multi-fuseaux (ex-COUNTDOWN_*) :
  build-time, titre kréyòl, redirection automatique à l'échéance
- browserconfig.xml (tuiles Windows)
This commit is contained in:
sucupira
2026-07-23 01:05:01 -04:00
parent d6efb6736f
commit 28c2334927
13 changed files with 386 additions and 33 deletions
+10 -2
View File
@@ -2,16 +2,24 @@
import { onMount } from 'svelte';
import type { Locale } from '$lib/types';
let { target, locale }: { target: string; locale: Locale } = $props();
let { target, locale, onReached = undefined }: { target: string; locale: Locale; onReached?: () => void } = $props();
let remaining = $state<{ days: number; hours: number; minutes: number; seconds: number } | null>(
null
);
let reached = false;
function compute(): typeof remaining {
const targetTime = new Date(target.replace(' ', 'T')).getTime();
const diff = targetTime - Date.now();
if (Number.isNaN(targetTime) || diff <= 0) return null;
if (Number.isNaN(targetTime)) return null;
if (diff <= 0) {
if (!reached) {
reached = true;
onReached?.();
}
return null;
}
return {
days: Math.floor(diff / 86_400_000),
hours: Math.floor((diff % 86_400_000) / 3_600_000),
+99
View File
@@ -0,0 +1,99 @@
<script lang="ts">
import CountdownClock from './CountdownClock.svelte';
import Icon from './Icon.svelte';
import type { Bundle } from '$lib/i18n';
import type { Locale } from '$lib/types';
let {
lock,
locale,
t
}: {
lock: { targetDate: string; schedule: { label: string; time: string }[] };
locale: Locale;
t: Bundle;
} = $props();
function onReached() {
window.location.assign('/');
}
</script>
<div class="flag-bar" aria-hidden="true"></div>
<main class="lock">
<Icon name="ka" class="lock-icon" />
<h1 class="lock-title">{t.countdown.title}</h1>
<p class="text-muted">{t.countdown.text}</p>
<CountdownClock target={lock.targetDate} {locale} {onReached} />
<h2 class="schedule-heading">{t.direct.inTimezones}</h2>
<ul class="schedule">
{#each lock.schedule as slot (slot.label)}
<li>
<span class="zone">{slot.label}</span>
<span>{slot.time}</span>
</li>
{/each}
</ul>
<p class="text-muted small">{t.countdown.back}</p>
</main>
<div class="flag-bar" aria-hidden="true"></div>
<style>
.lock {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: var(--space-3);
padding: var(--space-5) var(--space-3);
}
.lock :global(.lock-icon) {
width: 96px;
height: 96px;
}
.lock-title {
font-family: var(--font-display);
font-weight: 900;
text-transform: uppercase;
letter-spacing: -0.01em;
font-size: clamp(1.6rem, 5vw, 2.8rem);
}
.schedule-heading {
font-family: var(--font-display);
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--muted);
}
.schedule {
list-style: none;
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.schedule li {
display: flex;
gap: var(--space-2);
}
.zone {
font-weight: 600;
min-width: 12ch;
color: var(--accent);
text-align: right;
}
.small {
font-size: 0.85rem;
}
</style>
@@ -0,0 +1,50 @@
<script lang="ts">
import { onMount } from 'svelte';
import Icon from './Icon.svelte';
let { label }: { label: string } = $props();
let offline = $state(false);
onMount(() => {
const update = () => (offline = !navigator.onLine);
update();
window.addEventListener('online', update);
window.addEventListener('offline', update);
return () => {
window.removeEventListener('online', update);
window.removeEventListener('offline', update);
};
});
</script>
{#if offline}
<div class="offline-banner" role="status">
<Icon name="ka" inline />
<span>{label}</span>
</div>
{/if}
<style>
.offline-banner {
position: fixed;
bottom: var(--space-2);
left: 50%;
transform: translateX(-50%);
z-index: 300;
display: flex;
align-items: center;
gap: var(--space-1);
padding: 0.5rem var(--space-2);
background: var(--surface);
border: 1px solid var(--rouge-oki);
border-radius: var(--radius-sm);
font-size: 0.85rem;
font-weight: 600;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.offline-banner :global(.icon) {
color: var(--rouge-oki);
}
</style>