<div id="collab-app-root" style="width: 100%; height: 650px; background: #fff; overflow: hidden; position: relative; cursor: none; font-family: sans-serif; user-select: none; border: 1px solid #eee;">
<!-- THE CHARACTER (Z-index 300) -->
<div id="char-node" style="position: absolute; width: 120px; height: 120px; pointer-events: none; z-index: 300; transition: transform 0.1s ease-out;">
<!-- Body & Hand Fills -->
<div id="body-fill" style="position: absolute; top: 12.5%; left: 16%; width: 68%; height: 68%; border-radius: 50%; z-index: -1;"></div>
<div id="h-l-fill" style="position: absolute; top: 73%; left: 9.5%; width: 18%; height: 18%; border-radius: 50%; z-index: -1;"></div>
<div id="h-r-fill" style="position: absolute; top: 73%; left: 72.5%; width: 18%; height: 18%; border-radius: 50%; z-index: -1;"></div>
<svg viewBox="0 0 100 100" style="width: 100%; height: 100%; overflow: visible;">
<!-- Face Group (Parallax) -->
<g id="face-group" style="transition: transform 0.12s ease-out; transform: scale(1.15); transform-origin: 50% 45%;">
<circle cx="44" cy="38" r="4.5" fill="#1a1a1a" />
<circle cx="56" cy="38" r="4.5" fill="#1a1a1a" />
<path id="mouth" d="M48 48 Q50 50 52 48" stroke="#1a1a1a" fill="none" stroke-width="2" stroke-linecap="round" />
</g>
<!-- Magnifier Pivot (Pivot point: 75.57, 69.15) -->
<g id="mag-pivot" style="transform-origin: 75.57px 69.15px; transition: transform 0.7s cubic-bezier(0.34, 1.56, 0.64, 1);">
<line x1="75.57" y1="69.15" x2="88" y2="82" stroke="#1a1a1a" stroke-width="5" stroke-linecap="round" />
<circle cx="65" cy="55" r="14" fill="rgba(255,255,255,0.1)" stroke="#1a1a1a" stroke-width="3" />
</g>
</svg>
</div>
<!-- UI: Now Create Button -->
<div id="victory-ui" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 100; display: none;">
<button id="create-btn" style="background: #1a1a1a; color: white; border: none; padding: 16px 45px; border-radius: 50px; font-size: 18px; cursor: pointer; letter-spacing: 1px;">now create!</button>
</div>
<!-- THE ART WASH (Z-index 500: Full Screen Center) -->
<div id="art-wash" style="position: absolute; top: 50%; left: 50%; width: 300vmax; height: 300vmax; z-index: 500; pointer-events: none; opacity: 0; transform: translate(-50%, -50%) scale(0); border-radius: 50%; overflow: hidden;"></div>
<!-- Speech bubble -->
<div id="speech" style="position: absolute; background: white; padding: 10px 18px; border-radius: 20px; box-shadow: 0 10px 25px rgba(0,0,0,0.08); font-size: 14px; opacity: 0; pointer-events: none; z-index: 350; transform: translate(-50%, -100%);"></div>
<canvas id="main-stage" style="display: block;"></canvas>
</div>
<script>
(function() {
// --- MANUAL CONFIGURATION ---
const COLOR_GREY = "#aeaeae"; // Hex code for character grey
const ROT_REST = 45; // Magnifier angle when moving (degrees)
const ROT_ACTIVE = 135; // Magnifier angle when over face (degrees)
// ----------------------------
const root = document.getElementById('collab-app-root');
const char = document.getElementById('char-node');
const mouth = document.getElementById('mouth');
const mag = document.getElementById('mag-pivot');
const wash = document.getElementById('art-wash');
const speech = document.getElementById('speech');
const canvas = document.getElementById('main-stage');
const ctx = canvas.getContext('2d');
let width, height, mouse = {x:-500, y:-500}, pos = {x:0, y:0};
let shapes = [], discovered = [], state = 'DISCOVERY';
const thoughts = ["ooooo fascinating.","what an interesting story.","ah, a new perspective.","hm, I hadn't thought of that.","How delightful!","oh… that’s unexpected.","curious… very curious.", "I didn’t see that coming."];
function initShapes() {
shapes = [];
const palette = ['#FF595E', '#FFCA3A', '#8AC926', '#1982C4', '#6A4C93'];
const types = ['square', 'diamond', 'hexagon', 'star', 'trefoil'];
for(let i=0; i<5; i++) {
shapes.push({
type: types[i],
color: palette[i],
x: 0.15 + Math.random()*0.7,
y: 0.15 + Math.random()*0.7,
found: false, rot: 0, scale: 1, orbit: 0,
phrase: thoughts[Math.floor(Math.random()*thoughts.length)]
});
}
}
function resize() {
width = root.clientWidth;
height = root.clientHeight;
canvas.width = width;
canvas.height = height;
initShapes();
}
window.addEventListener('resize', resize);
root.addEventListener('mousemove', e => {
const r = root.getBoundingClientRect();
mouse.x = e.clientX - r.left;
mouse.y = e.clientY - r.top;
});
function updateColors() {
const fill = discovered.length === 0 ? COLOR_GREY :
discovered.length === 1 ? discovered[0] :
`conic-gradient(${discovered.map((c, i) => `${c} ${(i/discovered.length)*100}% ${((i+1)/discovered.length)*100}%`).join(',')})`;
document.getElementById('body-fill').style.background = fill;
document.getElementById('h-l-fill').style.background = fill;
document.getElementById('h-r-fill').style.background = fill;
}
function triggerWash() {
wash.style.transition = 'none';
wash.style.opacity = 1;
wash.style.background = `conic-gradient(${discovered.join(',')})`;
wash.style.transform = 'translate(-50%, -50%) scale(0)';
setTimeout(() => {
wash.style.transition = 'transform 1.2s cubic-bezier(0.1, 0, 0.1, 1), opacity 0.5s 3s';
wash.style.transform = 'translate(-50%, -50%) scale(1)';
setTimeout(() => {
wash.style.opacity = 0;
setTimeout(() => {
discovered = [];
state = 'DISCOVERY';
updateColors();
initShapes();
}, 600);
}, 3000);
}, 50);
}
document.getElementById('create-btn').onclick = () => {
document.getElementById('victory-ui').style.display = 'none';
state = 'CELEBRATION';
triggerWash();
};
function loop() {
// Character Motion
const dx = mouse.x - pos.x;
const dy = mouse.y - pos.y;
pos.x += dx * 0.08;
pos.y += dy * 0.08;
// Face Parallax
document.getElementById('face-group').style.transform = `translate(${Math.max(-7, Math.min(7, dx*0.14))}px, ${Math.max(-5, Math.min(5, dy*0.14))}px)`;
let renderY = pos.y;
if(discovered.length === 5 && state === 'DISCOVERY') renderY += Math.sin(Date.now()*0.015)*15 - 15;
char.style.transform = `translate(${pos.x - 60}px, ${renderY - 60}px)`;
ctx.clearRect(0,0,width,height);
let near = false;
if(state === 'DISCOVERY') {
shapes.forEach(s => {
const px = s.x * width;
const py = s.y * height;
const d = Math.sqrt((pos.x - px)**2 + (pos.y - py)**2);
if(d < 75) {
near = true;
if(!s.found) {
s.found = true;
discovered.push(s.color);
updateColors();
speech.innerText = s.phrase;
if(discovered.length === 5) document.getElementById('victory-ui').style.display = 'block';
}
s.scale += (1.9 - s.scale) * 0.1;
s.rot += 0.1;
s.orbit += 0.1;
// Small Orbiters
ctx.globalAlpha = 0.5;
for(let i=0; i<2; i++) {
const ang = s.orbit + (i*Math.PI);
const ox = px + Math.cos(ang)*30;
const oy = py + Math.sin(ang)*30;
ctx.save();
ctx.translate(ox, oy);
ctx.rotate(s.rot);
ctx.fillStyle = s.color;
ctx.fillRect(-2,-2,4,4);
ctx.restore();
}
ctx.globalAlpha = 1;
} else {
s.scale += (1 - s.scale) * 0.1;
s.rot *= 0.9;
}
// Draw Shapes
ctx.save();
ctx.translate(px, py);
ctx.rotate(s.rot);
ctx.scale(s.scale, s.scale);
ctx.fillStyle = s.color;
if(!s.found) ctx.filter = 'blur(15px)';
ctx.beginPath();
if(s.type==='square') ctx.rect(-12,-12,24,24);
else if(s.type==='diamond') { ctx.moveTo(0,-16); ctx.lineTo(16,0); ctx.lineTo(0,16); ctx.lineTo(-16,0); }
else if(s.type==='hexagon') { for(let i=0;i<6;i++) ctx.lineTo(14*Math.cos(i*Math.PI/3), 14*Math.sin(i*Math.PI/3)); }
else if(s.type==='star') { for(let i=0;i<10;i++) { let r=i%2===0?16:7; ctx.lineTo(r*Math.cos(i*Math.PI/5-Math.PI/2), r*Math.sin(i*Math.PI/5-Math.PI/2)); } }
else if(s.type==='trefoil') { for(let i=0;i<3;i++) ctx.arc(5*Math.cos(i*2*Math.PI/3-Math.PI/2), 5*Math.sin(i*2*Math.PI/3-Math.PI/2), 8, 0, Math.PI*2); }
ctx.fill();
ctx.restore();
ctx.filter = 'none';
});
}
// Handle Mouth & Mag States
if(near) {
speech.style.opacity = 1;
speech.style.left = pos.x+'px';
speech.style.top = (renderY-75)+'px';
mouth.setAttribute('d', 'M40 50 L60 50 L50 65 Z'); // Wide Wedge Mouth
mouth.setAttribute('fill', '#1a1a1a');
mouth.setAttribute('stroke-width', '0');
mag.style.transform = `rotate(${ROT_ACTIVE}deg)`;
} else {
speech.style.opacity = 0;
mouth.setAttribute('d', discovered.length === 5 ? 'M44 48 Q50 56 56 48' : 'M48 48 Q50 50 52 48');
mouth.setAttribute('fill', 'none');
mouth.setAttribute('stroke-width', '2');
mag.style.transform = `rotate(${ROT_REST}deg)`;
}
requestAnimationFrame(loop);
}
resize();
updateColors();
loop();
})();
</script>