diff --git a/photon-client/src/App.vue b/photon-client/src/App.vue
index 35fe75268..a1ab2b31d 100644
--- a/photon-client/src/App.vue
+++ b/photon-client/src/App.vue
@@ -76,16 +76,35 @@ onBeforeMount(() => {
-
-
-
-
-
![Quarky]()
-
-
+
+
+
+
![Quarky]()
+
-
-
+
-
diff --git a/photon-client/src/lib/quarky.js b/photon-client/src/lib/quarky.js
index 2dcfb252a..2ea0e4d42 100644
--- a/photon-client/src/lib/quarky.js
+++ b/photon-client/src/lib/quarky.js
@@ -1,35 +1,35 @@
-import IDLEGIF from '@/assets/images/idle.gif';
-import GROWGIF from '@/assets/images/grow.gif';
-import BLINKGIF from '@/assets/images/blink.gif';
-import WAVEGIF from '@/assets/images/wave.gif';
-import SPEAKGIF from '@/assets/images/speak.gif';
-import SHRINKGIF from '@/assets/images/shrink.gif';
-import POINTGIF from '@/assets/images/point.gif';
-import { useCameraSettingsStore } from '@/stores/settings/CameraSettingsStore';
-import { useStateStore } from '@/stores/StateStore';
+import IDLEGIF from "@/assets/images/idle.gif";
+import GROWGIF from "@/assets/images/grow.gif";
+import BLINKGIF from "@/assets/images/blink.gif";
+import WAVEGIF from "@/assets/images/wave.gif";
+import SPEAKGIF from "@/assets/images/speak.gif";
+import SHRINKGIF from "@/assets/images/shrink.gif";
+import POINTGIF from "@/assets/images/point.gif";
+import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
+import { useStateStore } from "@/stores/StateStore";
const ANIMATIONS = {
- idle: IDLEGIF,
- grow: GROWGIF,
- blink: BLINKGIF,
- wave: WAVEGIF,
- speak: SPEAKGIF,
- shrink: SHRINKGIF,
- point: POINTGIF
+ idle: IDLEGIF,
+ grow: GROWGIF,
+ blink: BLINKGIF,
+ wave: WAVEGIF,
+ speak: SPEAKGIF,
+ shrink: SHRINKGIF,
+ point: POINTGIF
};
// Extended animation sequence
const SEQUENCE = [
- { animation: 'grow', loops: 1 },
- { animation: 'idle', loops: 2 },
- { animation: 'blink', loops: 1 },
- { animation: 'idle', loops: 1 },
- { animation: 'speak', loops: 1 },
- { animation: 'idle', loops: 1 },
- { animation: 'point', loops: 1 },
- { animation: 'idle', loops: 1 },
- { animation: 'wave', loops: 1 },
- { animation: 'idle', loops: 1 },
+ { animation: "grow", loops: 1 },
+ { animation: "idle", loops: 2 },
+ { animation: "blink", loops: 1 },
+ { animation: "idle", loops: 1 },
+ { animation: "speak", loops: 1 },
+ { animation: "idle", loops: 1 },
+ { animation: "point", loops: 1 },
+ { animation: "idle", loops: 1 },
+ { animation: "wave", loops: 1 },
+ { animation: "idle", loops: 1 }
];
let quarkyImage;
@@ -40,149 +40,150 @@ let currentLoopCount = 0;
let isMovingDemo = false;
let hasPlayedGrow = false;
-
// Speech bubble text (configurable)
let quarkySpeechText = "Hello from Quarky!";
// Turbo-encabulator style nonsense phrases
const quarkyPhrases = [
- "Reverse phase oscillation detected!",
- "Initializing hyperflux capacitor...",
- "Reticulating splines in progress.",
- "Quantum entanglement buffer overflowzomg",
- "Did you remember the turbo-encabulator?",
- "Engaging magnetic flux inverter.",
- "Calibrating photon resonance field.",
- "Deploying recursive feedback loop.",
- "wow.",
- "I applaud your pseudo-random bitstream.",
- "Rebooting quantum foam stabilizer.",
- "Analyzing subspace harmonics.",
- "Transmitting encrypted flux packets.",
- "Verifying entropic phase alignment.",
- "Reconfiguring nano-particle array.",
- "pew pew. pew pew.",
- "I can't parse the synthetic logic matrix.",
- "Don't forget to make the holographic interface.",
- "You should generate more stochastic resonance.",
- "Greetings",
- "You look like you need some help!",
- "Set this slider to 25",
- "Set this slider to 67. HAHA 67!!!!",
- "That's a horrible choice!",
- "Fun is a core value! Is that a fun choice?",
- "If your grandma saw that choice, would she be proud?",
- "Chute Door?",
- "Yes, Chute Door!",
- "That's a bold strategy, Cotton.",
- "asdflkjaslkdflklnf2222",
- "00110101? That's just gibberish!",
- "Three is my favorite number too!",
- "Robots should not quit, but yours did!",
- "Don’t forget to disable auto-exposure! Or enable it. I'm not sure. ",
- "Have you glued your lenses to keep them in focus?",
- "Don’t put spaces in your camera names — it makes the robot very sad",
- "Upgrade to Photon Pro for gtsam support 👍",
- "Did you forget to take off the lense covers? It’s dark in here…"
+ "Reverse phase oscillation detected!",
+ "Initializing hyperflux capacitor...",
+ "Reticulating splines in progress.",
+ "Quantum entanglement buffer overflowzomg",
+ "Did you remember the turbo-encabulator?",
+ "Engaging magnetic flux inverter.",
+ "Calibrating photon resonance field.",
+ "Deploying recursive feedback loop.",
+ "wow.",
+ "I applaud your pseudo-random bitstream.",
+ "Rebooting quantum foam stabilizer.",
+ "Analyzing subspace harmonics.",
+ "Transmitting encrypted flux packets.",
+ "Verifying entropic phase alignment.",
+ "Reconfiguring nano-particle array.",
+ "pew pew. pew pew.",
+ "I can't parse the synthetic logic matrix.",
+ "Don't forget to make the holographic interface.",
+ "You should generate more stochastic resonance.",
+ "Greetings",
+ "You look like you need some help!",
+ "Set this slider to 25",
+ "Set this slider to 67. HAHA 67!!!!",
+ "That's a horrible choice!",
+ "Fun is a core value! Is that a fun choice?",
+ "If your grandma saw that choice, would she be proud?",
+ "Chute Door?",
+ "Yes, Chute Door!",
+ "That's a bold strategy, Cotton.",
+ "asdflkjaslkdflklnf2222",
+ "00110101? That's just gibberish!",
+ "Three is my favorite number too!",
+ "Robots should not quit, but yours did!",
+ "Don’t forget to disable auto-exposure! Or enable it. I'm not sure. ",
+ "Have you glued your lenses to keep them in focus?",
+ "Don’t put spaces in your camera names — it makes the robot very sad",
+ "Upgrade to Photon Pro for gtsam support 👍",
+ "Did you forget to take off the lense covers? It’s dark in here…"
];
// State-specific humorous phrases
const cameraNeedsSetupPhrases = [
- "These cameras are just standing there... menacingly",
- "Are your cameras plugged in? Trick question -- they aren't!",
- "Have you hot-glued your USB cameras?",
+ "These cameras are just standing there... menacingly",
+ "Are your cameras plugged in? Trick question -- they aren't!",
+ "Have you hot-glued your USB cameras?"
];
const backendNotConnectedPhrases = [
- "Um, is this thing even on?",
- "Anyone home? Bulldozer? Bulldozer?",
- "Have you tried turning the NI™ RoboRIO™ off and on again?",
+ "Um, is this thing even on?",
+ "Anyone home? Bulldozer? Bulldozer?",
+ "Have you tried turning the NI™ RoboRIO™ off and on again?"
];
const ntDisconnectedPhrases = [
- "NetworkTables? More like Network'(; DROP TABLE websockets;--",
- "Robots shouldn't quit, but I sure can't talk to yours!",
- "Are you an OM5P? Because I can't talk to you over the LAN!",
- "I'm a sentient subatomic particle, not a networking engineer."
+ "NetworkTables? More like Network'(; DROP TABLE websockets;--",
+ "Robots shouldn't quit, but I sure can't talk to yours!",
+ "Are you an OM5P? Because I can't talk to you over the LAN!",
+ "I'm a sentient subatomic particle, not a networking engineer."
];
/**
* Get list of applicable phrase categories based on current UI state
*/
function getApplicablePhraseLists() {
- const cameraStore = useCameraSettingsStore();
- const stateStore = useStateStore();
-
- // Build list of applicable phrase categories
- const applicableLists = [quarkyPhrases];
-
- // Add state-specific categories (additive, not replacing)
- if (cameraStore?.needsCameraConfiguration) {
- applicableLists.push(cameraNeedsSetupPhrases);
- }
-
- if (!stateStore?.backendConnected) {
- applicableLists.push(backendNotConnectedPhrases);
- }
-
- if (!stateStore?.ntConnectionStatus?.connected) {
- applicableLists.push(ntDisconnectedPhrases);
- }
-
- return applicableLists;
+ const cameraStore = useCameraSettingsStore();
+ const stateStore = useStateStore();
+
+ // Build list of applicable phrase categories
+ const applicableLists = [quarkyPhrases];
+
+ // Add state-specific categories (additive, not replacing)
+ if (cameraStore?.needsCameraConfiguration) {
+ applicableLists.push(cameraNeedsSetupPhrases);
+ }
+
+ if (!stateStore?.backendConnected) {
+ applicableLists.push(backendNotConnectedPhrases);
+ }
+
+ if (!stateStore?.ntConnectionStatus?.connected) {
+ applicableLists.push(ntDisconnectedPhrases);
+ }
+
+ return applicableLists;
}
/**
* Pick a random phrase from applicable categories
*/
function pickRandomPhrase() {
- const applicableLists = getApplicablePhraseLists();
- const randomList = applicableLists[Math.floor(Math.random() * applicableLists.length)];
- return randomList[Math.floor(Math.random() * randomList.length)];
+ const applicableLists = getApplicablePhraseLists();
+ const randomList = applicableLists[Math.floor(Math.random() * applicableLists.length)];
+ return randomList[Math.floor(Math.random() * randomList.length)];
}
/**
* Get the duration of an animation in milliseconds
*/
function getAnimationDuration(animation) {
- if (!animation) return 500; // Default 0.5s for empty state
+ if (!animation) return 500; // Default 0.5s for empty state
- // Animation durations (in seconds) based on quarky_generator.py
- const durations = {
- idle: 0.5,
- grow: 2.0,
- blink: 0.3,
- wave: 1.8,
- speak: 2.0,
- shrink: 2.0,
- point: 1.5
- };
+ // Animation durations (in seconds) based on quarky_generator.py
+ const durations = {
+ idle: 0.5,
+ grow: 2.0,
+ blink: 0.3,
+ wave: 1.8,
+ speak: 2.0,
+ shrink: 2.0,
+ point: 1.5
+ };
- return (durations[animation] || 1.0) * 1000; // Convert to ms
+ return (durations[animation] || 1.0) * 1000; // Convert to ms
}
/**
* Play an animation
*/
function playAnimation(animation) {
- if (!animation) {
- quarkyImage.src = '';
- quarkyImage.style.display = 'none';
- return;
- }
- quarkyImage.style.display = 'block';
- quarkyImage.src = ANIMATIONS[animation];
- if (animation === 'speak') {
- // Pick random phrase from applicable categories
- quarkySpeechText = pickRandomPhrase();
- speechBubble.textContent = quarkySpeechText;
- speechBubble.style.display = 'block';
- speechBubble.style.opacity = 1;
- setTimeout(() => {
- speechBubble.style.opacity = 0;
- setTimeout(() => { speechBubble.style.display = 'none'; }, 700);
- }, 3000);
- }
+ if (!animation) {
+ quarkyImage.src = "";
+ quarkyImage.style.display = "none";
+ return;
+ }
+ quarkyImage.style.display = "block";
+ quarkyImage.src = ANIMATIONS[animation];
+ if (animation === "speak") {
+ // Pick random phrase from applicable categories
+ quarkySpeechText = pickRandomPhrase();
+ speechBubble.textContent = quarkySpeechText;
+ speechBubble.style.display = "block";
+ speechBubble.style.opacity = 1;
+ setTimeout(() => {
+ speechBubble.style.opacity = 0;
+ setTimeout(() => {
+ speechBubble.style.display = "none";
+ }, 700);
+ }, 3000);
+ }
}
// Mini Quarky management
@@ -199,237 +200,252 @@ const MINI_QUARKY_SPAWN_BASE_DELAY_MS = 4000;
const MINI_QUARKY_SPAWN_DELAY_RANGE_MS = 8000;
// Random movement every few cycles
-let randomMoveCounter = 0;
let mouseX = window.innerWidth / 2;
let mouseY = window.innerHeight / 2;
let mouseMoving = false;
let mouseMoveTimeout = null;
-function mouseMoveHandler(e){
- mouseX = e.clientX + window.scrollX;
- mouseY = e.clientY + window.scrollY;
- mouseMoving = true;
- clearTimeout(mouseMoveTimeout);
- // After 1.5s of no movement, return Quarky to home and resume nonsense
- mouseMoveTimeout = setTimeout(() => {
- mouseMoving = false;
- quarkyContainer.style.left = 'calc(100vw - 550px)';
- quarkyContainer.style.top = 'calc(100vh - 550px)';
- isMovingDemo = false;
- playNextAnimation();
- }, 1500);
- // Actively track mouse: update Quarky's position every mouse move
- quarkyContainer.style.left = `${mouseX}px`;
- quarkyContainer.style.top = `${mouseY}px`;
- // Immediately trigger Quarky to point at cursor
- if (!isMovingDemo) {
- isMovingDemo = true;
- playAnimation('point');
- setTimeout(() => {
- playAnimation('idle');
- }, getAnimationDuration('point'));
- }
+/**
+ * Clamp Quarky's position to stay within the viewport, accounting for the sidebar
+ */
+function clampQuarkyPosition(x, y) {
+ const rect = quarkyContainer.getBoundingClientRect();
+
+ // Get sidebar width (account for both expanded and compact modes)
+ const sidebar = document.querySelector(".v-navigation-drawer");
+ const sidebarWidth = sidebar ? sidebar.offsetWidth : 0;
+
+ // Clamp to viewport, starting after the sidebar
+ const clampedX = Math.max(sidebarWidth, Math.min(x, window.innerWidth - rect.width));
+ const clampedY = Math.max(0, Math.min(y, window.innerHeight - rect.height));
+ return { x: clampedX, y: clampedY };
+}
+
+function mouseMoveHandler(e) {
+ mouseX = e.clientX + window.scrollX;
+ mouseY = e.clientY + window.scrollY;
+ mouseMoving = true;
+ clearTimeout(mouseMoveTimeout);
+ // After 1.5s of no movement, return Quarky to home and resume nonsense
+ mouseMoveTimeout = setTimeout(() => {
+ mouseMoving = false;
+ quarkyContainer.style.left = "calc(100vw - 550px)";
+ quarkyContainer.style.top = "calc(100vh - 550px)";
+ isMovingDemo = false;
+ playNextAnimation();
+ }, 1500);
+ // Actively track mouse: update Quarky's position every mouse move (clamped to viewport)
+ const clamped = clampQuarkyPosition(mouseX, mouseY);
+ quarkyContainer.style.left = `${clamped.x}px`;
+ quarkyContainer.style.top = `${clamped.y}px`;
+ // Immediately trigger Quarky to point at cursor
+ if (!isMovingDemo) {
+ isMovingDemo = true;
+ playAnimation("point");
+ setTimeout(() => {
+ playAnimation("idle");
+ }, getAnimationDuration("point"));
+ }
}
/**
* Advance to the next animation in the sequence
*/
function playNextAnimation() {
- if (isMovingDemo) return;
- let currentStep = SEQUENCE[currentSequenceIndex];
+ if (isMovingDemo) return;
+ let currentStep = SEQUENCE[currentSequenceIndex];
- // On loop, skip grow after first time
- if (hasPlayedGrow && currentSequenceIndex === 0 && currentStep.animation === 'grow') {
- currentSequenceIndex = 1;
- currentStep = SEQUENCE[currentSequenceIndex];
- }
+ // On loop, skip grow after first time
+ if (hasPlayedGrow && currentSequenceIndex === 0 && currentStep.animation === "grow") {
+ currentSequenceIndex = 1;
+ currentStep = SEQUENCE[currentSequenceIndex];
+ }
- // If mouse is moving, don't do normal cycle
- if (mouseMoving) {
- // Quarky will point at cursor via mousemove handler
- return;
- }
-
- // Show speech bubble before speak
- if (currentStep.animation === 'speak') {
- quarkySpeechText = pickRandomPhrase();
- speechBubble.textContent = quarkySpeechText;
- speechBubble.style.display = 'block';
- speechBubble.style.opacity = 1;
- setTimeout(() => {
- speechBubble.style.opacity = 0;
- setTimeout(() => { speechBubble.style.display = 'none'; }, 700);
- }, 3000);
- }
-
- // Fade out speech bubble after speak (during idle)
- if (SEQUENCE[currentSequenceIndex - 1]?.animation === 'speak' && currentStep.animation === 'idle') {
- // Speech bubble already has its own timeout from above
- }
-
- // Always return to corner when idle
- quarkyContainer.style.left = 'calc(100vw - 550px)';
- quarkyContainer.style.top = 'calc(100vh - 550px)';
-
- // Play the animation
- playAnimation(currentStep.animation);
-
- // Calculate duration and schedule next animation
- const duration = getAnimationDuration(currentStep.animation);
+ // If mouse is moving, don't do normal cycle
+ if (mouseMoving) {
+ // Quarky will point at cursor via mousemove handler
+ return;
+ }
+ // Show speech bubble before speak
+ if (currentStep.animation === "speak") {
+ quarkySpeechText = pickRandomPhrase();
+ speechBubble.textContent = quarkySpeechText;
+ speechBubble.style.display = "block";
+ speechBubble.style.opacity = 1;
setTimeout(() => {
- currentLoopCount++;
+ speechBubble.style.opacity = 0;
+ setTimeout(() => {
+ speechBubble.style.display = "none";
+ }, 700);
+ }, 3000);
+ }
- // Check if we've completed all loops for this step
- if (currentLoopCount >= currentStep.loops) {
- // Move to next step
- currentLoopCount = 0;
- currentSequenceIndex++;
+ // Fade out speech bubble after speak (during idle)
+ if (SEQUENCE[currentSequenceIndex - 1]?.animation === "speak" && currentStep.animation === "idle") {
+ // Speech bubble already has its own timeout from above
+ }
- // Loop back to start if we've completed the sequence
- if (currentSequenceIndex >= SEQUENCE.length) {
- currentSequenceIndex = 0;
- hasPlayedGrow = true;
- }
- }
+ // Always return to corner when idle
+ quarkyContainer.style.left = "calc(100vw - 550px)";
+ quarkyContainer.style.top = "calc(100vh - 550px)";
- // Play the next animation
- playNextAnimation();
- }, duration);
+ // Play the animation
+ playAnimation(currentStep.animation);
+
+ // Calculate duration and schedule next animation
+ const duration = getAnimationDuration(currentStep.animation);
+
+ setTimeout(() => {
+ currentLoopCount++;
+
+ // Check if we've completed all loops for this step
+ if (currentLoopCount >= currentStep.loops) {
+ // Move to next step
+ currentLoopCount = 0;
+ currentSequenceIndex++;
+
+ // Loop back to start if we've completed the sequence
+ if (currentSequenceIndex >= SEQUENCE.length) {
+ currentSequenceIndex = 0;
+ hasPlayedGrow = true;
+ }
+ }
+
+ // Play the next animation
+ playNextAnimation();
+ }, duration);
}
-
-function clickToPoint(e){
- // Ignore clicks on the button
- if (e.target.id === 'moveDemoBtn') return;
- isMovingDemo = true;
- const clickX = e.clientX + window.scrollX;
- const clickY = e.clientY + window.scrollY;
- quarkyContainer.style.left = `${clickX}px`;
- quarkyContainer.style.top = `${clickY}px`;
+function clickToPoint(e) {
+ // Ignore clicks on the button
+ if (e.target.id === "moveDemoBtn") return;
+ isMovingDemo = true;
+ const clickX = e.clientX + window.scrollX;
+ const clickY = e.clientY + window.scrollY;
+ const clamped = clampQuarkyPosition(clickX, clickY);
+ quarkyContainer.style.left = `${clamped.x}px`;
+ quarkyContainer.style.top = `${clamped.y}px`;
+ setTimeout(() => {
+ playAnimation("point");
setTimeout(() => {
- playAnimation('point');
- setTimeout(() => {
- playAnimation('idle');
- quarkyContainer.style.left = 'calc(100vw - 550px)';
- quarkyContainer.style.top = 'calc(100vh - 550px)';
- setTimeout(() => {
- isMovingDemo = false;
- playNextAnimation();
- }, 1000);
- }, getAnimationDuration('point'));
- }, 1000);
+ playAnimation("idle");
+ quarkyContainer.style.left = "calc(100vw - 550px)";
+ quarkyContainer.style.top = "calc(100vh - 550px)";
+ setTimeout(() => {
+ isMovingDemo = false;
+ playNextAnimation();
+ }, 1000);
+ }, getAnimationDuration("point"));
+ }, 1000);
}
/**
* Spawn a mini Quarky that moves randomly around the screen
*/
function spawnMiniQuarky() {
- console.log("SPAWNING A QUARKY");
+ console.log("SPAWNING A QUARKY");
- // If at max, don't spawn
- if (miniQuarkies.length >= MAX_MINI_QUARKIES) {
- return;
+ // If at max, don't spawn
+ if (miniQuarkies.length >= MAX_MINI_QUARKIES) {
+ return;
+ }
+
+ const miniContainer = document.createElement("div");
+ miniContainer.style.position = "fixed";
+ miniContainer.style.width = `${MINI_QUARKY_SIZE}px`;
+ miniContainer.style.height = `${MINI_QUARKY_SIZE}px`;
+ miniContainer.style.pointerEvents = "none";
+ miniContainer.style.zIndex = MINI_QUARKY_Z_INDEX.toString();
+
+ // Spawn from main quarky's actual position
+ const rect = quarkyContainer.getBoundingClientRect();
+ const startX = rect.left + window.scrollX + rect.width / 2;
+ const startY = rect.top + window.scrollY + rect.height / 2;
+ miniContainer.style.left = startX + "px";
+ miniContainer.style.top = startY + "px";
+
+ const miniImage = document.createElement("img");
+ miniImage.src = ANIMATIONS["idle"];
+ miniImage.style.width = "100%";
+ miniImage.style.height = "100%";
+ miniImage.style.objectFit = "contain";
+
+ miniContainer.appendChild(miniImage);
+ document.body.appendChild(miniContainer);
+
+ const miniQuarky = {
+ container: miniContainer,
+ image: miniImage,
+ x: startX,
+ y: startY,
+ vx: (Math.random() - MINI_QUARKY_VELOCITY_CENTER) * MINI_QUARKY_VELOCITY_MULTIPLIER,
+ vy: (Math.random() - MINI_QUARKY_VELOCITY_CENTER) * MINI_QUARKY_VELOCITY_MULTIPLIER,
+ animationInterval: null
+ };
+
+ miniQuarkies.push(miniQuarky);
+
+ // Start movement loop
+ miniQuarky.animationInterval = setInterval(() => {
+ miniQuarky.x += miniQuarky.vx;
+ miniQuarky.y += miniQuarky.vy;
+
+ // Bounce off edges
+ if (miniQuarky.x <= 0 || miniQuarky.x >= window.innerWidth - MINI_QUARKY_SIZE) {
+ miniQuarky.vx *= -1;
+ miniQuarky.x = Math.max(0, Math.min(miniQuarky.x, window.innerWidth - MINI_QUARKY_SIZE));
+ }
+ if (miniQuarky.y <= 0 || miniQuarky.y >= window.innerHeight - MINI_QUARKY_SIZE) {
+ miniQuarky.vy *= -1;
+ miniQuarky.y = Math.max(0, Math.min(miniQuarky.y, window.innerHeight - MINI_QUARKY_SIZE));
}
- const miniContainer = document.createElement('div');
- miniContainer.style.position = 'fixed';
- miniContainer.style.width = `${MINI_QUARKY_SIZE}px`;
- miniContainer.style.height = `${MINI_QUARKY_SIZE}px`;
- miniContainer.style.pointerEvents = 'none';
- miniContainer.style.zIndex = MINI_QUARKY_Z_INDEX.toString();
-
- // Spawn from main quarky's actual position
- const rect = quarkyContainer.getBoundingClientRect();
- const startX = rect.left + window.scrollX + rect.width / 2;
- const startY = rect.top + window.scrollY + rect.height / 2;
- miniContainer.style.left = startX + 'px';
- miniContainer.style.top = startY + 'px';
-
- const miniImage = document.createElement('img');
- miniImage.src = ANIMATIONS['idle'];
- miniImage.style.width = '100%';
- miniImage.style.height = '100%';
- miniImage.style.objectFit = 'contain';
-
- miniContainer.appendChild(miniImage);
- document.body.appendChild(miniContainer);
-
- const miniQuarky = {
- container: miniContainer,
- image: miniImage,
- x: startX,
- y: startY,
- vx: (Math.random() - MINI_QUARKY_VELOCITY_CENTER) * MINI_QUARKY_VELOCITY_MULTIPLIER,
- vy: (Math.random() - MINI_QUARKY_VELOCITY_CENTER) * MINI_QUARKY_VELOCITY_MULTIPLIER,
- animationInterval: null
- };
-
- miniQuarkies.push(miniQuarky);
-
- // Start movement loop
- miniQuarky.animationInterval = setInterval(() => {
- miniQuarky.x += miniQuarky.vx;
- miniQuarky.y += miniQuarky.vy;
-
- // Bounce off edges
- if (miniQuarky.x <= 0 || miniQuarky.x >= window.innerWidth - MINI_QUARKY_SIZE) {
- miniQuarky.vx *= -1;
- miniQuarky.x = Math.max(0, Math.min(miniQuarky.x, window.innerWidth - MINI_QUARKY_SIZE));
- }
- if (miniQuarky.y <= 0 || miniQuarky.y >= window.innerHeight - MINI_QUARKY_SIZE) {
- miniQuarky.vy *= -1;
- miniQuarky.y = Math.max(0, Math.min(miniQuarky.y, window.innerHeight - MINI_QUARKY_SIZE));
- }
-
- // Occasionally change direction randomly
- if (Math.random() < DIRECTION_CHANGE_PROBABILITY) {
- miniQuarky.vx = (Math.random() - MINI_QUARKY_VELOCITY_CENTER) * DIRECTION_CHANGE_VELOCITY_MULTIPLIER;
- miniQuarky.vy = (Math.random() - MINI_QUARKY_VELOCITY_CENTER) * DIRECTION_CHANGE_VELOCITY_MULTIPLIER;
- }
-
- miniContainer.style.left = miniQuarky.x + 'px';
- miniContainer.style.top = miniQuarky.y + 'px';
- }, MINI_QUARKY_ANIMATION_INTERVAL_MS);
+ // Occasionally change direction randomly
+ if (Math.random() < DIRECTION_CHANGE_PROBABILITY) {
+ miniQuarky.vx = (Math.random() - MINI_QUARKY_VELOCITY_CENTER) * DIRECTION_CHANGE_VELOCITY_MULTIPLIER;
+ miniQuarky.vy = (Math.random() - MINI_QUARKY_VELOCITY_CENTER) * DIRECTION_CHANGE_VELOCITY_MULTIPLIER;
+ }
+
+ miniContainer.style.left = miniQuarky.x + "px";
+ miniContainer.style.top = miniQuarky.y + "px";
+ }, MINI_QUARKY_ANIMATION_INTERVAL_MS);
}
/**
* Clean up all mini Quarkies
*/
-function cleanupMiniQuarkies() {
- miniQuarkies.forEach(mini => {
- clearInterval(mini.animationInterval);
- mini.container.remove();
- });
- miniQuarkies = [];
+function cleanupMiniQuarkies() { // eslint-disable-line @typescript-eslint/no-unused-vars
+ miniQuarkies.forEach((mini) => {
+ clearInterval(mini.animationInterval);
+ mini.container.remove();
+ });
+ miniQuarkies = [];
}
-
-
-
export default function setup() {
- quarkyImage = document.getElementById('quarkyImage');
- quarkyContainer = document.getElementById('quarkyContainer');
- speechBubble = document.getElementById('quarkySpeechBubble');
+ quarkyImage = document.getElementById("quarkyImage");
+ quarkyContainer = document.getElementById("quarkyContainer");
+ speechBubble = document.getElementById("quarkySpeechBubble");
- // Start the animation sequence
- playNextAnimation();
+ // Start the animation sequence
+ playNextAnimation();
- //Install mouse move handler
- window.addEventListener('mousemove', mouseMoveHandler);
+ //Install mouse move handler
+ window.addEventListener("mousemove", mouseMoveHandler);
- // Click-to-point feature installation
- window.addEventListener('click', (e) => {
- clickToPoint(e);
- });
+ // Click-to-point feature installation
+ window.addEventListener("click", (e) => {
+ clickToPoint(e);
+ });
- // Spawn mini Quarkies on a random timer
- function scheduleNextMiniQuarkySpawn() {
- const delayMs = MINI_QUARKY_SPAWN_BASE_DELAY_MS + Math.random() * MINI_QUARKY_SPAWN_DELAY_RANGE_MS;
- setTimeout(() => {
- spawnMiniQuarky();
- scheduleNextMiniQuarkySpawn();
- }, delayMs);
- }
- scheduleNextMiniQuarkySpawn();
-}
\ No newline at end of file
+ // Spawn mini Quarkies on a random timer
+ function scheduleNextMiniQuarkySpawn() {
+ const delayMs = MINI_QUARKY_SPAWN_BASE_DELAY_MS + Math.random() * MINI_QUARKY_SPAWN_DELAY_RANGE_MS;
+ setTimeout(() => {
+ spawnMiniQuarky();
+ scheduleNextMiniQuarkySpawn();
+ }, delayMs);
+ }
+ scheduleNextMiniQuarkySpawn();
+}