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
6 changes: 4 additions & 2 deletions app/(core)/components/P5Wrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ export default function P5Wrapper({ sketch, simInfos }) {
}, [sketch]);

return (
<div className="p5-wrapper">
<div ref={containerRef} className="screen" id="Screen">
<div className="p5-wrapper" style={{ position: 'relative' }}>
<div ref={containerRef} className="screen" id="Screen"></div>
{/* Render simInfos as a sibling so it doesn't sit inside the canvas container and intercept events */}
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
{simInfos ?? ""}
</div>
</div>
Expand Down
154 changes: 83 additions & 71 deletions app/(core)/components/Theme.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from "react";
"use client";

import React, { useEffect, useState } from "react";
import { motion } from "motion/react";

interface Props {
Expand All @@ -8,84 +10,94 @@ interface Props {

export const Theme = ({ mode, onToggle }: Props) => {
const isLight = mode === 'light';
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

// To avoid hydration mismatch, do not depend on client-only values
// for attributes that were rendered on the server. Defer animations/style
// changes until after hydration by gating them on `mounted`.
const transformStyle = mounted ? (isLight ? "rotate(90deg)" : "rotate(40deg)") : undefined;

return (
<button
className={`grid size-11 place-items-center cursor-pointer rounded-xl bg-gradient-to-t shadow-lg ${isLight ? "from-[#f8fafc] to-[#f1f5f9] text-stone-950" : "from-[#020617] to-[#0F172A] text-stone-50"}`}
onClick={onToggle}
aria-label="Toggle theme"
>
<svg
viewBox="0 0 18 18"
style={{ transform: isLight ? "rotate(90deg)" : "rotate(40deg)" }}
className="size-6 overflow-visible transition-transform duration-500"
className={`grid size-11 place-items-center cursor-pointer rounded-xl bg-gradient-to-t shadow-lg ${isLight ? "from-[#f8fafc] to-[#f1f5f9] text-stone-950" : "from-[#020617] to-[#0F172A] text-stone-50"}`}
onClick={onToggle}
aria-label="Toggle theme"
>
<mask id="moon-mask-main-nav">
<rect x="0" y="0" width="18" height="18" fill="#FFF" />
<motion.circle
animate={{ cx: isLight ? 25 : 10 }}
cy="2"
r="8"
fill="black"
/>
</mask>
<motion.circle
cx="9"
cy="9"
fill="currentColor"
mask="url(#moon-mask-main-nav)"
animate={{ r: isLight ? 5 : 8 }}
/>
<g>
<svg
viewBox="0 0 18 18"
style={transformStyle ? { transform: transformStyle } : undefined}
className="size-6 overflow-visible transition-transform duration-500"
>
<mask id="moon-mask-main-nav">
<rect x="0" y="0" width="18" height="18" fill="#FFF" />
<motion.circle
animate={mounted ? { cx: isLight ? 25 : 10 } : {}}
cy="2"
r="8"
fill="black"
/>
</mask>
<motion.circle
cx="17"
cx="9"
cy="9"
r="1.5"
fill="currentColor"
animate={{ scale: isLight ? 1 : 0 }}
/>
<motion.circle
cx="13"
cy="15.928203230275509"
r="1.5"
fill="currentColor"
animate={{ scale: isLight ? 1 : 0 }}
transition={{ delay: isLight ? 0.05 : 0 }}
/>
<motion.circle
cx="5.000000000000002"
cy="15.92820323027551"
r="1.5"
fill="currentColor"
animate={{ scale: isLight ? 1 : 0 }}
transition={{ delay: isLight ? 0.1 : 0 }}
/>
<motion.circle
cx="1"
cy="9.000000000000002"
r="1.5"
fill="currentColor"
animate={{ scale: isLight ? 1 : 0 }}
transition={{ delay: isLight ? 0.15 : 0 }}
/>
<motion.circle
cx="4.9999999999999964"
cy="2.071796769724492"
r="1.5"
fill="currentColor"
animate={{ scale: isLight ? 1 : 0 }}
transition={{ delay: isLight ? 0.2 : 0 }}
/>
<motion.circle
cx="13"
cy="2.0717967697244912"
r="1.5"
fill="currentColor"
animate={{ scale: isLight ? 1 : 0 }}
transition={{ delay: isLight ? 0.25 : 0 }}
mask="url(#moon-mask-main-nav)"
animate={mounted ? { r: isLight ? 5 : 8 } : {}}
/>
</g>
</svg>
<g>
<motion.circle
cx="17"
cy="9"
r="1.5"
fill="currentColor"
animate={mounted ? { scale: isLight ? 1 : 0 } : {}}
/>
<motion.circle
cx="13"
cy="15.928203230275509"
r="1.5"
fill="currentColor"
animate={mounted ? { scale: isLight ? 1 : 0 } : {}}
transition={mounted ? { delay: isLight ? 0.05 : 0 } : undefined}
/>
<motion.circle
cx="5.000000000000002"
cy="15.92820323027551"
r="1.5"
fill="currentColor"
animate={mounted ? { scale: isLight ? 1 : 0 } : {}}
transition={mounted ? { delay: isLight ? 0.1 : 0 } : undefined}
/>
<motion.circle
cx="1"
cy="9.000000000000002"
r="1.5"
fill="currentColor"
animate={mounted ? { scale: isLight ? 1 : 0 } : {}}
transition={mounted ? { delay: isLight ? 0.15 : 0 } : undefined}
/>
<motion.circle
cx="4.9999999999999964"
cy="2.071796769724492"
r="1.5"
fill="currentColor"
animate={mounted ? { scale: isLight ? 1 : 0 } : {}}
transition={mounted ? { delay: isLight ? 0.2 : 0 } : undefined}
/>
<motion.circle
cx="13"
cy="2.0717967697244912"
r="1.5"
fill="currentColor"
animate={mounted ? { scale: isLight ? 1 : 0 } : {}}
transition={mounted ? { delay: isLight ? 0.25 : 0 } : undefined}
/>
</g>
</svg>
</button>
);
};
39 changes: 39 additions & 0 deletions app/(core)/data/chapters.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,45 @@ function draw(){
],
},
},
{
id: 8,
name: "Magnetic Field",
desc: "Charged particle motion in a uniform magnetic field (B out-of-plane).",
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description states "Charged particle motion in a uniform magnetic field (B out-of-plane)" but the simulation does not actually implement particle motion. The description should be updated to accurately reflect that this simulation visualizes magnetic dipole field lines and allows manipulation of the rod orientation, not charged particle motion.

Copilot uses AI. Check for mistakes.
link: "/simulations/MagneticField",
tags: [TAGS.MEDIUM, TAGS.PHYSICS],
icon: "/icons/magnetic.png",
theory: {
sections: [
{
title: "Introduction",
blocks: [
{
type: "paragraph",
text: "A charged particle moving in a uniform magnetic field experiences the Lorentz force, producing circular motion when the velocity is perpendicular to the field. Use the draggable particle to set initial velocity and observe the resulting trajectory.",
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The introduction text mentions "Use the draggable particle to set initial velocity and observe the resulting trajectory" but there is no particle functionality implemented in this simulation. The text should be updated to describe what users can actually do: drag the rod to change orientation, shift+drag to move it, and toggle field lines.

Suggested change
text: "A charged particle moving in a uniform magnetic field experiences the Lorentz force, producing circular motion when the velocity is perpendicular to the field. Use the draggable particle to set initial velocity and observe the resulting trajectory.",
text: "A charged particle moving in a uniform magnetic field experiences the Lorentz force, producing circular motion when the velocity is perpendicular to the field. In this simulation, you can drag the rod to change its orientation, hold Shift while dragging to move it, and use the controls to toggle the visibility of magnetic field lines.",

Copilot uses AI. Check for mistakes.
},
],
},
{
title: "Dipole field & right-hand rule",
blocks: [
{
type: "paragraph",
text: "A magnetic dipole (like a small bar magnet) produces a field that loops from the north to the south pole. Field lines emerge from the north side, curve through space, and return to the south side. Use the controls to change the dipole moment and orientation.",
},
{
type: "list",
ordered: false,
items: [
"Magnetic dipole field (far-field approximation): B(r) = (μ0/4π) [ (3 r̂ (m·r̂) - m) / r^3 ]",
"Right-hand rule: point your thumb along the dipole moment (m); your curled fingers show the circulation direction of the magnetic field.",
"Field lines are strongest near the poles and decay roughly as 1/r^3 in the far field.",
],
},
],
},
],
},
},
{
id: 2,
name: "Vector Operations",
Expand Down
33 changes: 33 additions & 0 deletions app/(core)/data/configs/MagneticField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { EARTH_G_SI } from "../../constants/Config.js";

Comment on lines +1 to +2
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import EARTH_G_SI.

Suggested change
import { EARTH_G_SI } from "../../constants/Config.js";

Copilot uses AI. Check for mistakes.
export const INITIAL_INPUTS = {
rodLength: 1.0,
moment: 1.0,
angle: 0,
rodX: 0,
rodY: 0,
showFieldLines: true,
// field-line density is fixed now; simplify inputs
// (numSeeds and seedRadius removed for simplicity)
trailEnabled: true,
};

export const INPUT_FIELDS = [
{ name: "rodLength", label: "Rod length (m)", type: "number" },
{ name: "moment", label: "Dipole moment (A·m²)", type: "number" },
{ name: "angle", label: "Angle (°)", type: "number" },
{ name: "rodX", label: "Rod X (m)", type: "number" },
{ name: "rodY", label: "Rod Y (m)", type: "number" },
{ name: "showFieldLines", label: "Show field lines", type: "checkbox" },
];

export const SimInfoMapper = (state, context, refs) => {
const { pos, vel } = state || {};
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable vel.

Suggested change
const { pos, vel } = state || {};
const { pos } = state || {};

Copilot uses AI. Check for mistakes.
const m = context?.moment ?? 0;
const angle = context?.angle ?? 0;
return {
"m (dipole)": `${Number.isFinite(m) ? m.toFixed(3) : "0.000"} A·m²`,
"θ (rod angle)": `${Number.isFinite(angle) ? angle.toFixed(2) : "0.00"} °`,
"s(x,y)": `(${pos?.x?.toFixed?.(2) ?? "0.00"}, ${pos?.y?.toFixed?.(2) ?? "0.00"}) m`,
};
};
Loading