/* global React */ /* The signature "live design playground" — drag stickers, type, shapes, swap bg */ const { useState: lcUseState, useRef: lcUseRef, useEffect: lcUseEffect } = React; const LC_BG_OPTIONS = [ { name: "PAPER", val: "var(--paper-2)" }, { name: "ACID", val: "var(--acid)" }, { name: "PINK", val: "var(--pink)" }, { name: "BLUE", val: "var(--blue)" }, { name: "MINT", val: "var(--mint)" }, { name: "LILAC", val: "var(--lilac)" }, { name: "INK", val: "var(--ink)" }, ]; const LC_STICKERS = [ "MORE COWBELL", "NEW!", "AS SEEN ON YOUR FYP", "SHIP IT", "★ TASTY", "MADE IN INDIA", "DO IT LOUDER", "BIG IF TRUE", ]; const LC_EMOJIS = ["★","✺","✦","◐","◯","❍","♥","⚡","☀","☻"]; const LC_PHRASES = ["LOUDER.", "BOLDER.", "RAW.", "weird.", "FAST.", "honest."]; const LC_SHAPE_COLORS = ["var(--pink)","var(--acid)","var(--blue)","var(--mint)","var(--lava)","var(--lilac)"]; function uid() { return Math.random().toString(36).slice(2, 9); } function initialItems(stageW = 900, stageH = 560) { // Layout items as a percentage of the stage so they fit any viewport. const px = (rx, ry) => ({ x: Math.round(rx * stageW), y: Math.round(ry * stageH) }); return [ { id: uid(), type: "sticker", text: "DRAG ME →", ...px(0.30, 0.12), r: -6, bg: "var(--acid)" }, { id: uid(), type: "emoji", text: "★", ...px(0.62, 0.20), r: 12 }, { id: uid(), type: "text", text: "open studio.", ...px(0.28, 0.38), r: -3, color: "var(--ink)" }, { id: uid(), type: "shape", shape: "circle", ...px(0.78, 0.46), r: 0, bg: "var(--pink)", w: 110, h: 110 }, { id: uid(), type: "sticker", text: "★ ★ ★ ★ ★", ...px(0.50, 0.66), r: 4, bg: "var(--pink)", color: "var(--paper)" }, { id: uid(), type: "emoji", text: "✦", ...px(0.86, 0.14), r: -10, color: "var(--blue)" }, ]; } function LiveCanvas() { const [bg, setBg] = lcUseState(LC_BG_OPTIONS[0].val); const [items, setItems] = lcUseState(() => initialItems()); const [stageSize, setStageSize] = lcUseState({ w: 900, h: 560 }); const stageRef = lcUseRef(null); const dragRef = lcUseRef(null); const didInitRef = lcUseRef(false); // Observe stage size; on first measurement, reflow initial items to fit. lcUseEffect(() => { const el = stageRef.current; if (!el) return; const measure = () => { const r = el.getBoundingClientRect(); setStageSize({ w: r.width, h: r.height }); if (!didInitRef.current && r.width > 0) { setItems(initialItems(r.width, r.height)); didInitRef.current = true; } }; measure(); const ro = new ResizeObserver(measure); ro.observe(el); window.addEventListener("resize", measure); return () => { ro.disconnect(); window.removeEventListener("resize", measure); }; }, []); const spawnRange = () => ({ minX: 20, maxX: Math.max(40, stageSize.w - 140), minY: 20, maxY: Math.max(40, stageSize.h - 100), }); const addSticker = () => { const text = LC_STICKERS[Math.floor(Math.random() * LC_STICKERS.length)]; const colors = ["var(--acid)","var(--pink)","var(--blue)","var(--mint)","var(--lilac)","var(--ink)"]; const bg = colors[Math.floor(Math.random() * colors.length)]; const color = (bg === "var(--acid)" || bg === "var(--mint)" || bg === "var(--lilac)") ? "var(--ink)" : "var(--paper)"; spawn({ type: "sticker", text, r: rand(-10, 10), bg, color }); }; const addEmoji = () => spawn({ type: "emoji", text: LC_EMOJIS[Math.floor(Math.random()*LC_EMOJIS.length)], r: rand(-15, 15), color: ["var(--ink)","var(--pink)","var(--blue)","var(--lava)"][Math.floor(Math.random()*4)] }); const addText = () => spawn({ type: "text", text: LC_PHRASES[Math.floor(Math.random()*LC_PHRASES.length)], r: rand(-5, 5), color: "var(--ink)" }); const addShape = () => { const shape = ["circle","square","triangle"][Math.floor(Math.random()*3)]; spawn({ type: "shape", shape, r: rand(0, 30), bg: LC_SHAPE_COLORS[Math.floor(Math.random()*LC_SHAPE_COLORS.length)], w: 110, h: 110 }); }; const clearAll = () => setItems([]); const reset = () => setItems(initialItems(stageSize.w, stageSize.h)); const shuffle = () => { const range = spawnRange(); setItems(items => items.map(it => ({ ...it, r: rand(-12, 12), x: rand(range.minX, range.maxX), y: rand(range.minY, range.maxY), }))); }; function rand(a, b) { return Math.round(a + Math.random() * (b - a)); } function spawn(partial) { const r = spawnRange(); setItems(items => [...items, { id: uid(), x: rand(r.minX, r.maxX), y: rand(r.minY, r.maxY), ...partial }]); } function startDrag(e, id) { const item = items.find(i => i.id === id); if (!item) return; const stageRect = stageRef.current.getBoundingClientRect(); const point = e.touches ? e.touches[0] : e; dragRef.current = { id, offX: point.clientX - stageRect.left - item.x, offY: point.clientY - stageRect.top - item.y, moved: false, }; window.addEventListener("mousemove", onDrag); window.addEventListener("mouseup", endDrag); window.addEventListener("touchmove", onDrag, { passive: false }); window.addEventListener("touchend", endDrag); e.preventDefault(); } function onDrag(e) { if (!dragRef.current) return; const stageRect = stageRef.current.getBoundingClientRect(); const point = e.touches ? e.touches[0] : e; const nx = point.clientX - stageRect.left - dragRef.current.offX; const ny = point.clientY - stageRect.top - dragRef.current.offY; dragRef.current.moved = true; setItems(items => items.map(it => it.id === dragRef.current.id ? { ...it, x: nx, y: ny } : it)); if (e.preventDefault) e.preventDefault(); } function endDrag() { window.removeEventListener("mousemove", onDrag); window.removeEventListener("mouseup", endDrag); window.removeEventListener("touchmove", onDrag); window.removeEventListener("touchend", endDrag); setTimeout(() => { dragRef.current = null; }, 50); } function rotateItem(id) { if (dragRef.current && dragRef.current.moved) return; setItems(items => items.map(it => it.id === id ? { ...it, r: (it.r || 0) + 18 } : it)); } function deleteItem(e, id) { e.preventDefault(); setItems(items => items.filter(it => it.id !== id)); } return (
The signature move · live

The studio
is open.

Most agency websites show you a portfolio. We're handing you the tools. Spawn stickers, drag emoji, throw shapes around. The composition you make is yours.

↳ click = rotate · drag = move · right-click = delete

Tools

Background

{LC_BG_OPTIONS.map((c) => (

Stage

drag · click · right-click ✕
{items.map((it) => ( startDrag(e, it.id)} onTouchStart={(e) => startDrag(e, it.id)} onClick={() => rotateItem(it.id)} onContextMenu={(e) => deleteItem(e, it.id)} /> ))}
↳ Build a moodboard. Send it to us with your inquiry. Take it further →
); } function CanvasItem({ item, onMouseDown, onTouchStart, onClick, onContextMenu }) { const style = { transform: `translate(${item.x}px, ${item.y}px) rotate(${item.r || 0}deg)`, }; if (item.type === "sticker") { return (
{item.text}
); } if (item.type === "emoji") { return (
{item.text}
); } if (item.type === "text") { return (
{item.text}
); } // shape let shapeStyle = { ...style, width: item.w, height: item.h, background: item.bg }; if (item.shape === "circle") shapeStyle.borderRadius = "50%"; if (item.shape === "triangle") { shapeStyle.background = "transparent"; shapeStyle.width = 0; shapeStyle.height = 0; shapeStyle.borderLeft = `${item.w/2}px solid transparent`; shapeStyle.borderRight = `${item.w/2}px solid transparent`; shapeStyle.borderBottom = `${item.h}px solid ${item.bg}`; shapeStyle.border = undefined; // we lose the styled border with triangle; just keep transparent borders defined above } return (
); } Object.assign(window, { LiveCanvas });