Spaces:
Configuration error
Configuration error
File size: 2,934 Bytes
e8a6c67 fb54991 e8a6c67 fb54991 e8a6c67 fb54991 e8a6c67 fb54991 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | <script lang="ts">
import type { BriefingBlock, Citation } from '$lib/types/claim';
import Claim from './Claim.svelte';
import Cite from './Cite.svelte';
import SectionHead from './SectionHead.svelte';
interface Props {
blocks: BriefingBlock[];
citations: Record<string, Citation>;
streaming?: boolean;
replayKey?: number;
}
let { blocks, citations: cites, streaming = false, replayKey = 0 }: Props = $props();
let visibleCount = $state(blocks.length);
let prefersReducedMotion = $state(false);
$effect(() => {
if (typeof window === 'undefined') return;
prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
});
$effect(() => {
// re-run when replayKey changes
void replayKey;
if (!streaming) {
visibleCount = blocks.length;
return;
}
if (prefersReducedMotion) {
visibleCount = blocks.length;
return;
}
visibleCount = 0;
let i = 0;
let timer: ReturnType<typeof setTimeout>;
const tick = () => {
i++;
visibleCount = i;
if (i < blocks.length) {
timer = setTimeout(tick, i < 2 ? 280 : 420);
}
};
timer = setTimeout(tick, 240);
return () => clearTimeout(timer);
});
</script>
<div
class="briefing-prose"
role="log"
aria-live="polite"
aria-atomic="false"
aria-label="Streaming flood-exposure briefing"
>
{#each blocks.slice(0, visibleCount) as block, i (i)}
{#if block.kind === 'status'}
<!-- briefing-status HTML comes from either:
(a) the static sample fixture (lib/data/sample.ts, trusted), or
(b) the parser's preamble fallback (currently disabled).
No user-supplied input flows here.
-->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
<div class="briefing-status briefing-fade-in">{@html block.html}</div>
{:else if block.kind === 'head'}
<div class="briefing-fade-in">
<SectionHead n={block.n} label={block.label} tier={block.tier} title={block.title} />
</div>
{:else}
<p class="briefing-para briefing-fade-in">
{#each block.parts as part, j (j)}
{#if part.tier}
<Claim tier={part.tier}>{part.text}</Claim>{#if part.cite && cites[part.cite]}<Cite c={cites[part.cite]} />{/if}
{:else}
<span>{part.text}</span>
{/if}
{/each}
</p>
{/if}
{/each}
</div>
<style>
/* Each newly-revealed block fades in over 320ms instead of the
blinking-cursor "typing" cadence. Citation-grounded paragraphs
should land with authority, not chatter. Respects
prefers-reduced-motion via the global rule in tokens.css. */
.briefing-fade-in {
animation: briefing-fade 320ms ease-out both;
}
@keyframes briefing-fade {
from { opacity: 0; transform: translateY(2px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
|