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>
|