mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2026-02-12 15:52:47 +00:00
initial commit
This commit is contained in:
@@ -495,6 +495,89 @@
|
||||
animation: float 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Flying plane animation for kids map */
|
||||
@keyframes flyPlane {
|
||||
0% {
|
||||
left: -200px;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flying plane animation across full map width */
|
||||
@keyframes flyPlaneMap {
|
||||
0% {
|
||||
left: -250px;
|
||||
}
|
||||
100% {
|
||||
left: var(--map-width, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Engine vibration animation for vehicles */
|
||||
@keyframes engineVibrate {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Propeller spin animation */
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Car driving animations */
|
||||
@keyframes driveLeft {
|
||||
0% {
|
||||
left: var(--map-width, 100%);
|
||||
}
|
||||
100% {
|
||||
left: -80px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes driveRight {
|
||||
0% {
|
||||
left: -80px;
|
||||
}
|
||||
100% {
|
||||
left: var(--map-width, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pixelated flag wave animation - broken wave effect */
|
||||
@keyframes flagWave {
|
||||
0% {
|
||||
transform: skewX(0deg) scaleX(1);
|
||||
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 100%, 4px 50%);
|
||||
}
|
||||
25% {
|
||||
transform: skewX(-2deg) scaleX(0.98);
|
||||
clip-path: polygon(0 2px, calc(100% - 10px) 0, 100% 45%, calc(100% - 6px) 100%, 0 calc(100% - 2px), 6px 50%);
|
||||
}
|
||||
50% {
|
||||
transform: skewX(0deg) scaleX(1);
|
||||
clip-path: polygon(0 0, calc(100% - 6px) 2px, 100% 55%, calc(100% - 10px) calc(100% - 2px), 0 100%, 4px 45%);
|
||||
}
|
||||
75% {
|
||||
transform: skewX(2deg) scaleX(0.98);
|
||||
clip-path: polygon(0 2px, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 calc(100% - 2px), 4px 55%);
|
||||
}
|
||||
100% {
|
||||
transform: skewX(0deg) scaleX(1);
|
||||
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 100%, 4px 50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Cloud animations for kids background */
|
||||
@keyframes cloud-drift {
|
||||
0% {
|
||||
|
||||
@@ -360,3 +360,255 @@ export function PixelPath({ width = 100, className }: { width?: number; classNam
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// Pixel Art Sports Car
|
||||
export function PixelCar({ className, color = "#E74C3C" }: { className?: string; color?: string }) {
|
||||
// Darker shade for details
|
||||
const darkColor = color === "#E74C3C" ? "#C0392B" : color === "#27AE60" ? "#1E8449" : "#2471A3";
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 36 14"
|
||||
className={cn("w-28 h-11", className)}
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
>
|
||||
{/* Shadow */}
|
||||
<rect x="4" y="12" width="28" height="2" fill="#00000033" />
|
||||
{/* Car body - low sleek profile */}
|
||||
<rect x="2" y="6" width="32" height="6" fill={color} />
|
||||
{/* Sloped hood */}
|
||||
<rect x="26" y="5" width="8" height="2" fill={color} />
|
||||
{/* Low cabin */}
|
||||
<rect x="10" y="3" width="14" height="4" fill={color} />
|
||||
{/* Body details/shading */}
|
||||
<rect x="2" y="10" width="32" height="2" fill={darkColor} />
|
||||
{/* Windshield - angled */}
|
||||
<rect x="20" y="3" width="4" height="3" fill="#87CEEB" />
|
||||
{/* Rear window */}
|
||||
<rect x="12" y="4" width="4" height="2" fill="#87CEEB" />
|
||||
{/* Wheels - larger sporty */}
|
||||
<rect x="4" y="10" width="6" height="4" fill="#222" />
|
||||
<rect x="5" y="11" width="4" height="2" fill="#555" />
|
||||
<rect x="26" y="10" width="6" height="4" fill="#222" />
|
||||
<rect x="27" y="11" width="4" height="2" fill="#555" />
|
||||
{/* Headlights */}
|
||||
<rect x="32" y="6" width="2" height="2" fill="#FFD700" />
|
||||
{/* Taillights */}
|
||||
<rect x="2" y="7" width="2" height="2" fill="#DC143C" />
|
||||
{/* Spoiler */}
|
||||
<rect x="2" y="4" width="6" height="2" fill={darkColor} />
|
||||
<rect x="4" y="2" width="2" height="2" fill={darkColor} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// Pixel Art Old Classic Car (Beetle style)
|
||||
export function PixelOldCar({ className, color = "#27AE60" }: { className?: string; color?: string }) {
|
||||
const darkColor = color === "#27AE60" ? "#1E8449" : "#1a5276";
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 28 14"
|
||||
className={cn("w-22 h-11", className)}
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
>
|
||||
{/* Shadow */}
|
||||
<rect x="4" y="12" width="20" height="2" fill="#00000033" />
|
||||
{/* Car body - rounded beetle shape */}
|
||||
<rect x="2" y="6" width="24" height="6" fill={color} />
|
||||
{/* Rounded roof */}
|
||||
<rect x="6" y="2" width="16" height="6" fill={color} />
|
||||
<rect x="8" y="1" width="12" height="2" fill={color} />
|
||||
{/* Body shading */}
|
||||
<rect x="2" y="10" width="24" height="2" fill={darkColor} />
|
||||
{/* Front fender bump */}
|
||||
<rect x="20" y="5" width="6" height="2" fill={color} />
|
||||
{/* Rear fender bump */}
|
||||
<rect x="2" y="5" width="6" height="2" fill={color} />
|
||||
{/* Windshield - rounded */}
|
||||
<rect x="16" y="3" width="4" height="3" fill="#87CEEB" />
|
||||
{/* Rear window */}
|
||||
<rect x="8" y="3" width="4" height="3" fill="#87CEEB" />
|
||||
{/* Wheels */}
|
||||
<rect x="4" y="10" width="5" height="4" fill="#222" />
|
||||
<rect x="5" y="11" width="3" height="2" fill="#555" />
|
||||
<rect x="19" y="10" width="5" height="4" fill="#222" />
|
||||
<rect x="20" y="11" width="3" height="2" fill="#555" />
|
||||
{/* Headlights - round */}
|
||||
<rect x="24" y="7" width="2" height="2" fill="#FFD700" />
|
||||
{/* Taillights */}
|
||||
<rect x="2" y="7" width="2" height="2" fill="#DC143C" />
|
||||
{/* Chrome bumpers */}
|
||||
<rect x="24" y="10" width="2" height="2" fill="#C0C0C0" />
|
||||
<rect x="2" y="10" width="2" height="2" fill="#C0C0C0" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// Pixel Art A-Team style Van with optional text
|
||||
export function PixelVan({ className, text }: { className?: string; text?: string }) {
|
||||
return (
|
||||
<div className={cn("relative", className)}>
|
||||
<svg
|
||||
viewBox="0 0 40 20"
|
||||
className="w-32 h-16"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
>
|
||||
{/* Shadow */}
|
||||
<rect x="4" y="18" width="32" height="2" fill="#00000033" />
|
||||
|
||||
{/* Van body - white */}
|
||||
<rect x="2" y="4" width="34" height="12" fill="#F5F5F5" />
|
||||
|
||||
{/* Roof */}
|
||||
<rect x="4" y="2" width="30" height="4" fill="#E8E8E8" />
|
||||
|
||||
{/* Windshield */}
|
||||
<rect x="28" y="4" width="6" height="6" fill="#87CEEB" />
|
||||
|
||||
{/* Wheels */}
|
||||
<rect x="6" y="16" width="6" height="4" fill="#222" />
|
||||
<rect x="7" y="17" width="4" height="2" fill="#555" />
|
||||
<rect x="28" y="16" width="6" height="4" fill="#222" />
|
||||
<rect x="29" y="17" width="4" height="2" fill="#555" />
|
||||
|
||||
{/* Headlights */}
|
||||
<rect x="34" y="8" width="2" height="2" fill="#FFD700" />
|
||||
{/* Taillights */}
|
||||
<rect x="2" y="12" width="2" height="2" fill="#DC143C" />
|
||||
|
||||
{/* Front bumper */}
|
||||
<rect x="34" y="14" width="4" height="2" fill="#333" />
|
||||
</svg>
|
||||
{/* Text overlay on van side */}
|
||||
{text && (
|
||||
<span
|
||||
className="absolute top-[40%] left-[40%] -translate-x-1/2 -translate-y-1/2 text-gray-800 font-bold text-[11px] whitespace-nowrap"
|
||||
style={{ textShadow: "0.5px 0.5px 0 #fff", transform: "scaleX(-1)" }}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Pixel Art Lake
|
||||
export function PixelLake({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 32 16"
|
||||
className={cn("w-16 h-8", className)}
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
>
|
||||
{/* Lake water - multiple shades of blue */}
|
||||
<rect x="4" y="4" width="24" height="2" fill="#5DADE2" />
|
||||
<rect x="2" y="6" width="28" height="2" fill="#3498DB" />
|
||||
<rect x="0" y="8" width="32" height="4" fill="#2980B9" />
|
||||
<rect x="2" y="12" width="28" height="2" fill="#3498DB" />
|
||||
<rect x="6" y="14" width="20" height="2" fill="#5DADE2" />
|
||||
{/* Water sparkles */}
|
||||
<rect x="8" y="8" width="2" height="2" fill="#AED6F1" />
|
||||
<rect x="18" y="10" width="2" height="2" fill="#AED6F1" />
|
||||
<rect x="24" y="8" width="2" height="2" fill="#D4E6F1" />
|
||||
{/* Shore/edge */}
|
||||
<rect x="4" y="2" width="6" height="2" fill="#C4A574" />
|
||||
<rect x="22" y="2" width="6" height="2" fill="#C4A574" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// Pixel Art Small Pond
|
||||
export function PixelPond({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 12"
|
||||
className={cn("w-10 h-6", className)}
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
>
|
||||
{/* Pond water */}
|
||||
<rect x="4" y="2" width="12" height="2" fill="#5DADE2" />
|
||||
<rect x="2" y="4" width="16" height="4" fill="#3498DB" />
|
||||
<rect x="4" y="8" width="12" height="2" fill="#2980B9" />
|
||||
{/* Water sparkle */}
|
||||
<rect x="8" y="4" width="2" height="2" fill="#AED6F1" />
|
||||
{/* Lily pad */}
|
||||
<rect x="12" y="6" width="4" height="2" fill="#27AE60" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// Pixel Art Cessna-style Plane with Banner (banner trails behind)
|
||||
export function PixelPlaneWithBanner({ className, bannerText = "LEARN AI!" }: { className?: string; bannerText?: string }) {
|
||||
return (
|
||||
<div className={cn("flex items-center", className)}>
|
||||
{/* Banner trailing behind - wavy flag style with wave animation */}
|
||||
<div
|
||||
className="bg-[#FFD700] border-2 border-[#DAA520] px-4 py-1.5 -mr-1"
|
||||
style={{
|
||||
clipPath: "polygon(0 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 100%, 4px 50%)",
|
||||
animation: "flagWave 0.4s steps(2) infinite",
|
||||
}}
|
||||
>
|
||||
<span className="text-[#8B4513] font-bold text-sm whitespace-nowrap">{bannerText}</span>
|
||||
</div>
|
||||
|
||||
{/* Banner rope */}
|
||||
<svg viewBox="0 0 24 8" className="w-6 h-2 -mr-1" style={{ imageRendering: "pixelated" }}>
|
||||
<rect x="0" y="3" width="24" height="2" fill="#8B4513" />
|
||||
</svg>
|
||||
|
||||
{/* Cessna-style Plane */}
|
||||
<svg
|
||||
viewBox="0 0 48 28"
|
||||
className="w-24 h-14"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
>
|
||||
{/* Fuselage (body) - white/cream */}
|
||||
<rect x="8" y="12" width="28" height="8" fill="#F5F5F5" />
|
||||
<rect x="6" y="14" width="4" height="4" fill="#E8E8E8" />
|
||||
<rect x="34" y="12" width="6" height="6" fill="#F5F5F5" />
|
||||
|
||||
{/* Nose cone */}
|
||||
<rect x="38" y="14" width="4" height="4" fill="#E8E8E8" />
|
||||
<rect x="40" y="15" width="3" height="2" fill="#D0D0D0" />
|
||||
|
||||
{/* Cockpit windows - blue */}
|
||||
<rect x="30" y="13" width="6" height="3" fill="#87CEEB" />
|
||||
<rect x="31" y="13" width="2" height="2" fill="#5CB8E8" />
|
||||
<rect x="34" y="13" width="2" height="2" fill="#5CB8E8" />
|
||||
|
||||
{/* High wing (Cessna style - wing on top) */}
|
||||
<rect x="16" y="8" width="20" height="4" fill="#F5F5F5" />
|
||||
<rect x="14" y="9" width="4" height="2" fill="#E8E8E8" />
|
||||
<rect x="34" y="9" width="4" height="2" fill="#E8E8E8" />
|
||||
{/* Wing strut */}
|
||||
<rect x="22" y="12" width="2" height="2" fill="#888" />
|
||||
<rect x="28" y="12" width="2" height="2" fill="#888" />
|
||||
|
||||
{/* Red stripe on fuselage */}
|
||||
<rect x="10" y="16" width="26" height="2" fill="#E74C3C" />
|
||||
|
||||
{/* Tail section */}
|
||||
<rect x="4" y="10" width="6" height="2" fill="#F5F5F5" />
|
||||
<rect x="2" y="8" width="4" height="4" fill="#F5F5F5" />
|
||||
<rect x="0" y="6" width="4" height="4" fill="#E8E8E8" />
|
||||
{/* Horizontal stabilizer */}
|
||||
<rect x="2" y="18" width="8" height="2" fill="#F5F5F5" />
|
||||
|
||||
{/* Propeller hub */}
|
||||
<rect x="43" y="14" width="3" height="4" fill="#444" />
|
||||
|
||||
{/* Animated propeller */}
|
||||
<g style={{ transformOrigin: "45px 16px", animation: "spin 0.1s linear infinite" }}>
|
||||
<rect x="44" y="8" width="2" height="6" fill="#666" />
|
||||
<rect x="44" y="18" width="2" height="6" fill="#666" />
|
||||
</g>
|
||||
|
||||
{/* Landing gear */}
|
||||
<rect x="18" y="20" width="2" height="4" fill="#555" />
|
||||
<rect x="16" y="24" width="6" height="2" fill="#333" />
|
||||
<rect x="30" y="20" width="2" height="4" fill="#555" />
|
||||
<rect x="28" y="24" width="6" height="2" fill="#333" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ import {
|
||||
PixelFlower,
|
||||
PixelStar,
|
||||
PixelRobot,
|
||||
PixelLevelNode
|
||||
PixelLevelNode,
|
||||
PixelPlaneWithBanner,
|
||||
PixelLake,
|
||||
PixelPond,
|
||||
PixelCar,
|
||||
PixelOldCar,
|
||||
PixelVan
|
||||
} from "./pixel-art";
|
||||
|
||||
export function ProgressMap() {
|
||||
@@ -56,14 +62,22 @@ export function ProgressMap() {
|
||||
<div className="relative h-full flex flex-col">
|
||||
{/* Scroll controls for desktop */}
|
||||
<div className="hidden md:flex absolute left-2 top-1/2 -translate-y-1/2 z-20">
|
||||
<Button variant="secondary" size="icon" onClick={scrollLeft} className="rounded-full shadow-lg">
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<button
|
||||
onClick={scrollLeft}
|
||||
className="w-10 h-10 bg-[#FFD700] border-3 border-[#DAA520] flex items-center justify-center hover:bg-[#FFC000] transition-colors shadow-lg"
|
||||
style={{ clipPath: "polygon(0 4px, 4px 4px, 4px 0, calc(100% - 4px) 0, calc(100% - 4px) 4px, 100% 4px, 100% calc(100% - 4px), calc(100% - 4px) calc(100% - 4px), calc(100% - 4px) 100%, 4px 100%, 4px calc(100% - 4px), 0 calc(100% - 4px))" }}
|
||||
>
|
||||
<ChevronLeft className="h-5 w-5 text-[#8B4513]" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="hidden md:flex absolute right-2 top-1/2 -translate-y-1/2 z-20">
|
||||
<Button variant="secondary" size="icon" onClick={scrollRight} className="rounded-full shadow-lg">
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
</Button>
|
||||
<button
|
||||
onClick={scrollRight}
|
||||
className="w-10 h-10 bg-[#FFD700] border-3 border-[#DAA520] flex items-center justify-center hover:bg-[#FFC000] transition-colors shadow-lg"
|
||||
style={{ clipPath: "polygon(0 4px, 4px 4px, 4px 0, calc(100% - 4px) 0, calc(100% - 4px) 4px, 100% 4px, 100% calc(100% - 4px), calc(100% - 4px) calc(100% - 4px), calc(100% - 4px) 100%, 4px 100%, 4px calc(100% - 4px), 0 calc(100% - 4px))" }}
|
||||
>
|
||||
<ChevronRight className="h-5 w-5 text-[#8B4513]" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Horizontal scrolling map container */}
|
||||
@@ -83,32 +97,48 @@ export function ProgressMap() {
|
||||
<div className="absolute bottom-12 left-0 right-0 h-4 bg-[#228B22]" style={{ minWidth: `${mapWidth}px` }} />
|
||||
<div className="absolute bottom-16 left-0 right-0 h-2 bg-[#32CD32]" style={{ minWidth: `${mapWidth}px` }} />
|
||||
|
||||
{/* Road with dashed center line */}
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-8 bg-[#4A4A4A]"
|
||||
style={{ minWidth: `${mapWidth}px` }}
|
||||
>
|
||||
{/* Road edges */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-[#6B6B6B]" />
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-[#3A3A3A]" />
|
||||
{/* Dashed center line */}
|
||||
<div
|
||||
className="absolute top-1/2 -translate-y-1/2 left-0 right-0 h-1.5 bg-repeat-x"
|
||||
style={{
|
||||
backgroundImage: "repeating-linear-gradient(90deg, #FFD700 0px, #FFD700 20px, transparent 20px, transparent 40px)",
|
||||
minWidth: `${mapWidth}px`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Pixel art path connecting all levels */}
|
||||
<svg
|
||||
className="absolute inset-0 w-full h-full pointer-events-none z-0"
|
||||
style={{ width: `${mapWidth}px` }}
|
||||
style={{ width: `${mapWidth}px`, imageRendering: "pixelated" }}
|
||||
preserveAspectRatio="none"
|
||||
shapeRendering="crispEdges"
|
||||
>
|
||||
{/* Draw dotted path between levels - pixel style */}
|
||||
{/* Path border for depth */}
|
||||
<path
|
||||
d={generatePathD(levelPositions)}
|
||||
d={generatePixelPathD(levelPositions)}
|
||||
fill="none"
|
||||
stroke="#D4A574"
|
||||
stroke="#8B5A2B"
|
||||
strokeWidth="12"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="miter"
|
||||
strokeDasharray="16 8"
|
||||
/>
|
||||
{/* Path border for depth */}
|
||||
{/* Draw path between levels - pixel style */}
|
||||
<path
|
||||
d={generatePathD(levelPositions)}
|
||||
d={generatePixelPathD(levelPositions)}
|
||||
fill="none"
|
||||
stroke="#8B5A2B"
|
||||
strokeWidth="16"
|
||||
stroke="#D4A574"
|
||||
strokeWidth="8"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="miter"
|
||||
strokeDasharray="16 8"
|
||||
className="opacity-30"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -205,7 +235,60 @@ function generatePathD(positions: { x: number; y: number }[]): string {
|
||||
return d;
|
||||
}
|
||||
|
||||
// Generate pixelated path using straight line segments (stair-step pattern)
|
||||
function generatePixelPathD(positions: { x: number; y: number }[]): string {
|
||||
if (positions.length === 0) return "";
|
||||
|
||||
let d = `M ${positions[0].x} ${positions[0].y}`;
|
||||
|
||||
for (let i = 1; i < positions.length; i++) {
|
||||
const prev = positions[i - 1];
|
||||
const curr = positions[i];
|
||||
const midX = (prev.x + curr.x) / 2;
|
||||
// Create stair-step pattern for pixelated look
|
||||
d += ` H ${midX}`; // Horizontal to midpoint
|
||||
d += ` V ${curr.y}`; // Vertical to new Y
|
||||
d += ` H ${curr.x}`; // Horizontal to destination
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
function MapDecorations({ mapWidth }: { mapWidth: number }) {
|
||||
const [planeY, setPlaneY] = useState(15); // percentage from top
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const planeRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Handle plane dragging
|
||||
useEffect(() => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const handleMove = (clientY: number) => {
|
||||
const container = planeRef.current?.parentElement;
|
||||
if (!container) return;
|
||||
const rect = container.getBoundingClientRect();
|
||||
const y = ((clientY - rect.top) / rect.height) * 100;
|
||||
// Constrain between 8% and 35% from top
|
||||
setPlaneY(Math.max(8, Math.min(35, y)));
|
||||
};
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => handleMove(e.clientY);
|
||||
const onTouchMove = (e: TouchEvent) => handleMove(e.touches[0].clientY);
|
||||
const onEnd = () => setIsDragging(false);
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('touchmove', onTouchMove);
|
||||
document.addEventListener('mouseup', onEnd);
|
||||
document.addEventListener('touchend', onEnd);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('touchmove', onTouchMove);
|
||||
document.removeEventListener('mouseup', onEnd);
|
||||
document.removeEventListener('touchend', onEnd);
|
||||
};
|
||||
}, [isDragging]);
|
||||
|
||||
// Generate decorations along the map - using seeded positions for consistency
|
||||
const decorations = [];
|
||||
const spacing = 100;
|
||||
@@ -221,7 +304,7 @@ function MapDecorations({ mapWidth }: { mapWidth: number }) {
|
||||
className="absolute pointer-events-none"
|
||||
style={{
|
||||
left: `${x + (i % 3) * 15}px`,
|
||||
bottom: `${55 + yOffset + (i % 4) * 5}px`
|
||||
bottom: `${50 + yOffset + (i % 4) * 5}px`
|
||||
}}
|
||||
>
|
||||
{type === 0 && <PixelTree />}
|
||||
@@ -233,7 +316,30 @@ function MapDecorations({ mapWidth }: { mapWidth: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
// Add castle at the end
|
||||
// Add lakes and ponds scattered on the ground
|
||||
const lakePositions = [
|
||||
{ x: 210, type: 'lake' },
|
||||
{ x: 1000, type: 'pond' },
|
||||
{ x: 2180, type: 'lake' },
|
||||
{ x: 2920, type: 'pond' },
|
||||
{ x: 3680, type: 'lake' },
|
||||
];
|
||||
|
||||
lakePositions.forEach((lake, i) => {
|
||||
if (lake.x < mapWidth - 100) {
|
||||
decorations.push(
|
||||
<div
|
||||
key={`lake-${i}`}
|
||||
className="absolute pointer-events-none"
|
||||
style={{ left: `${lake.x}px`, bottom: "50px" }}
|
||||
>
|
||||
{lake.type === 'lake' ? <PixelLake /> : <PixelPond />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Add castle at the end
|
||||
decorations.push(
|
||||
<div
|
||||
key="castle"
|
||||
@@ -244,6 +350,116 @@ function MapDecorations({ mapWidth }: { mapWidth: number }) {
|
||||
</div>
|
||||
);
|
||||
|
||||
// Add cars on the road with hover traffic light
|
||||
// Car 1 - going left (top lane)
|
||||
decorations.push(
|
||||
<div
|
||||
key="car1"
|
||||
className="absolute z-[2] group"
|
||||
style={{
|
||||
bottom: "7px",
|
||||
transform: "scaleX(-1)", // Flip to face left
|
||||
animation: `driveLeft ${Math.max(10, mapWidth / 200)}s linear infinite`,
|
||||
["--map-width" as string]: `${mapWidth}px`,
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.animationPlayState = "paused"}
|
||||
onMouseLeave={(e) => e.currentTarget.style.animationPlayState = "running"}
|
||||
>
|
||||
{/* Traffic light - appears on hover, in front of car */}
|
||||
<div className="absolute -right-8 -bottom-2 opacity-0 group-hover:opacity-100 transition-opacity flex flex-col items-center" style={{ transform: "scaleX(-1)" }}>
|
||||
{/* Light box */}
|
||||
<div className="w-4 h-10 bg-gray-700 flex flex-col items-center justify-center gap-0.5 p-1">
|
||||
<div className="w-2 h-2 bg-red-500 shadow-[0_0_6px_#ef4444]" />
|
||||
<div className="w-2 h-2 bg-yellow-800" />
|
||||
<div className="w-2 h-2 bg-green-800" />
|
||||
</div>
|
||||
{/* Pole */}
|
||||
<div className="w-1.5 h-8 bg-gray-500" />
|
||||
</div>
|
||||
<PixelCar color="#E74C3C" />
|
||||
</div>
|
||||
);
|
||||
|
||||
// Van - going left (top lane, different timing)
|
||||
decorations.push(
|
||||
<div
|
||||
key="van1"
|
||||
className="absolute z-[2] group"
|
||||
style={{
|
||||
bottom: "0px",
|
||||
transform: "scaleX(-1)", // Flip to face left
|
||||
animation: `driveLeft ${Math.max(18, mapWidth / 70)}s linear infinite`,
|
||||
animationDelay: "-8s",
|
||||
["--map-width" as string]: `${mapWidth}px`,
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.animationPlayState = "paused"}
|
||||
onMouseLeave={(e) => e.currentTarget.style.animationPlayState = "running"}
|
||||
>
|
||||
{/* Traffic light - appears on hover, in front of van */}
|
||||
<div className="absolute -right-8 -bottom-2 opacity-0 group-hover:opacity-100 transition-opacity flex flex-col items-center" style={{ transform: "scaleX(-1)" }}>
|
||||
{/* Light box */}
|
||||
<div className="w-4 h-10 bg-gray-700 flex flex-col items-center justify-center gap-0.5 p-1">
|
||||
<div className="w-2 h-2 bg-red-500 shadow-[0_0_6px_#ef4444]" />
|
||||
<div className="w-2 h-2 bg-yellow-800" />
|
||||
<div className="w-2 h-2 bg-green-800" />
|
||||
</div>
|
||||
{/* Pole */}
|
||||
<div className="w-1.5 h-8 bg-gray-500" />
|
||||
</div>
|
||||
<div style={{ animation: "engineVibrate 0.1s steps(2) infinite" }}>
|
||||
<PixelVan text="prompts.chat" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Car 2 - going right (bottom lane)
|
||||
decorations.push(
|
||||
<div
|
||||
key="car2"
|
||||
className="absolute z-[1] group"
|
||||
style={{
|
||||
bottom: "22px",
|
||||
animation: `driveRight ${Math.max(12, mapWidth / 90)}s linear infinite`,
|
||||
["--map-width" as string]: `${mapWidth}px`,
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.animationPlayState = "paused"}
|
||||
onMouseLeave={(e) => e.currentTarget.style.animationPlayState = "running"}
|
||||
>
|
||||
{/* Traffic light - appears on hover, on right side of car (going right) */}
|
||||
<div className="absolute -right-8 bottom-2 opacity-0 group-hover:opacity-100 transition-opacity flex flex-col items-center">
|
||||
{/* Light box */}
|
||||
<div className="w-4 h-10 bg-gray-700 flex flex-col items-center justify-center gap-0.5 p-1">
|
||||
<div className="w-2 h-2 bg-red-500 shadow-[0_0_6px_#ef4444]" />
|
||||
<div className="w-2 h-2 bg-yellow-800" />
|
||||
<div className="w-2 h-2 bg-green-800" />
|
||||
</div>
|
||||
{/* Pole */}
|
||||
<div className="w-1.5 h-8 bg-gray-500" />
|
||||
</div>
|
||||
<div style={{ animation: "engineVibrate 0.1s steps(2) infinite" }}>
|
||||
<PixelOldCar />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Add flying plane with banner - flies across full map width, draggable up/down
|
||||
decorations.push(
|
||||
<div
|
||||
key="plane"
|
||||
ref={planeRef}
|
||||
className="absolute z-10 cursor-grab active:cursor-grabbing select-none"
|
||||
style={{
|
||||
top: `${planeY}%`,
|
||||
animation: `flyPlaneMap ${Math.max(20, mapWidth / 50)}s linear infinite`,
|
||||
["--map-width" as string]: `${mapWidth}px`,
|
||||
}}
|
||||
onMouseDown={() => setIsDragging(true)}
|
||||
onTouchStart={() => setIsDragging(true)}
|
||||
>
|
||||
<PixelPlaneWithBanner bannerText="prompts.chat" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return <>{decorations}</>;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ export function SettingsButton() {
|
||||
);
|
||||
}
|
||||
|
||||
// Pixel clip-paths for consistent styling
|
||||
const pixelClipPath = "polygon(0 8px, 8px 8px, 8px 0, calc(100% - 8px) 0, calc(100% - 8px) 8px, 100% 8px, 100% calc(100% - 8px), calc(100% - 8px) calc(100% - 8px), calc(100% - 8px) 100%, 8px 100%, 8px calc(100% - 8px), 0 calc(100% - 8px))";
|
||||
const smallPixelClipPath = "polygon(0 4px, 4px 4px, 4px 0, calc(100% - 4px) 0, calc(100% - 4px) 4px, 100% 4px, 100% calc(100% - 4px), calc(100% - 4px) calc(100% - 4px), calc(100% - 4px) 100%, 4px 100%, 4px calc(100% - 4px), 0 calc(100% - 4px))";
|
||||
|
||||
function SettingsModal({ onClose }: { onClose: () => void }) {
|
||||
const t = useTranslations("kids.settings");
|
||||
const currentLocale = useLocale();
|
||||
@@ -95,11 +99,15 @@ function SettingsModal({ onClose }: { onClose: () => void }) {
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative bg-[#FEF3C7] border-4 border-[#8B4513] rounded-xl p-6 w-full max-w-md max-h-[90vh] overflow-y-auto animate-in zoom-in-95 fade-in duration-200">
|
||||
<div
|
||||
className="relative bg-[#FEF3C7] border-4 border-[#8B4513] p-6 w-full max-w-md max-h-[90vh] overflow-y-auto animate-in zoom-in-95 fade-in duration-200"
|
||||
style={{ clipPath: pixelClipPath }}
|
||||
>
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-3 right-3 p-2 text-[#8B4513] hover:bg-[#8B4513]/10 rounded-lg"
|
||||
className="absolute top-3 right-3 p-2 text-[#8B4513] hover:bg-[#8B4513]/10"
|
||||
style={{ clipPath: smallPixelClipPath }}
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
@@ -145,27 +153,31 @@ function SettingsModal({ onClose }: { onClose: () => void }) {
|
||||
<Globe className="w-5 h-5" />
|
||||
{t("language")}
|
||||
</h3>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{SUPPORTED_LOCALES.map((locale) => (
|
||||
<button
|
||||
key={locale.code}
|
||||
onClick={() => handleLanguageChange(locale.code)}
|
||||
className={cn(
|
||||
"p-2 rounded-lg border-2 text-sm font-medium transition-all",
|
||||
"p-2 border-2 text-sm font-medium transition-all flex items-center gap-2",
|
||||
currentLocale === locale.code
|
||||
? "bg-[#8B4513] border-[#5D4037] text-white"
|
||||
: "bg-white border-[#D4A574] text-[#5D4037] hover:border-[#8B4513]"
|
||||
)}
|
||||
style={{ clipPath: smallPixelClipPath }}
|
||||
>
|
||||
<span className="text-lg">{locale.flag}</span>
|
||||
<div className="text-xs mt-1">{locale.label}</div>
|
||||
<span className="text-xs">{locale.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Info */}
|
||||
<div className="mb-6 p-4 bg-white/50 rounded-lg border-2 border-[#D4A574]">
|
||||
<div
|
||||
className="mb-6 p-4 bg-white/50 border-2 border-[#D4A574]"
|
||||
style={{ clipPath: smallPixelClipPath }}
|
||||
>
|
||||
<h3 className="text-lg font-bold text-[#5D4037] mb-2">
|
||||
{t("progress")}
|
||||
</h3>
|
||||
@@ -189,7 +201,10 @@ function SettingsModal({ onClose }: { onClose: () => void }) {
|
||||
</h3>
|
||||
|
||||
{resetComplete ? (
|
||||
<div className="p-3 bg-green-100 border-2 border-green-500 rounded-lg text-green-700 font-medium flex items-center gap-2">
|
||||
<div
|
||||
className="p-3 bg-green-100 border-2 border-green-500 text-green-700 font-medium flex items-center gap-2"
|
||||
style={{ clipPath: smallPixelClipPath }}
|
||||
>
|
||||
<Check className="w-5 h-5" />
|
||||
{t("resetComplete")}
|
||||
</div>
|
||||
@@ -198,16 +213,18 @@ function SettingsModal({ onClose }: { onClose: () => void }) {
|
||||
<p className="text-red-600 font-medium text-sm">
|
||||
{t("resetWarning")}
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
onClick={handleResetProgress}
|
||||
className="flex-1 py-2 px-4 bg-red-500 hover:bg-red-600 text-white font-bold rounded-lg"
|
||||
className="w-full py-2 px-4 bg-red-500 hover:bg-red-600 text-white font-bold"
|
||||
style={{ clipPath: smallPixelClipPath }}
|
||||
>
|
||||
{t("resetConfirm")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowResetConfirm(false)}
|
||||
className="flex-1 py-2 px-4 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold rounded-lg"
|
||||
className="w-full py-2 px-4 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold"
|
||||
style={{ clipPath: smallPixelClipPath }}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
@@ -216,7 +233,8 @@ function SettingsModal({ onClose }: { onClose: () => void }) {
|
||||
) : (
|
||||
<button
|
||||
onClick={handleResetProgress}
|
||||
className="w-full py-2 px-4 bg-red-100 hover:bg-red-200 text-red-700 font-bold rounded-lg border-2 border-red-300"
|
||||
className="w-full py-2 px-4 bg-red-100 hover:bg-red-200 text-red-700 font-bold border-2 border-red-300"
|
||||
style={{ clipPath: smallPixelClipPath }}
|
||||
>
|
||||
{t("resetButton")}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user