/** * Interactive Poster Engine v1.0 * Usage:
* */ const InteractivePoster = { load: async function(containerId, jsonUrlOrData) { const wrapper = document.getElementById(containerId); if (!wrapper) return console.error("Poster container not found"); // 1. Load Data let data; if (typeof jsonUrlOrData === 'string' && (jsonUrlOrData.endsWith('.json') || jsonUrlOrData.endsWith('.poster'))) { const response = await fetch(jsonUrlOrData); data = await response.json(); } else { data = jsonUrlOrData; } // 2. Setup Container wrapper.style.position = 'relative'; wrapper.style.overflow = 'hidden'; wrapper.style.width = '100%'; wrapper.style.height = '100%'; wrapper.style.background = data.config.background; // Responsive Scaling const baseW = parseFloat(data.config.width); const baseH = parseFloat(data.config.height); const contentLayer = document.createElement('div'); contentLayer.style.width = data.config.width; contentLayer.style.height = data.config.height; contentLayer.style.transformOrigin = 'center center'; contentLayer.style.position = 'absolute'; contentLayer.style.left = '50%'; contentLayer.style.top = '50%'; wrapper.appendChild(contentLayer); function resize() { const scale = Math.min(wrapper.offsetWidth / baseW, wrapper.offsetHeight / baseH); contentLayer.style.transform = `translate(-50%, -50%) scale(${scale})`; } window.addEventListener('resize', resize); resize(); // Run once // 3. Build DOM & Physics const Engine = Matter.Engine, Runner = Matter.Runner, Bodies = Matter.Bodies, Composite = Matter.Composite, Mouse = Matter.Mouse, MouseConstraint = Matter.MouseConstraint; const engine = Engine.create(); const world = engine.world; const bodies = []; // Walls (Invisible containment) const wallOpts = { isStatic: true, render: { visible: false } }; bodies.push(Bodies.rectangle(baseW/2, baseH+50, baseW, 100, wallOpts)); // Floor bodies.push(Bodies.rectangle(baseW/2, -50, baseW, 100, wallOpts)); // Ceiling bodies.push(Bodies.rectangle(-50, baseH/2, 100, baseH, wallOpts)); // Left bodies.push(Bodies.rectangle(baseW+50, baseH/2, 100, baseH, wallOpts)); // Right // Create Elements from JSON data.elements.forEach(item => { const el = document.createElement(item.type === 'h1' || item.type === 'p' ? item.type : 'div'); // SAFETY: Use innerText, NEVER innerHTML if(item.content) el.innerText = item.content; // Styles el.style.position = 'absolute'; el.style.left = '0'; el.style.top = '0'; // Physics controls pos el.style.width = item.w; el.style.height = item.h; el.style.color = item.color; el.style.backgroundColor = item.type === 'shape' ? item.color : 'transparent'; if(item.fontSize) el.style.fontSize = item.fontSize; if(item.type === 'h1') { el.style.fontFamily = 'Inter'; el.style.fontWeight = '900'; el.style.lineHeight = '0.9'; } if(item.shapeType === 'circle') el.style.borderRadius = '50%'; contentLayer.appendChild(el); // Physics Body const x = parseFloat(item.x) + (parseFloat(item.w)/2); // Convert top-left to center const y = parseFloat(item.y) + (parseFloat(item.h)/2); const body = item.shapeType === 'circle' ? Bodies.circle(x, y, parseFloat(item.w)/2) : Bodies.rectangle(x, y, parseFloat(item.w), parseFloat(item.h)); body.isStatic = item.isStatic; body.restitution = 0.6; // Bounciness body.domElement = el; // Link DOM to Physics bodies.push(body); }); Composite.add(world, bodies); // Mouse Interaction const mouse = Mouse.create(wrapper); // Attach to wrapper to capture events // Scale mouse coords because of CSS transform // (Advanced implementation omitted for brevity, simpler to put mouse on contentLayer) // Run Physics const runner = Runner.create(); Runner.run(runner, engine); // Render Loop (function render() { Engine.update(engine, 1000 / 60); bodies.forEach(b => { if(b.domElement && !b.isStatic) { b.domElement.style.transform = `translate(${b.position.x - parseFloat(b.domElement.style.width)/2}px, ${b.position.y - parseFloat(b.domElement.style.height)/2}px) rotate(${b.angle}rad)`; } }); requestAnimationFrame(render); })(); } }; ``` ### 3. How to Host This (The Platform) Here is your "Standard" for users: 1. **Host the Script:** Upload `poster-engine.js` and `matter.min.js` to your website (e.g., `/scripts/`). 2. **User Workflow:** * User creates poster in your Studio. * User clicks **"Save .POSTER"**. * User uploads that file to their website (Squarespace "Link" upload). 3. **Embed Code:** The user pastes this into a Squarespace Code Block: ```html
3% Cover the Fee