/**
* 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