Skip to content

Commit e3e2f09

Browse files
functionstackxclaudeadibarra
authored andcommitted
feat(theme): minecraft assets scattered around + Ender Dragon fly-across (SemiAnalysisAI#297)
* feat(theme): scatter Minecraft assets + Ender Dragon fly-across on minecraft theme Adds a `MinecraftDecorations` client component that renders six iconic Minecraft assets at fixed corner positions when the minecraft theme is active, plus a one-shot Ender Dragon GIF that flies right-to-left across the viewport every time the theme is activated: - top-left: Creeper mob render - top-right: Diamond pickaxe (rotated 35°, mining vibe) - mid-right: Technoblade body render (RIP — pig-king skin) - bottom-left: Grass block - bottom-mid: TNT - bottom-right: Diamond - top stripe: Ender Dragon, fly-across once per activation Mirrors the `RickMortyDecorations` pattern from PR SemiAnalysisAI#295: a MutationObserver on `document.documentElement` toggles the cast live with the mode picker, all images are `pointer-events: none` at z-0, and the static cutouts are hidden below `lg` so narrow chart layouts stay clean. The dragon uses a `dragonNonce` React key so its `<img>` remounts on every off→on theme transition, restarting the CSS animation. Keyframe `mc-dragon-flyacross` lives in globals.css next to the existing splash-bounce animation, takes ~9s, and respects `prefers-reduced-motion: reduce`. Asset provenance: minecraft.wiki (CC BY-NC-SA 3.0) for blocks/items and the Ender Dragon GIF; mc-heads.net body render for the Technoblade skin (decorative use). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(theme): drop Technoblade cutout from minecraft decorations Removes the mid-right Technoblade body render and its asset. Six remaining decorations (creeper, diamond pickaxe, grass block, TNT, diamond, dragon) keep the spread of the cast unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(theme): swap creeper for zombified piglin facing inward Replace the top-left creeper with a Zombified Piglin sprite from the Minecraft Wiki. The canonical render faces left (sword arm out the viewer's left), which on a top-left anchor would point AWAY from the chart. Mirror it with `scaleX(-1)` so the sword-arm faces toward the middle of the viewport, where the user's attention actually is. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(theme): add CC BY-NC-SA 3.0 attribution for minecraft assets --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: adibarra <93070681+adibarra@users.noreply.github.com>
1 parent a276b89 commit e3e2f09

9 files changed

Lines changed: 201 additions & 0 deletions

File tree

64.7 KB
Loading
93.7 KB
Loading
1.38 MB
Loading
80.7 KB
Loading
109 KB
Loading
71.1 KB
Loading

packages/app/src/app/globals.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,41 @@
319319
display: none;
320320
}
321321

322+
/* Ender-dragon fly-across — one-shot animation on minecraft theme activation,
323+
* triggered from minecraft-decorations.tsx. Plays once (iteration-count: 1)
324+
* and lands the dragon off-screen left so the GIF stops being rendered. */
325+
@keyframes mc-dragon-flyacross {
326+
0% {
327+
transform: translate(110vw, 0) scale(1);
328+
opacity: 0;
329+
}
330+
8% {
331+
opacity: 0.95;
332+
}
333+
50% {
334+
transform: translate(40vw, -3vh) scale(1.05);
335+
}
336+
92% {
337+
opacity: 0.95;
338+
}
339+
100% {
340+
transform: translate(-130vw, 1vh) scale(1);
341+
opacity: 0;
342+
}
343+
}
344+
345+
.mc-dragon-flyacross {
346+
animation: mc-dragon-flyacross 9s cubic-bezier(0.4, 0, 0.6, 1) 1 forwards;
347+
will-change: transform, opacity;
348+
}
349+
350+
@media (prefers-reduced-motion: reduce) {
351+
.mc-dragon-flyacross {
352+
animation: none;
353+
display: none;
354+
}
355+
}
356+
322357
/* Splash text — yellow bouncing rotated text (Minecraft title screen style) */
323358
@keyframes splash-bounce {
324359
0%,

packages/app/src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Footer } from '@/components/footer/footer';
1212
import { Header } from '@/components/header/header';
1313
import { CircuitBackground } from '@/components/circuit-background';
1414
import { MinecraftBackgroundLazy } from '@/components/minecraft/minecraft-background-lazy';
15+
import { MinecraftDecorations } from '@/components/minecraft/minecraft-decorations';
1516
import { ThemeProvider } from '@/components/ui/theme-provider';
1617
import {
1718
AUTHOR_HANDLE,
@@ -181,6 +182,7 @@ export default async function RootLayout({
181182
<body className={`${dm_sans.variable} antialiased relative min-h-screen flex flex-col`}>
182183
<CircuitBackground />
183184
<MinecraftBackgroundLazy />
185+
<MinecraftDecorations />
184186
<PostHogProvider>
185187
<script type="application/ld+json">{JSON.stringify(jsonLd)}</script>
186188
<QueryProvider>
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
5+
/**
6+
* Decorative Minecraft asset PNGs scattered around the viewport corners,
7+
* plus a one-shot Ender Dragon fly-across at theme activation. Only renders
8+
* while the minecraft theme is active. Watches `document.documentElement`
9+
* class changes via a MutationObserver so it appears / disappears live with
10+
* the mode-toggle (mirrors `minecraft-toggles.tsx`).
11+
*
12+
* All static images are `pointer-events: none` at low z-index. The dragon is
13+
* absolutely positioned and flies right-to-left once per theme activation
14+
* (animation-iteration-count: 1) — the GIF's wing-flap plays during the
15+
* traversal so the entrance feels alive.
16+
*
17+
* Asset provenance: minecraft.wiki (CC BY-NC-SA 3.0) for blocks/items/dragon.
18+
*/
19+
const DECORATIONS = [
20+
// Top-left: Zombified Piglin, mirrored so its sword arm points toward the
21+
// middle of the screen rather than off-screen left. Keep the original
22+
// -6deg tilt — flipping over X also flips the rotate direction visually,
23+
// which lands as a slight forward lean toward the chart area.
24+
{
25+
src: '/decorative/minecraft/zombified-piglin.png',
26+
alt: 'Zombified Piglin',
27+
style: {
28+
top: '5rem',
29+
left: '0.5rem',
30+
width: 'min(110px, 9vw)',
31+
transform: 'scaleX(-1) rotate(-6deg)',
32+
},
33+
},
34+
// Top-right: Diamond pickaxe, mining vibe.
35+
{
36+
src: '/decorative/minecraft/diamond-pickaxe.png',
37+
alt: 'Diamond pickaxe',
38+
style: {
39+
top: '5rem',
40+
right: '0.5rem',
41+
width: 'min(110px, 9vw)',
42+
transform: 'rotate(35deg)',
43+
},
44+
},
45+
// Bottom-left: Grass block, classic anchor.
46+
{
47+
src: '/decorative/minecraft/grass-block.png',
48+
alt: 'Grass block',
49+
style: {
50+
bottom: '4rem',
51+
left: '0.5rem',
52+
width: 'min(110px, 9vw)',
53+
transform: 'rotate(-4deg)',
54+
},
55+
},
56+
// Bottom-mid-left: TNT, ready to go boom.
57+
{
58+
src: '/decorative/minecraft/tnt.png',
59+
alt: 'TNT',
60+
style: {
61+
bottom: '4rem',
62+
left: '20%',
63+
width: 'min(95px, 8vw)',
64+
transform: 'rotate(6deg)',
65+
},
66+
},
67+
// Bottom-right: Diamond, the loot.
68+
{
69+
src: '/decorative/minecraft/diamond.png',
70+
alt: 'Diamond',
71+
style: {
72+
bottom: '4rem',
73+
right: '0.5rem',
74+
width: 'min(95px, 8vw)',
75+
transform: 'rotate(-6deg)',
76+
},
77+
},
78+
] as const;
79+
80+
export function MinecraftDecorations() {
81+
const [active, setActive] = useState(false);
82+
// Bumps each time the theme is (re)activated, used as the React key on the
83+
// dragon `<img>` to force-remount and re-trigger the CSS fly-across.
84+
const [dragonNonce, setDragonNonce] = useState(0);
85+
86+
useEffect(() => {
87+
let wasMinecraft = document.documentElement.classList.contains('minecraft');
88+
setActive(wasMinecraft);
89+
if (wasMinecraft) setDragonNonce((n) => n + 1);
90+
91+
const check = () => {
92+
const isMinecraft = document.documentElement.classList.contains('minecraft');
93+
setActive(isMinecraft);
94+
// Re-trigger the dragon only on a transition off→on, not on every
95+
// unrelated class change.
96+
if (isMinecraft && !wasMinecraft) setDragonNonce((n) => n + 1);
97+
wasMinecraft = isMinecraft;
98+
};
99+
100+
const observer = new MutationObserver(check);
101+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
102+
return () => observer.disconnect();
103+
}, []);
104+
105+
if (!active) return null;
106+
107+
return (
108+
<>
109+
<div
110+
aria-hidden="true"
111+
className="hidden lg:block fixed inset-0 pointer-events-none z-0 overflow-hidden"
112+
>
113+
{DECORATIONS.map((d) => (
114+
<img
115+
key={d.src}
116+
src={d.src}
117+
alt={d.alt}
118+
className="absolute opacity-70 [image-rendering:pixelated] drop-shadow-[0_0_14px_rgba(0,0,0,0.45)]"
119+
style={d.style}
120+
/>
121+
))}
122+
</div>
123+
124+
{/* One-shot Ender Dragon fly-across. Keyed on dragonNonce so it
125+
* remounts (and the CSS animation re-plays) every theme activation. */}
126+
<div
127+
aria-hidden="true"
128+
className="hidden md:block fixed inset-0 pointer-events-none z-0 overflow-hidden"
129+
>
130+
<img
131+
key={dragonNonce}
132+
src="/decorative/minecraft/ender-dragon.gif"
133+
alt=""
134+
className="absolute top-[15%] [image-rendering:pixelated] drop-shadow-[0_0_24px_rgba(160,80,200,0.55)] mc-dragon-flyacross"
135+
style={{ width: 'min(280px, 22vw)' }}
136+
/>
137+
</div>
138+
139+
<div className="hidden lg:block fixed bottom-1 right-2 z-0 text-[10px] text-foreground/50 text-right leading-tight">
140+
<div>
141+
art:{' '}
142+
<a
143+
href="https://minecraft.wiki/"
144+
target="_blank"
145+
rel="noopener noreferrer"
146+
className="underline hover:text-foreground"
147+
>
148+
minecraft.wiki
149+
</a>
150+
</div>
151+
<div>
152+
<a
153+
href="https://creativecommons.org/licenses/by-nc-sa/3.0/"
154+
target="_blank"
155+
rel="noopener noreferrer"
156+
className="underline hover:text-foreground"
157+
>
158+
CC BY-NC-SA 3.0
159+
</a>
160+
</div>
161+
</div>
162+
</>
163+
);
164+
}

0 commit comments

Comments
 (0)