08 / Canvas / один HTML-файл

Капли в сосуд

Позволяет собрать проект из SEO, контента, дизайна, кода и аналитики в заданном бюджете.

Капли в сосуд — JavaScript и Canvas
мини игра HTML CSS JavaScript кодБез библиотек

Live preview

Попробуйте механику

Адаптировать
02

Что показывает эта механика

Позволяет собрать проект из SEO, контента, дизайна, кода и аналитики в заданном бюджете.

Где использовать

Калькуляторы, конфигураторы, e-commerce. Для коммерческого проекта меняются правила, визуальный язык, тексты, аналитические события и действие после завершения.

Технологии

Семантический HTML, адаптивный CSS, Canvas API, Pointer Events и requestAnimationFrame. Внешние библиотеки и графические ассеты не требуются.

Полный исходный код / 12 строк
<!doctype html><html lang="ru"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><meta name="theme-color" content="#f3f0e9"><meta name="robots" content="noindex, nofollow"><title>Капли в сосуд — Lavrushin Interactive</title><style>
:root{--bg:#f3f0e9;--ink:#111;--accent:#ff5a1f;--muted:#6f6b64;--line:rgba(17,17,17,.16)}
*{box-sizing:border-box}html,body{margin:0;min-height:100%;background:var(--bg);color:var(--ink);font-family:Arial,Helvetica,sans-serif;-webkit-text-size-adjust:100%}
body{padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}main{width:100%;max-width:760px;margin:0 auto;padding:14px}.card{border:1px solid var(--ink);overflow:hidden;background:var(--bg)}
.head{position:relative;padding:20px 18px 16px;border-bottom:1px solid var(--ink)}.kicker{margin:0 0 10px;font-size:10px;letter-spacing:.16em;text-transform:uppercase}.num{position:absolute;right:16px;top:16px;font-size:58px;line-height:.8;font-weight:900;color:var(--accent);opacity:.17;letter-spacing:-.08em}
h1{margin:0;max-width:82%;font-size:clamp(36px,11vw,64px);line-height:.9;letter-spacing:-.055em}.copy{margin:14px 0 0;max-width:620px;font-size:14px;line-height:1.5;color:#34312d}.stage{padding:12px}.canvas-wrap{position:relative;border:1px solid var(--ink);overflow:hidden;background:#fff9}
canvas{display:block;width:100%;height:clamp(360px,58svh,500px);touch-action:none;-webkit-user-select:none;user-select:none}.hud{position:absolute;left:10px;right:10px;top:10px;display:flex;justify-content:space-between;gap:8px;pointer-events:none}.chip{background:var(--bg);border:1px solid var(--ink);padding:7px 9px;font-size:9px;letter-spacing:.08em;text-transform:uppercase;font-weight:800}.status{min-height:62px;padding:13px 2px 8px;font-size:14px;line-height:1.45}.actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}.actions .wide{grid-column:1/-1}
button,a.btn{min-height:46px;border:1px solid var(--ink);border-radius:0;background:var(--ink);color:var(--bg);padding:12px 14px;font:800 11px/1 Arial,sans-serif;letter-spacing:.07em;text-transform:uppercase;text-decoration:none;display:flex;align-items:center;justify-content:center;-webkit-tap-highlight-color:transparent}button.secondary{background:transparent;color:var(--ink)}.help{padding:13px 18px;border-top:1px solid var(--ink);font-size:11px;line-height:1.45;color:var(--muted)}.help strong{color:var(--ink)}
@media(min-width:700px){main{padding:24px}.head{padding:24px}.stage{padding:18px}.actions{display:flex;justify-content:flex-end}.actions .wide{grid-column:auto}.status{min-height:48px}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important}}
.dropgrid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-top:10px}.dropbtn{min-height:58px;border:1px solid var(--ink);background:transparent;color:var(--ink);padding:10px 12px;text-align:left;display:block}.dropbtn b{display:block;font-size:12px}.dropbtn span{display:block;margin-top:4px;font-size:10px;color:var(--muted)}@media(min-width:640px){.dropgrid{grid-template-columns:repeat(4,1fr)}}</style></head><body><main><section class="card" aria-labelledby="title"><header class="head"><p class="kicker">Interactive demo / Project mix</p><h1 id="title">Капли в сосуд</h1><div class="num" aria-hidden="true">07</div><p class="copy">Соберите проект из SEO, контента, дизайна, кода и аналитики. Сильный digital-продукт — это не максимум компонентов, а правильная пропорция.</p></header><div class="stage"><div class="canvas-wrap"><canvas id="game" aria-label="Игра Капли в сосуд"></canvas><div class="hud"><span class="chip" id="fill">0 / 100</span><span class="chip">Цель 90–100</span></div></div><div class="dropgrid" id="buttons"></div><div class="status" id="status" aria-live="polite">Соберите состав проекта. Нужно попасть в диапазон 90–100, не переполнив бюджет.</div><div class="actions"><button class="secondary" id="reset">Очистить сосуд</button><a class="btn wide" href="https://t.me/DMITRIY_LAVRUSHIN" target="_blank" rel="noopener">Обсудить проект</a></div></div><footer class="help"><strong>Mobile-first:</strong> touch, Pointer Events, safe-area. Без библиотек и внешних ассетов.</footer></section></main><script>(()=>{'use strict';const c=document.getElementById('game'),x=c.getContext('2d'),fillEl=document.getElementById('fill'),status=document.getElementById('status'),buttons=document.getElementById('buttons');let W=320,H=420,dpr=1,level=0,parts=0;const opts=[['SEO',18],['КОНТЕНТ',16],['ДИЗАЙН',22],['HTML',24],['АНАЛИТИКА',10],['API',15],['MOTION',8],['ФОТО',12]];
function track(action,d={}){const p={event:'interactive_game',game:'drops-vessel',action,...d};window.dataLayer=window.dataLayer||[];window.dataLayer.push(p)}opts.forEach(([n,v])=>{const b=document.createElement('button');b.className='dropbtn';b.innerHTML='<b>'+n+'</b><span>+'+v+' к составу</span>';b.onclick=()=>add(n,v,b);buttons.appendChild(b)});function fit(){const r=c.getBoundingClientRect();W=Math.max(280,Math.round(r.width));H=Math.max(340,Math.round(r.height));dpr=Math.min(2,devicePixelRatio||1);c.width=W*dpr;c.height=H*dpr;x.setTransform(dpr,0,0,dpr,0,0);draw()}function add(name,v,b){if(b.disabled)return;b.disabled=true;b.style.opacity=.38;level+=v;parts++;fillEl.textContent=level+' / 100';track('add',{name,value:v,total:level});if(level>100){status.innerHTML='<strong>Переполнение.</strong> В проект добавлено больше, чем действительно нужно.';track('overflow',{total:level})}else if(level>=90){status.innerHTML='<strong>Состав сбалансирован.</strong> Фундамент закрыт без лишнего технологического веса.';track('complete',{total:level,parts})}else status.innerHTML='<strong>'+level+'/100.</strong> Основа уже есть, но проекту пока не хватает нескольких слоёв.';draw()}
function draw(){x.clearRect(0,0,W,H);x.fillStyle='#f7f4ed';x.fillRect(0,0,W,H);const vw=Math.min(W*.58,290),vx=(W-vw)/2,vy=48,vh=H-92;x.strokeStyle='#111';x.lineWidth=4;x.beginPath();x.moveTo(vx,vy);x.lineTo(vx+18,vy+vh);x.quadraticCurveTo(W/2,vy+vh+18,vx+vw-18,vy+vh);x.lineTo(vx+vw,vy);x.stroke();const fh=Math.min(1,level/110)*(vh-12);x.fillStyle=level>100?'#111':'#ff5a1f';x.globalAlpha=.92;x.fillRect(vx+14,vy+vh-fh,vw-28,fh);x.globalAlpha=1;const ty=vy+vh-(100/110)*(vh-12);x.setLineDash([6,6]);x.strokeStyle='rgba(17,17,17,.55)';x.beginPath();x.moveTo(vx+4,ty);x.lineTo(vx+vw-4,ty);x.stroke();x.setLineDash([]);x.fillStyle='#111';x.font='800 9px Arial';x.textAlign='left';x.fillText('TARGET 90–100',vx+8,ty-8);x.font='900 34px Arial';x.textAlign='center';x.fillText(level,W/2,H*.53)}function reset(){level=0;parts=0;fillEl.textContent='0 / 100';[...buttons.children].forEach(b=>{b.disabled=false;b.style.opacity=1});status.textContent='Соберите состав проекта. Нужно попасть в диапазон 90–100, не переполнив бюджет.';draw();track('reset')}document.getElementById('reset').onclick=reset;if('ResizeObserver'in window)new ResizeObserver(fit).observe(c);else addEventListener('resize',fit);requestAnimationFrame(fit)})();</script></body></html>