Skip to content

Commit

Permalink
feat: subtle page load animation
Browse files Browse the repository at this point in the history
  • Loading branch information
jacob-shuman committed Sep 5, 2024
1 parent b5d5388 commit 8a38632
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 61 deletions.
113 changes: 52 additions & 61 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,78 +1,69 @@
<script lang="ts">
import '@fontsource/unifrakturcook';
import '@fontsource/unifrakturmaguntia';
import { IconAtom, IconJson, IconRss } from '@tabler/icons-svelte';
import { onMount } from 'svelte';
import Article from './Article.svelte';
export let data;
$: ({ title, articles, description } = data);
</script>
<header class="border-gray flex w-full flex-col border-b dark:border-black">
<section class="p-page bg-gray flex flex-col gap-y-1 dark:bg-black dark:text-white">
<h1 class="font-title text-4xl">{title}</h1>
<p><i>{description}</i></p>
</section>
let isMounted = false;
<section
class="px-page flex w-full flex-col gap-y-2 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-y-0"
>
<p class="inline-flex gap-x-4">
<i>{new Date().toDateString()}</i>
<span>•</span>
<i>
{articles.length} Article{articles.length !== 1 ? 's' : ''}
</i>
</p>
onMount(() => {
isMounted = true;
});
</script>

<div class="flex gap-x-4">
<a href="/rss.xml"><IconRss class="size-4" /></a>
<a href="/atom"><IconAtom class="size-4" /></a>
<a href="/json"><IconJson class="size-4" /></a>
</div>
</section>
</header>
{#if isMounted}
<header
class="flex w-full flex-col border-b border-gray dark:border-black"
class:fade-header={isMounted}
>
<section class="flex flex-col gap-y-1 bg-gray p-page dark:bg-black dark:text-white">
<h1 class="font-title text-4xl">{title}</h1>
<p><i>{description}</i></p>
</section>

<main class="gap-x-0 sm:columns-2 xl:columns-3">
{#each articles as article}
<article
class="border-gray hover:bg-gray -ml-[1px] flex break-inside-avoid flex-col gap-y-2 border border-t-0 p-6 duration-[25ms] ease-out motion-safe:transition-colors dark:border-black dark:hover:bg-black"
<section
class="flex w-full flex-col gap-y-2 px-page py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-y-0"
>
<div class="flex flex-col gap-y-1">
<h2 class="font-title flex items-start gap-x-2 text-2xl">
{#if article.favicon}
<img
class="inline"
width="24px"
src={article.favicon}
alt={`${article.title} favicon`}
/>
{/if}

<a class="hover:underline" href={article.url}>{article.title}</a>
</h2>
<p class="inline-flex gap-x-4">
<i>{new Date().toDateString()}</i>
<span>•</span>
<i>
{articles.length} Article{articles.length !== 1 ? 's' : ''}
</i>
</p>

<h3 class="inline-flex gap-x-1">
<i>{article.author || 'By some author'}</i>
<span>•</span>
<i>{article.date ? new Date(article.date).toDateString() : 'At some point in time'}</i>
</h3>
<div class="flex gap-x-4">
<a href="/rss.xml"><IconRss class="size-4" /></a>
<a href="/atom"><IconAtom class="size-4" /></a>
<a href="/json"><IconJson class="size-4" /></a>
</div>
</section>
</header>

<p class="text-justify">{article.description}</p>
<main class="gap-x-0 sm:columns-2 xl:columns-3">
{#each articles as article, index}
<Article {...article} {index} />
{/each}
</main>
{/if}

<div class="flex items-center justify-between">
<a class="bold font-subtitle text-start hover:underline" href={article.url}>
Read more
<!-- TODO: enable when time to read (ttr) is implemented -->
<!-- ({article.ttr}) -->
</a>
<style>
@keyframes fade {
from {
transform: translateY(-8px);
opacity: 0;
}
to {
transform: translateY(0px);
opacity: 100;
}
}
<!-- TODO: enable when delete article is implemented -->
<!-- <button>
<IconTrashFilled class="size-4" />
</button> -->
</div>
</article>
{/each}
</main>
.fade-header {
animation: 250ms fade ease-out;
}
</style>
65 changes: 65 additions & 0 deletions src/routes/Article.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<script lang="ts">
import type { FeedItem } from '$lib/feed';
import { onMount } from 'svelte';
export let { index, favicon, title, url, author, date, description } = $$props as FeedItem & {
index: number;
};
let isMounted = false;
onMount(() => {
setTimeout(() => (isMounted = true), 500 + 15 * index);
});
</script>

<article
class="-ml-[1px] flex break-inside-avoid flex-col gap-y-2 border border-t-0 border-gray p-6 duration-[25ms] ease-out hover:bg-gray motion-safe:transition-colors dark:border-black dark:hover:bg-black"
class:faded={isMounted}
class:opacity-0={!isMounted}
>
<div class="flex flex-col gap-y-1">
<h2 class="flex items-start gap-x-2 font-title text-2xl">
{#if favicon}
<img class="inline" width="24px" src={favicon} alt={`${title} favicon`} />
{/if}

<a class="hover:underline" href={url}>{title}</a>
</h2>

<h3 class="inline-flex gap-x-1">
<i>{author || 'By some author'}</i>
<span>•</span>
<i>{date ? new Date(date).toDateString() : 'At some point in time'}</i>
</h3>
</div>

<p class="text-justify">{description}</p>

<div class="flex items-center justify-between">
<a class="bold text-start font-subtitle hover:underline" href={url}>
Read more
<!-- TODO: enable when time to read (ttr) is implemented -->
<!-- ({ttr}) -->
</a>

<!-- TODO: enable when delete article is implemented -->
<!-- <button>
<IconTrashFilled class="size-4" />
</button> -->
</div>
</article>

<style>
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 100;
}
}
.faded {
animation: 500ms fade ease-out;
}
</style>

0 comments on commit 8a38632

Please sign in to comment.