look ma no inter

This commit is contained in:
Chris Gerth
2026-03-11 20:03:42 -05:00
committed by samfreund
parent 515a1a3d78
commit 502ae644a4
10 changed files with 300 additions and 0 deletions

View File

@@ -75,6 +75,17 @@ onBeforeMount(() => {
<photon-log-view />
<photon-error-snackbar />
</v-app>
<!-- Quarky overlay -->
<div class="quarky-overlay">
<div class="quarky-container" id="quarkyContainer" style="left:calc(100vw - 550px);top:calc(100vh - 550px);">
<img id="quarkyImage" src="" alt="Quarky">
<div id="quarkySpeechBubble" style="display:none; position:absolute; left:50%; top:0; transform:translateX(-50%); min-width:120px; max-width:320px; padding:16px 24px; background:#fff; color:#222; border-radius:16px; box-shadow:0 4px 16px rgba(0,0,0,0.15); font-size:1.2em; font-family:sans-serif; opacity:0; transition:opacity 0.7s; pointer-events:none; z-index:10;">
</div>
</div>
</div>
</template>
<style lang="scss">
@@ -117,4 +128,33 @@ onBeforeMount(() => {
div.v-layout {
overflow: unset !important;
}
/* Overlay container for Quarky */
.quarky-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
}
/* Quarky animation container */
.quarky-container {
position: absolute;
width: 500px;
height: 500px;
background-color: transparent;
transition: left 1s cubic-bezier(.42,0,.58,1), top 1s cubic-bezier(.42,0,.58,1);
}
.quarky-container img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -0,0 +1,257 @@
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';
const ANIMATIONS = {
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 },
];
let quarkyImage;
let quarkyContainer;
let speechBubble;
let currentSequenceIndex = 0;
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 overflow.",
"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",
"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!",
];
/**
* Get the duration of an animation in milliseconds
*/
function getAnimationDuration(animation) {
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
};
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
quarkySpeechText = quarkyPhrases[Math.floor(Math.random() * quarkyPhrases.length)];
speechBubble.textContent = quarkySpeechText;
speechBubble.style.display = 'block';
setTimeout(() => { speechBubble.style.opacity = 1; }, 10);
}
}
// 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'));
}
}
/**
* Advance to the next animation in the sequence
*/
function playNextAnimation() {
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];
}
// 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 = quarkyPhrases[Math.floor(Math.random() * quarkyPhrases.length)];
speechBubble.textContent = quarkySpeechText;
speechBubble.style.display = 'block';
setTimeout(() => { speechBubble.style.opacity = 1; }, 10);
}
// Fade out speech bubble after speak (during idle)
if (SEQUENCE[currentSequenceIndex - 1]?.animation === 'speak' && currentStep.animation === 'idle') {
speechBubble.style.opacity = 0;
setTimeout(() => { speechBubble.style.display = 'none'; }, 700);
}
// 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);
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`;
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);
}
export default function setup() {
quarkyImage = document.getElementById('quarkyImage');
quarkyContainer = document.getElementById('quarkyContainer');
speechBubble = document.getElementById('quarkySpeechBubble');
// Start the animation sequence
playNextAnimation();
//Install mouse move handler
window.addEventListener('mousemove', mouseMoveHandler);
// Click-to-point feature installation
window.addEventListener('click', (e) => {
clickToPoint(e);
});
}

View File

@@ -6,6 +6,8 @@ import router from "@/router";
import vuetify from "@/plugins/vuetify";
import axios from "axios";
import setup from "@/lib/quarky.js";
type PhotonClientRuntimeMode = "production" | "development" | "local-network-development";
const runtimeMode: PhotonClientRuntimeMode = process.env.NODE_ENV as PhotonClientRuntimeMode;
@@ -45,3 +47,4 @@ app.use(pinia);
app.use(vuetify);
app.use(router);
app.mount("#app");
setup();