look ma no inter
@@ -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>
|
||||
|
||||
|
||||
BIN
photon-client/src/assets/images/blink.gif
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
photon-client/src/assets/images/grow.gif
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
photon-client/src/assets/images/idle.gif
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
photon-client/src/assets/images/point.gif
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
photon-client/src/assets/images/shrink.gif
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
photon-client/src/assets/images/speak.gif
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
photon-client/src/assets/images/wave.gif
Normal file
|
After Width: | Height: | Size: 99 KiB |
257
photon-client/src/lib/quarky.js
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
|
||||