<script lang="ts">
import Reveal from "./Reveal.svelte";
/*
* The /timeline page in outline: its three columns as tiles, no words. Notifications
* and recents on the left, the feed's event cards in the middle, trending and
* announcements on the right, each with a touch of the colour it has in the app.
* The tiles rise in; then the notifications' unread dots land, the trending bars
* grow like a chart, and the newest event's avatar lights up.
*/
const success = "var(--color-background-success-emphasis)";
const accent = "var(--color-background-info-emphasis)";
const star = "#f5b301";
interface Tile {
kind: string;
glyph: string;
rows: { tone: string; width: string }[];
}
const left: Tile[] = [
{
kind: "notifications",
glyph: accent,
rows: [
{ tone: accent, width: "w-3/4" },
{ tone: accent, width: "w-2/3" },
{ tone: accent, width: "w-1/2" }
]
},
{
kind: "recents",
glyph: "var(--color-foreground-subtle)",
rows: [
{ tone: success, width: "w-2/3" },
{ tone: accent, width: "w-3/4" }
]
}
];
const right: Tile[] = [
{
kind: "trending",
glyph: star,
rows: [
{ tone: star, width: "w-4/5" },
{ tone: star, width: "w-3/5" },
{ tone: star, width: "w-2/5" }
]
},
{ kind: "announcements", glyph: accent, rows: [{ tone: accent, width: "w-3/4" }] }
];
const events = [
{ avatar: "#ef8f5b", lang: "#00add8", fresh: true },
{ avatar: "#8b7cf6", lang: "#dea584", fresh: false },
{ avatar: "#4fb286", lang: "#f1e05a", fresh: false }
];
</script>
{#snippet rail(tiles: Tile[])}
<div class="rail">
{#each tiles as tile (tile.kind)}
<div class={["tile", tile.kind]}>
<span class="head">
<span class="glyph" style:--tone={tile.glyph}></span>
<span class="bar w-1/3"></span>
</span>
<span class="plate">
{#each tile.rows as row, r (r)}
<span class="row" style:--tone={row.tone} style:--r={r}>
<span class="dot"></span>
<span class={["bar", row.width]}></span>
</span>
{/each}
</span>
</div>
{/each}
</div>
{/snippet}
<Reveal items=".tile" class="page">
{@render rail(left)}
<div class="rail feed">
{#each events as event, e (e)}
<div class={["tile", event.fresh && "fresh"]} style:--avatar={event.avatar} style:--lang={event.lang}>
<span class="head"><span class="avatar"></span><span class="bar w-2/5"></span></span>
<span class="plate tall">
<span class="bar strong w-1/2"></span>
<span class="bar w-full dim"></span>
<span class="bar w-3/4 dim"></span>
<span class="foot"><span class="lang"></span><span class="bar w-1/6 dim"></span></span>
</span>
</div>
{/each}
</div>
{@render rail(right)}
</Reveal>
<style>
:global(.page) {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr) minmax(0, 1fr);
align-items: start;
gap: 0.625rem;
}
.rail {
display: flex;
flex-direction: column;
gap: 0.625rem;
}
/* the app's layered card: a subtle frame, its header strip, the body plate inside */
.tile {
display: flex;
flex-direction: column;
gap: 3px;
padding: 3px;
border-radius: calc(var(--radius-md, 0.375rem) + 4px);
background: var(--color-background-subtle);
box-shadow: 0 0 0 1px var(--color-border-default);
}
.head {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.5rem;
}
.plate {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.625rem;
border-radius: var(--radius-md, 0.375rem);
background: var(--color-background-default);
box-shadow: 0 0 0 1px var(--color-border-default);
}
.plate.tall {
min-height: 5.5rem;
}
.row,
.foot {
display: flex;
align-items: center;
gap: 0.375rem;
}
.foot {
margin-top: auto;
}
.bar {
display: block;
height: 0.375rem;
border-radius: 999px;
background: color-mix(in srgb, var(--color-foreground-default) 20%, transparent);
}
.bar.strong {
height: 0.5rem;
background: color-mix(in srgb, var(--color-foreground-default) 28%, transparent);
}
.bar.dim {
background: color-mix(in srgb, var(--color-foreground-default) 10%, transparent);
}
/* the colour: each tile's glyph, its rows' dots, the feed's avatars and languages */
.glyph {
flex: none;
width: 0.75rem;
height: 0.75rem;
border-radius: 3px;
background: color-mix(in srgb, var(--tone) 85%, transparent);
}
.dot {
flex: none;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background: var(--tone);
}
.trending .row .bar {
background: color-mix(in srgb, var(--tone) 35%, transparent);
transform-origin: left;
}
.announcements .row .bar {
background: color-mix(in srgb, var(--tone) 30%, transparent);
}
.avatar {
flex: none;
width: 0.875rem;
height: 0.875rem;
border-radius: 50%;
background: color-mix(in srgb, var(--avatar) 70%, var(--color-background-default));
}
.lang {
flex: none;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background: var(--lang);
}
/* armed: each detail waits in its start pose until the tiles are in */
:global(.reveal.armed) .notifications .dot {
scale: 0;
}
:global(.reveal.armed) .trending .row .bar {
scale: 0 1;
}
/* the unread dots land one by one, the last keeps a slow pulse */
:global(.reveal.playing) .notifications .dot {
animation: land 0.35s cubic-bezier(0.3, 1.6, 0.5, 1) calc(0.6s + var(--r) * 0.15s) both;
}
:global(.reveal.playing) .notifications .row:first-child .dot {
animation:
land 0.35s cubic-bezier(0.3, 1.6, 0.5, 1) 0.6s both,
pulse 2.4s ease-in-out 1.2s infinite;
}
/* the trending bars grow out from the left, like a chart */
:global(.reveal.playing) .trending .row .bar {
animation: grow 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) calc(0.8s + var(--r) * 0.12s) both;
}
/* the newest event's avatar lights up for a moment */
:global(.reveal.playing) .fresh .avatar {
animation: ring 1.2s ease-out 1.1s both;
}
@keyframes land {
from {
scale: 0;
}
to {
scale: 1;
}
}
@keyframes pulse {
0%,
100% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--tone) 45%, transparent);
}
50% {
box-shadow: 0 0 0 0.25rem transparent;
}
}
@keyframes grow {
from {
scale: 0 1;
}
to {
scale: 1 1;
}
}
@keyframes ring {
from {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--avatar) 55%, transparent);
}
to {
box-shadow: 0 0 0 0.5rem transparent;
}
}
@media (prefers-reduced-motion: reduce) {
:global(.reveal.playing) .notifications .row:first-child .dot {
animation: none;
}
}
@media (width < 40rem) {
:global(.page) {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.feed {
grid-column: span 2;
order: -1;
}
}
</style>