Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions web/src/lib/components/SettingsPopover.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import Gear from "phosphor-svelte/lib/Gear";
import { Button, Label, Popover, Radio, Switch, Text } from "kumo-svelte";
import { autoRefresh, setAutoRefresh } from "$lib/settings.svelte";
import { autoRefresh, brainrotLevel, setAutoRefresh, setBrainrotLevel, type BrainrotLevel } from "$lib/settings.svelte";
import { setTheme, theme, type Theme } from "$lib/theme.svelte";
</script>

Expand All @@ -13,7 +13,7 @@
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-56" align="end">
<Popover.Content class="w-64" align="end">
<div class="grid gap-4">
<div class="grid gap-1.5">
<Text variant="secondary" size="sm">Theme</Text>
Expand All @@ -29,6 +29,11 @@
<Label for="auto-refresh" class="font-normal">Auto refresh</Label>
<Switch id="auto-refresh" size="sm" checked={autoRefresh.current} onCheckedChange={setAutoRefresh} aria-label="Auto refresh" />
</div>
<Radio.Group orientation="horizontal" value={brainrotLevel.current} onValueChange={(next) => setBrainrotLevel(next as BrainrotLevel)}>
<Radio.Item label="Off" value="off" />
<Radio.Item label="On" value="on" />
<Radio.Item label="Weeb" value="weeb" />
</Radio.Group>
</div>
</div>
</Popover.Content>
Expand Down
113 changes: 113 additions & 0 deletions web/src/lib/components/stats/BrainrotPanel.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<script lang="ts">
import ViewportDvdBouncer from "./ViewportDvdBouncer.svelte";

const videos = [
{
title: "Subway Surfers gameplay",
type: "youtube",
src: "https://www.youtube-nocookie.com/embed/i0M4ARe9v0Y?autoplay=1&mute=1&controls=1&loop=1&playlist=i0M4ARe9v0Y&playsinline=1&rel=0&enablejsapi=1",
},
{
title: "CS2 Surf Utopia gameplay",
type: "youtube",
src: "https://www.youtube-nocookie.com/embed/4fwSVo_bOo8?autoplay=1&mute=1&controls=1&loop=1&playlist=4fwSVo_bOo8&playsinline=1&rel=0&enablejsapi=1",
},
{
title: "Forgetting to rebase on master",
type: "local",
src: "/brainrot/papermc-rebase.mp4",
},
] as const;

function toggleYoutubeAudio(event: MouseEvent): void {
const button = event.currentTarget;
if (!(button instanceof HTMLButtonElement)) return;

const iframe = button.parentElement?.querySelector("iframe");
if (!(iframe instanceof HTMLIFrameElement) || !iframe.contentWindow) return;

const sendCommand = (func: string) => {
iframe.contentWindow?.postMessage(JSON.stringify({ event: "command", func, args: [] }), "https://www.youtube-nocookie.com");
};

const enabled = button.dataset.audioEnabled === "true";
if (enabled) {
sendCommand("mute");
} else {
sendCommand("unMute");
sendCommand("playVideo");
}
button.dataset.audioEnabled = String(!enabled);
button.style.color = enabled ? "" : "var(--color-kumo-success, #4ade80)";
button.setAttribute("aria-label", `${enabled ? "Enable" : "Mute"} sound`);
}
</script>

<section aria-label="Brainrot videos">
{#each videos as video, index (video.title)}
<ViewportDvdBouncer seed={17 + index * 36}>
<div class="floating-video border-kumo-line bg-kumo-layer">
{#if video.type === "local"}
<video src={video.src} autoplay muted loop playsinline controls aria-label={video.title}></video>
{:else}
<div class="youtube-frame">
<iframe src={video.src} title={video.title} allow="autoplay; encrypted-media; fullscreen; picture-in-picture" allowfullscreen></iframe>
<button class="audio-surface" type="button" onclick={toggleYoutubeAudio} aria-label={`Enable sound for ${video.title}`}></button>
</div>
{/if}
</div>
</ViewportDvdBouncer>
{/each}
</section>

<style>
.floating-video {
--video-width: min(36vw, 32rem);
width: var(--video-width);
overflow: hidden;
border-radius: 0.75rem;
box-shadow: 0 1rem 2.5rem rgb(0 0 0 / 28%);
pointer-events: auto;
will-change: transform;
}

.floating-video iframe,
.floating-video video {
display: block;
width: 100%;
aspect-ratio: 16 / 9;
border: 0;
}

.youtube-frame {
position: relative;
}

.audio-surface {
position: absolute;
z-index: 1;
inset: 0;
width: 100%;
height: 100%;
border: 0;
background: transparent;
cursor: pointer;
}

.audio-surface:focus-visible {
outline: 3px solid #67e8f9;
outline-offset: -0.35rem;
}

@media (min-width: 50.01rem) {
.audio-surface {
display: none;
}
}

@media (max-width: 50rem) {
.floating-video {
--video-width: min(72vw, 22rem);
}
}
</style>
89 changes: 89 additions & 0 deletions web/src/lib/components/stats/PaperChanCompanion.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<aside class="paper-chan" aria-label="Paper Chan companion">
<div class="companion-bubble border-kumo-line bg-kumo-layer text-kumo-default">
<strong>Paper Chan Companion</strong>
<span>Watching your patch queue. Ganbatte! ✨</span>
</div>
<img src="/brainrot/paper-chan.png" alt="Paper Chan" />
</aside>

<style>
.paper-chan {
position: fixed;
z-index: 31;
right: 1rem;
bottom: 1rem;
width: clamp(4.5rem, 11vw, 8rem);
pointer-events: none;
animation: companion-float 3.6s ease-in-out infinite;
}

.paper-chan img {
display: block;
width: 100%;
height: auto;
filter: drop-shadow(0 0 0.8rem rgb(236 72 153 / 55%));
}

.companion-bubble {
position: absolute;
right: 70%;
bottom: 75%;
display: grid;
gap: 0.2rem;
width: max-content;
max-width: min(15rem, 55vw);
padding: 0.6rem 0.7rem;
border: 1px solid;
border-radius: 0.75rem;
box-shadow: 0 0.75rem 1.5rem rgb(0 0 0 / 18%);
font-size: 0.7rem;
}

.companion-bubble::after {
position: absolute;
right: 0.75rem;
bottom: -0.4rem;
width: 0.75rem;
height: 0.75rem;
content: "";
border-right: 1px solid;
border-bottom: 1px solid;
background: inherit;
transform: rotate(45deg);
}

.companion-bubble strong {
font-size: 0.75rem;
}

.companion-bubble span {
color: var(--color-kumo-subtle, currentColor);
}

@keyframes companion-float {
0%,
100% {
transform: translateY(0) rotate(-1deg);
}
50% {
transform: translateY(-0.7rem) rotate(1deg);
}
}

@media (prefers-reduced-motion: reduce) {
.paper-chan {
animation-play-state: paused;
}
}

@media (max-width: 50rem) {
.paper-chan {
right: 0.35rem;
bottom: 0.35rem;
}

.companion-bubble {
right: 55%;
}
}
</style>
56 changes: 56 additions & 0 deletions web/src/lib/components/stats/PaperLogo.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<div class="paper-logo" aria-hidden="true">
<img src="https://assets.papermc.io/brand/papermc_logo.256.png" alt="" />
</div>

<style>
.paper-logo {
width: 6rem;
isolation: isolate;
}

.paper-logo::before {
position: absolute;
z-index: -1;
inset: -1.25rem;
content: "";
border-radius: 50%;
background: conic-gradient(#22d3ee, #818cf8, #e879f9, #fb7185, #facc15, #22d3ee);
filter: blur(1.1rem);
opacity: 0.82;
mix-blend-mode: screen;
animation:
glow-spin 5s linear infinite,
glow-pulse 2.4s ease-in-out infinite alternate;
}

.paper-logo img {
display: block;
width: 100%;
height: auto;
filter: drop-shadow(0 0 0.2rem #fff) drop-shadow(0 0 0.8rem #67e8f9) drop-shadow(0 0 1.2rem #e879f9);
}

@keyframes glow-spin {
to {
transform: rotate(360deg);
}
}

@keyframes glow-pulse {
to {
opacity: 0.98;
}
}

@media (prefers-reduced-motion: reduce) {
.paper-logo::before {
animation-play-state: paused;
}
}

@media (max-width: 26rem) {
.paper-logo {
width: 4.5rem;
}
}
</style>
Loading