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
113 changes: 113 additions & 0 deletions app/(core)/components/FunFactSlider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useEffect, useState } from "react";
import { Lightbulb, X } from "lucide-react";

export default function FunFactSlider({ fact }) {
const [visible, setVisible] = useState(false);
const [render, setRender] = useState(false);

useEffect(() => {
if (!fact) return;

const renderTimer = setTimeout(() => {
setRender(true);
}, 0);

const showTimer = setTimeout(() => {
setVisible(true);
}, 50);

const hideTimer = setTimeout(() => {
setVisible(false);
}, 5000);

const removeTimer = setTimeout(() => {
setRender(false);
}, 5800);

return () => {
clearTimeout(renderTimer);
clearTimeout(showTimer);
clearTimeout(hideTimer);
clearTimeout(removeTimer);
};
}, [fact]);

if (!render) return null;

return (
<div
style={{ borderRadius: "var(--border-radius)" }}
className={`
relative w-full max-w-[320px]
rounded-xl border border-cyan-400/30
bg-[#0b2529]/80 backdrop-blur-2xl
p-4 shadow-[0_0_40px_rgba(34,211,238,0.1)]
group overflow-hidden
transition-all duration-700 ease-[cubic-bezier(0.23,1,0.32,1)] mt-8
${visible ? "opacity-100 translate-x-0 scale-100" : "opacity-0 translate-x-12 scale-95 "}
`}
>
{/* Cyber-Grid Background Detail */}
<div className="absolute inset-0 opacity-[0.03] pointer-events-none bg-[linear-gradient(to_right,#808080_1px,transparent_1px),linear-gradient(to_bottom,#808080_1px,transparent_1px)] bg-[size:14px_24px]" />

{/* Top Glow/Reflection */}
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-cyan-400/50 to-transparent" />

{/* Corner Accent Decor */}
<div className="absolute top-0 left-0 w-2 h-2 border-t border-l border-cyan-400/60 rounded-tl-xl" />

<div className="flex items-start gap-4 relative z-10">
{/* Icon Container with Layered Glow */}
<div className="flex-shrink-0 relative">
<div className="absolute inset-0 rounded-lg bg-cyan-400/20 blur-sm animate-pulse" />
<div className="relative flex items-center justify-center w-9 h-9 rounded-lg bg-[#0b3a3f] border border-cyan-400/40 shadow-inner">
<Lightbulb
size={18}
className="text-cyan-300 drop-shadow-[0_0_8px_rgba(34,211,238,0.8)]"
/>
</div>
</div>

{/* Text Content */}
<div className="flex-1 min-w-0 pt-0.5 mt-9px">
<div className="flex items-center gap-2 mb-1.5">
<div className="w-1 h-1 bg-cyan-400 rounded-full animate-ping" />
<p className="text-cyan-400/70 text-[10px] tracking-[0.25em] uppercase font-black font-extrabold">
Do you know?
</p>
</div>
<div className="toast-message p-4">
<p className="text-cyan-50/90 text-[13px] leading-relaxed font-medium">
<span className="text-cyan-400/50 mr-1 italic">β€œ</span>
{fact}
<span className="text-cyan-400/50 ml-1 italic">”</span>
</p>
</div>
</div>

{/* Close Button */}
<button
onClick={() => setVisible(false)}
className={`
flex-shrink-0 -mt-1 -mr-1 p-2
rounded-lg border border-transparent
hover:border-cyan-400/30 hover:bg-cyan-400/10
hover:shadow-[0_0_15px_rgba(34,211,238,0.2)]
transition-all duration-300 group/btn
`}
>
<X
size={15}
className="
text-cyan-400/50
group-hover/btn:text-cyan-200
group-hover/btn:rotate-90
group-hover/btn:scale-110
transition-all duration-300
"
/>
</button>
</div>
</div>
);
}
40 changes: 31 additions & 9 deletions app/(core)/components/TopSim.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,57 @@ import Back from "./Back";
import simulations from "../data/chapters";
import { usePathname } from "next/navigation";
import useMobile from "../hooks/useMobile";
import { useEffect, useState } from "react";
import FunFactSlider from "../components/FunFactSlider";

export default function TopSim() {
const location = usePathname();
const idx = simulations.findIndex((sim) => sim.link === location);
const isMobile = useMobile();

const [fact, setFact] = useState<string | null>(null);

function getPrevious() {
if (idx === -1) return "/";
const prevIndex = (idx - 1 + simulations.length) % simulations.length;
return simulations[prevIndex].link;
return simulations[(idx - 1 + simulations.length) % simulations.length]
.link;
}

function getNext() {
if (idx === -1) return "/";
const nextIndex = (idx + 1) % simulations.length;
return simulations[nextIndex].link;
return simulations[(idx + 1) % simulations.length].link;
}

function getCurrentName() {
if (idx === -1) return "";
return `${simulations[idx].id} - ${simulations[idx].name}`;
}

useEffect(() => {
const funFacts = simulations[idx]?.funFacts ?? [];

const timer = setTimeout(() => {
if (funFacts.length === 0) {
setFact(null);
return;
}

const randomIndex = Math.floor(Math.random() * funFacts.length);
setFact(funFacts[randomIndex]);
}, 0);

return () => clearTimeout(timer);
}, [idx]);

return (
<div className="top-nav-sim">
{!isMobile && (
<div className="top-nav-sim-back-to-home-wrapper">
<Back link="/simulations" />
</div>
)}
<div
className="top-nav-sim-inner"
style={{ maxWidth: isMobile ? "100%" : "80%" }}
>

<div className="top-nav-sim-inner">
<Back
link={getPrevious()}
type="responsive"
Expand All @@ -50,7 +67,12 @@ export default function TopSim() {
content="Next"
/>
</div>
{!isMobile && <div className="top-nav-sim-filler" />}

{!isMobile && (
<div className="fun-fact-slider-wrapper">
<FunFactSlider fact={fact} />
</div>
)}
</div>
);
}
43 changes: 31 additions & 12 deletions app/(core)/data/chapters.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,22 @@ const chapters = [
tags: [TAGS.EASY, TAGS.KINEMATICS, TAGS.COLLISION],
icon: "/icons/bouncingBall.png",
relatedBlogSlug: "physics-bouncing-ball-comprehensive-educational-guide",
funFacts: [
"No real ball bounces forever because energy is lost as heat and sound.",
],
},

{
id: 2,
name: "Vector Operations Calculator",
desc: "Interactive 2D vector operations tool. Visualize vector addition, subtraction, and dot products in real-time with our physics calculator.",
desc: "Interactive 2D vector operations tool.",
link: "/simulations/VectorsOperations",
tags: [TAGS.EASY, TAGS.MATH, TAGS.VECTORS, TAGS.TRIGONOMETRY],
icon: "/icons/vector.png",
relatedBlogSlug: "operations-with-vectors",
funFacts: ["Dot product helps determine the angle between two vectors."],
},

{
id: 3,
name: "Ball Acceleration",
Expand All @@ -27,7 +33,9 @@ const chapters = [
tags: [TAGS.MEDIUM, TAGS.KINEMATICS, TAGS.ACCELERATION, TAGS.INTERACTIVE],
icon: "/icons/acceleration.png",
relatedBlogSlug: "ball-uniformly-accelerated-motion",
funFacts: ["Acceleration happens whenever velocity changes."],
},

{
id: 4,
name: "Ball Gravity",
Expand All @@ -36,49 +44,60 @@ const chapters = [
tags: [TAGS.MEDIUM, TAGS.DYNAMICS, TAGS.GRAVITY, TAGS.COLLISION],
icon: "/icons/gravity.png",
relatedBlogSlug: "ball-free-fall-comprehensive-guide",
funFacts: [
"All objects fall at the same rate in a vacuum, regardless of mass.",
],
},

{
id: 5,
name: "Spring Mass System Simulator",
desc: "Explore Hooke's Law and Simple Harmonic Motion. Adjust mass and spring constants to visualize oscillations and energy conservation.",
desc: "Explore Hooke's Law and Simple Harmonic Motion.",
link: "/simulations/SpringConnection",
tags: [TAGS.ADVANCED, TAGS.DYNAMICS, TAGS.SPRINGS, TAGS.OSCILLATIONS],
icon: "/icons/spring.png",
relatedBlogSlug: "spring-connection",
funFacts: [
"Hooke’s Law is valid only within the elastic limit of a spring.",
],
},

{
id: 6,
name: "Simple Pendulum Simulation",
desc: "Calculate the period and frequency of a pendulum. Visualize the relationship between length, gravity, and kinetic energy in real-time.",
desc: "Calculate the period and frequency of a pendulum.",
link: "/simulations/SimplePendulum",
tags: [TAGS.MEDIUM, TAGS.DYNAMICS, TAGS.OSCILLATIONS, TAGS.ENERGY],
icon: "/icons/pendulam.png",
relatedBlogSlug: "pendulum-motion",
funFacts: [
"For small angles, a pendulum’s period does not depend on mass.",
],
},

{
id: 7,
name: "Projectile & Parabolic Motion",
desc: "Simulate projectile motion under gravity. Predict trajectories, range, and maximum height with our interactive physics calculator.",
desc: "Simulate projectile motion under gravity.",
link: "/simulations/ParabolicMotion",
tags: [TAGS.MEDIUM, TAGS.KINEMATICS, TAGS.GRAVITY, TAGS.MATH],
icon: "/icons/parabola.png",
relatedBlogSlug: "projectile-parabolic-motion",
funFacts: [
"Maximum range occurs at a 45Β° launch angle without air resistance.",
],
},

{
id: 8,
name: "Inclined Plane",
desc: "Block sliding on an inclined plane.",
link: "/simulations/InclinedPlane",
tags: [TAGS.MEDIUM, TAGS.DYNAMICS, TAGS.FORCES, TAGS.FRICTION],
icon: "/icons/inclined.png",
},
{
id: 0,
name: "Test for benchmarks",
desc: "This is a simulation created specifically for performance testing.",
link: "/simulations/test",
tags: [TAGS.EXPERIMENTAL, TAGS.BENCHMARK, TAGS.PERFORMANCE],
icon: "/icons/test.png",
funFacts: [
"Inclined planes reduce the force needed to lift heavy objects.",
],
},
];

Expand Down
6 changes: 5 additions & 1 deletion app/(core)/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -1586,8 +1586,12 @@ footer {
width: 20%;
}

.top-nav-sim-filler {
.fun-fact-slider-wrapper {
width: 20%;
justify-content: center;
margin-left: auto;
margin-top: 20px;
margin-right: 55px;
}

/* Wind icon for Ball Gravity Simulation*/
Expand Down
23 changes: 23 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/(core)/styles/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
58 changes: 58 additions & 0 deletions components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);

export interface ButtonProps
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";

export { Button, buttonVariants };
Loading