mirror of
https://github.com/artiemis/artie.moe.git
synced 2026-02-14 09:01:55 +00:00
add seasonal snow, add rz music easter egg
This commit is contained in:
parent
534aac5c8d
commit
a51b6a6946
BIN
src/assets/i-trust-you.m4a
Normal file
BIN
src/assets/i-trust-you.m4a
Normal file
Binary file not shown.
@ -7,14 +7,35 @@
|
|||||||
} from "pixi-live2d-display";
|
} from "pixi-live2d-display";
|
||||||
import { reZeroModels } from "../utils/constants";
|
import { reZeroModels } from "../utils/constants";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
|
||||||
|
const audioSrc = new URL(
|
||||||
|
"../assets/i-trust-you.m4a",
|
||||||
|
import.meta.url
|
||||||
|
).toString();
|
||||||
|
const loudNormRatio = 100 / 40;
|
||||||
|
const modelName = getModelFromParams() || getRandomModelUrl();
|
||||||
|
const gap = modelName.includes("/ferris") ? "gap-6" : "gap-0";
|
||||||
|
|
||||||
let canvas = $state<HTMLCanvasElement>();
|
let canvas = $state<HTMLCanvasElement>();
|
||||||
let isLoaded = $state(false);
|
let isLoaded = $state(false);
|
||||||
let opacity = $derived(isLoaded ? "opacity-1" : "opacity-0");
|
|
||||||
|
let audio = $state<HTMLAudioElement>();
|
||||||
|
let volume = $state(50);
|
||||||
|
let volumeLabel = $state<string>();
|
||||||
|
let volumeLabelTimeout = $state<ReturnType<typeof setTimeout>>();
|
||||||
|
|
||||||
let app: PIXI.Application;
|
let app: PIXI.Application;
|
||||||
let model: Live2DModel;
|
let model: Live2DModel;
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (audio) {
|
||||||
|
audio.volume = Math.round(volume / loudNormRatio) / 100;
|
||||||
|
volumeLabel = `${volume}%`;
|
||||||
|
localStorage.setItem("volume", volume.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function getModelFromParams(): string | undefined {
|
function getModelFromParams(): string | undefined {
|
||||||
if (location.search) {
|
if (location.search) {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
@ -36,6 +57,48 @@
|
|||||||
return choices[Math.floor(Math.random() * choices.length)];
|
return choices[Math.floor(Math.random() * choices.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(e: KeyboardEvent) {
|
||||||
|
if (audio) {
|
||||||
|
switch (e.key) {
|
||||||
|
case "ArrowUp":
|
||||||
|
volume = Math.min(100, volume + 5);
|
||||||
|
showVolumeLabel();
|
||||||
|
break;
|
||||||
|
case "ArrowDown":
|
||||||
|
volume = Math.max(0, volume - 5);
|
||||||
|
showVolumeLabel();
|
||||||
|
break;
|
||||||
|
case " ":
|
||||||
|
audio.paused ? audio.play() : audio.pause();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleModelDoubleClick() {
|
||||||
|
if (!audio && isLoaded) {
|
||||||
|
showVolumeLabel();
|
||||||
|
audio = new Audio(audioSrc);
|
||||||
|
audio.play();
|
||||||
|
|
||||||
|
audio.addEventListener(
|
||||||
|
"ended",
|
||||||
|
() => {
|
||||||
|
audio = undefined;
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
audio.paused ? audio.play() : audio.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showVolumeLabel() {
|
||||||
|
volumeLabel = `${volume}%`;
|
||||||
|
clearTimeout(volumeLabelTimeout);
|
||||||
|
volumeLabelTimeout = setTimeout(() => (volumeLabel = undefined), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
(window as any).PIXI = PIXI;
|
(window as any).PIXI = PIXI;
|
||||||
app = new PIXI.Application({
|
app = new PIXI.Application({
|
||||||
@ -46,12 +109,9 @@
|
|||||||
height: 980,
|
height: 980,
|
||||||
});
|
});
|
||||||
|
|
||||||
model = await Live2DModel.from(
|
model = await Live2DModel.from(modelName, {
|
||||||
getModelFromParams() || getRandomModelUrl(),
|
motionPreload: MotionPreloadStrategy.NONE,
|
||||||
{
|
});
|
||||||
motionPreload: MotionPreloadStrategy.NONE,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
app.stage.on("childAdded", () => {
|
app.stage.on("childAdded", () => {
|
||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
@ -72,17 +132,48 @@
|
|||||||
model.scale.set(0.45);
|
model.scale.set(0.45);
|
||||||
model.x = -600;
|
model.x = -600;
|
||||||
model.y = 0;
|
model.y = 0;
|
||||||
|
|
||||||
|
if (localStorage.getItem("volume")) {
|
||||||
|
volume = +localStorage.getItem("volume");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
app.destroy(false);
|
app.destroy(false);
|
||||||
|
if (audio) {
|
||||||
|
audio.pause();
|
||||||
|
audio.remove();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<canvas
|
<svelte:window onkeydown={handleKeyDown} />
|
||||||
bind:this={canvas}
|
|
||||||
id="live2d"
|
<div class="grid fixed left-0 bottom-0 z-10 {gap}">
|
||||||
onpointerdown={() => model.motion("Tap")}
|
<div class="mx-auto">
|
||||||
class="left-0 bottom-0 fixed lg:w-96 w-44 transition-opacity {opacity}"
|
{#if audio}
|
||||||
class:!cursor-pointer={isLoaded}
|
{#if volumeLabel}
|
||||||
></canvas>
|
<div transition:fade={{ duration: 100 }} class="text-purple-300">
|
||||||
|
{volumeLabel}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<input
|
||||||
|
bind:value={volume}
|
||||||
|
oninput={showVolumeLabel}
|
||||||
|
transition:fade={{ duration: 100 }}
|
||||||
|
type="range"
|
||||||
|
class="lg:w-48 lg:h-2 w-32 h-2 rounded-xl appearance-none bg-purple-300 accent-purple-400 cursor-pointer"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<canvas
|
||||||
|
bind:this={canvas}
|
||||||
|
ondblclick={handleModelDoubleClick}
|
||||||
|
id="live2d"
|
||||||
|
onpointerdown={() => model.motion("Tap")}
|
||||||
|
class="lg:w-96 w-44 transition-opacity {isLoaded
|
||||||
|
? 'opacity-1'
|
||||||
|
: 'opacity-0'}"
|
||||||
|
class:!cursor-pointer={isLoaded}
|
||||||
|
></canvas>
|
||||||
|
</div>
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import Prompt from "../components/Prompt.astro";
|
|||||||
import Centerpiece from "../components/Centerpiece.astro";
|
import Centerpiece from "../components/Centerpiece.astro";
|
||||||
import Icons from "../components/Icons.astro";
|
import Icons from "../components/Icons.astro";
|
||||||
import Model from "../components/Model.svelte";
|
import Model from "../components/Model.svelte";
|
||||||
|
import Snow from "../components/Snow.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="artie">
|
<Layout title="artie">
|
||||||
|
<Snow />
|
||||||
<Prompt />
|
<Prompt />
|
||||||
<Centerpiece />
|
<Centerpiece />
|
||||||
<Icons />
|
<Icons />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user