- social: likes/comments rejected on a closed event (e2e proven). - admin: patch_config validates-then-writes atomically; char-based length. - hashtag/comment models: atomic tag writes in edit + comment paths. - Modal: inert the background (modal-inert action) for screen readers. - sse.ts: idempotent visibilitychange listener (no duplicate reconnects). - diashow: videos advance on `ended` (transitions + page wiring). - docs/hygiene: README clone-case fix; removed stale committed .env.test and gitignored it; docker-compose.dev.yml tidy. Left documented as accepted-risk per plan: PIN-persist-after-logout, UUID-reachable hidden uploads, DECISION-media-auth.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
55 lines
1.4 KiB
Svelte
55 lines
1.4 KiB
Svelte
<script lang="ts">
|
|
// Crossfade + slow zoom and pan (the "Ken Burns" effect). Image-only — videos
|
|
// fall back to the crossfade behaviour to avoid double-animation.
|
|
|
|
interface Props {
|
|
src: string;
|
|
isVideo: boolean;
|
|
durationMs: number;
|
|
/** Fired when a video slide finishes so the parent can advance early. */
|
|
onended?: () => void;
|
|
}
|
|
|
|
let { src, isVideo, durationMs, onended }: Props = $props();
|
|
|
|
// Mild random pan so each slide feels different. Range chosen so the image never
|
|
// pans out of frame given the object-fit: cover.
|
|
const panX = Math.round((Math.random() - 0.5) * 6); // -3% .. +3%
|
|
const panY = Math.round((Math.random() - 0.5) * 6);
|
|
</script>
|
|
|
|
<div
|
|
class="absolute inset-0 flex items-center justify-center overflow-hidden bg-black"
|
|
style="animation: kb-fade {durationMs}ms ease-out forwards;"
|
|
>
|
|
{#if isVideo}
|
|
<!-- svelte-ignore a11y_media_has_caption -->
|
|
<video
|
|
{src}
|
|
autoplay
|
|
muted
|
|
playsinline
|
|
{onended}
|
|
class="h-full w-full object-contain"
|
|
></video>
|
|
{:else}
|
|
<img
|
|
{src}
|
|
alt=""
|
|
class="h-full w-full object-cover"
|
|
style="animation: kb-zoom 10s ease-out forwards; transform-origin: {50 + panX}% {50 + panY}%;"
|
|
/>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
@keyframes kb-fade {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
@keyframes kb-zoom {
|
|
from { transform: scale(1.0); }
|
|
to { transform: scale(1.1); }
|
|
}
|
|
</style>
|