hyprland-website/src/routes/Hypractive.svelte

192 lines
5.1 KiB
Svelte
Raw Normal View History

2023-08-17 16:45:47 +02:00
<script>
import ActiveGitIcon from '~icons/gg/git-branch'
import VaxryImage from '$lib/images/vaxry-github.webp'
import {
Subject,
interval,
map,
of,
scan,
startWith,
switchMap,
merge,
timer,
timeInterval,
filter,
first,
take
} from 'rxjs'
import GitTile from '$lib/components/GitTile.svelte'
import { lerp } from '$lib/Helper.mjs'
import { cubicInOut, expoInOut } from 'svelte/easing'
import DiscordProfilePicture from './DiscordProfilePicture.svelte'
import { setContext } from 'svelte'
const click$ = new Subject()
const ASCENION_CLICKS = 69
const MAX_LIFESPAN_TILE = 2500
const MIN_LIFESPAN_TILE = 800
const MAX_TILES_PER_CLICK = 15
const MIN_TILES_PER_CLICK = 2
2023-09-15 18:46:48 +02:00
/** How fast the user has to click to progress */
2023-08-17 16:45:47 +02:00
const CLICK_EACH_MS = 400
const ASCENION_FALLOFF = -ASCENION_CLICKS / 20
const clickLevel$ = click$.pipe(
timeInterval(),
filter(({ interval }) => interval < CLICK_EACH_MS),
map(() => 1),
switchMap((value) =>
merge(
of(value),
2023-09-15 18:46:48 +02:00
/** If no new value comes in, start decreasing the progress */
2023-08-17 16:45:47 +02:00
interval(CLICK_EACH_MS + 100).pipe(
2023-09-15 18:46:48 +02:00
take(ASCENION_CLICKS), // Prevent this interval from running forever
2023-08-17 16:45:47 +02:00
map(() => ASCENION_FALLOFF)
)
)
),
scan((level, value) => Math.min(ASCENION_CLICKS, Math.max(level + value, 0))),
startWith(0)
)
2023-09-15 18:46:48 +02:00
/** How many clicks are left in percent */
2023-08-17 16:45:47 +02:00
const relativeLevel$ = clickLevel$.pipe(map((clicks) => clicks / ASCENION_CLICKS))
2023-09-15 18:46:48 +02:00
/** Tween/Ease the percents for a nicer look */
const cubicRelativeLevel$ = relativeLevel$.pipe(map(cubicInOut))
2023-08-17 16:45:47 +02:00
const expoRelativeLevel$ = relativeLevel$.pipe(map(expoInOut))
2023-09-15 18:46:48 +02:00
2023-08-17 16:45:47 +02:00
const hasAscended$ = relativeLevel$.pipe(
filter((level) => level >= 1),
first(),
map(() => true),
startWith(false)
)
// Only emit on mouseUp to prevent emitting when the level decreases due to no user interaction
const tiles$ = click$.pipe(
switchMap(() =>
merge(
2023-09-15 18:46:48 +02:00
of(Math.floor(lerp(MIN_TILES_PER_CLICK, MAX_TILES_PER_CLICK, $cubicRelativeLevel$))),
// Remove the tiles after a timeout, if no new ones came in
2023-08-17 16:45:47 +02:00
timer(MAX_LIFESPAN_TILE)
)
),
scan(
(acc, value) => (value === 0 ? [] : [...acc, ...Array.from({ length: value }, () => 1)]),
[]
),
startWith([])
)
2023-09-15 18:46:48 +02:00
$: hue = lerp(200, 130, $cubicRelativeLevel$)
$: scale = lerp(0.9, 2, $cubicRelativeLevel$)
$: translateY = lerp(0, 10, $cubicRelativeLevel$)
2023-08-17 16:45:47 +02:00
/** @type {HTMLDivElement} */
let containerElement
const vaxrySize = 220
const contextId = Symbol('hypractive context')
setContext(contextId, {
biggestSize: vaxrySize * 1.2, // Make it lighter to drag
getSectionElement: () => containerElement
})
function onClick() {
click$.next(1)
}
</script>
<div class="relative overflow-visible">
<button
2023-09-13 14:27:13 +02:00
class="flex items-center gap-3 font-bold text-slate-400 shadow-black drop-shadow-lg transition-colors hover:underline active:scale-95"
2023-08-17 16:45:47 +02:00
on:click={onClick}
style:color={$relativeLevel$ > 0 ? `hsl(${hue} 64% 53%)` : undefined}
style:scale={$relativeLevel$ > 0 ? scale : undefined}
2023-09-13 14:27:13 +02:00
style:translate={$relativeLevel$ > 0 ? `0px -${translateY}px` : undefined}
2023-08-17 16:45:47 +02:00
>
<ActiveGitIcon class="h-8 w-8" />
<span class="transition-colors"> Hypractive development </span>
</button>
2023-09-13 14:27:13 +02:00
<div class="pointer-events-none absolute left-1/2 top-1/2 -z-10">
2023-08-17 16:45:47 +02:00
{#each $tiles$ as _}
<GitTile
2023-09-15 18:46:48 +02:00
lifeSpan={lerp(MIN_LIFESPAN_TILE, MAX_LIFESPAN_TILE, $cubicRelativeLevel$)}
2023-08-17 16:45:47 +02:00
maxSpeed={lerp(10, 38, $expoRelativeLevel$)}
minSpeed={lerp(1, 9, $expoRelativeLevel$)}
/>
{/each}
</div>
<div class="mask" bind:this={containerElement}>
{#if $hasAscended$}
<div
2023-09-13 14:27:13 +02:00
class="vaxx-wrapper absolute bottom-[240px] left-1/2 z-50 aspect-square -translate-x-[100px] rounded-full animate-in fade-in-0 zoom-in-95 slide-in-from-bottom-[500px] slide-in-from-left-20 [animation-duration:2.5s]"
2023-08-17 16:45:47 +02:00
style:width={vaxrySize + 'px'}
>
<DiscordProfilePicture
image={VaxryImage}
size={vaxrySize}
coordinates={[0, 0]}
class="outline-orange-300"
{contextId}
isAnimating={false}
/>
</div>
{/if}
<!-- Rising sun -->
<div
class="bg-gradient"
style:opacity={$hasAscended$ ? 1 : $relativeLevel$}
style="--relativeLevel: {$hasAscended$ ? 1 : $expoRelativeLevel$ - 0.2}"
/>
</div>
</div>
<style lang="postcss">
.bg-gradient {
position: absolute;
bottom: -100%;
left: 50%;
translate: -50% calc(var(--relativeLevel) * -60%);
z-index: -10;
transition:
opacity 550ms,
translate 1.5s ease-in;
background: radial-gradient(
closest-side,
theme(colors.yellow.200),
theme(colors.orange.300 / 50%),
theme(colors.red.800 / 0%)
);
width: 150vw;
height: 800px;
}
.vaxx-wrapper {
animation-timing-function: cubic-bezier(0.05, -0.82, 0.165, 1);
pointer-events: auto;
filter: drop-shadow(0px 0px 10px theme(colors.orange.200))
drop-shadow(0px 0px 40px theme(colors.orange.300));
}
.mask {
position: absolute;
pointer-events: none;
bottom: 0%;
z-index: 10;
left: 50%;
translate: -50% 0%;
width: 150vw;
height: 1000px;
/* background: linear-gradient(0deg, black 50%, white 50%); */
/* mask-image: linear-gradient(0deg, transparent 50%, white 50%); */
overflow: hidden;
}
</style>