forked from ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI
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:
@@ -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),
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user