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

Катапульта запросов

Пользователь направляет запрос на релевантную посадочную и на практике понимает поисковый интент.

Катапульта запросов — JavaScript и Canvas
простая Canvas игра JavaScriptБез библиотек

Live preview

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

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

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

Пользователь направляет запрос на релевантную посадочную и на практике понимает поисковый интент.

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

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

Технологии

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

Полный исходный код / 13 строк
<!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}}
</style></head><body><main><section class="card" aria-labelledby="title"><header class="head"><p class="kicker">Interactive demo / Search routing</p><h1 id="title">Катапульта запросов</h1><div class="num" aria-hidden="true">04</div><p class="copy">Запустите поисковый запрос в правильную посадочную страницу. Хороший оффер не спасает, если интент приземляется не туда.</p></header><div class="stage"><div class="canvas-wrap"><canvas id="game" aria-label="Игра Катапульта запросов"></canvas><div class="hud"><span class="chip" id="query">заказать сайт</span><span class="chip" id="score">0 / 5</span></div></div><div class="status" id="status" aria-live="polite">Удерживайте «Сила», затем отпустите. Задача — посадить запрос на правильную страницу.</div><div class="actions"><button id="fire">Удерживать силу</button><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'),queryEl=document.getElementById('query'),scoreEl=document.getElementById('score'),status=document.getElementById('status'),fireBtn=document.getElementById('fire');let W=320,H=420,dpr=1,raf=0,last=0,round=0,hits=0,state='aim',power=.25,charging=false,shot={x:0,y:0,vx:0,vy:0};const rs=[['заказать сайт','УСЛУГА',.74],['как выбрать CMS','СТАТЬЯ',.56],['кейсы разработки','КЕЙС',.69],['стоимость сайта','PRICE',.48],['Дмитрий Лаврушин','ПРОФИЛЬ',.80]];let target={x:0,w:84,label:'УСЛУГА'};
function track(action,d={}){const p={event:'interactive_game',game:'catapult',action,...d};window.dataLayer=window.dataLayer||[];window.dataLayer.push(p)}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);setRound();draw()}function setRound(){const q=rs[round]||rs[0];queryEl.textContent=q[0];target.label=q[1];target.w=Math.max(74,W*.18);target.x=Math.min(W-target.w-10,W*q[2]-target.w/2)}function fire(){if(state!=='aim')return;charging=false;state='fly';const a=-Math.PI/3.15,s=225+power*330;shot={x:52,y:H-60,vx:Math.cos(a)*s,vy:Math.sin(a)*s};track('fire',{power:+power.toFixed(2),query:rs[round][0]})}function next(hit){if(hit)hits++;round++;scoreEl.textContent=hits+' / '+Math.min(round,rs.length);if(round>=rs.length){state='done';status.innerHTML='<strong>'+(hits>=4?'Маршрутизация точная.':'Нужна лучшая карта страниц.')+'</strong> '+hits+' из '+rs.length+' запросов приземлились на правильные посадочные.';track('complete',{hits});return}state='aim';power=.25;setRound()}
function loop(t){const dt=Math.min(.032,(t-last)/1000||.016);last=t;if(charging&&state==='aim'){power+=dt*.72;if(power>1)power=.2}if(state==='fly'){shot.vy+=430*dt;shot.x+=shot.vx*dt;shot.y+=shot.vy*dt;if(shot.y>H-32||shot.x>W+20){const hit=shot.x>target.x&&shot.x<target.x+target.w&&shot.y>H-78;status.innerHTML=hit?'<strong>Попадание.</strong> Интент пришёл на релевантную страницу.':'<strong>Промах.</strong> Неверная посадочная теряет намерение пользователя.';track(hit?'hit':'miss',{query:rs[round][0]});next(hit)}}draw();raf=requestAnimationFrame(loop)}function draw(){x.clearRect(0,0,W,H);x.fillStyle='#f7f4ed';x.fillRect(0,0,W,H);x.strokeStyle='rgba(17,17,17,.1)';for(let y=0;y<H;y+=32){x.beginPath();x.moveTo(0,y);x.lineTo(W,y);x.stroke()}x.fillStyle='#111';x.fillRect(target.x,H-38,target.w,24);x.fillStyle='#f3f0e9';x.font='800 9px Arial';x.textAlign='center';x.fillText(target.label,target.x+target.w/2,H-22);x.strokeStyle='#ff5a1f';x.lineWidth=5;x.beginPath();x.moveTo(32,H-46);x.lineTo(60,H-74);x.lineTo(80,H-46);x.stroke();if(state==='aim'){x.fillStyle='#111';x.font='800 9px Arial';x.textAlign='left';x.fillText('POWER',18,28);x.fillStyle='rgba(17,17,17,.15)';x.fillRect(18,38,120,8);x.fillStyle='#ff5a1f';x.fillRect(18,38,120*power,8);x.setLineDash([5,5]);x.strokeStyle='rgba(255,90,31,.65)';x.beginPath();x.moveTo(52,H-60);x.quadraticCurveTo(W*.42,H*.15,target.x+target.w/2,H-52);x.stroke();x.setLineDash([])}if(state==='fly'){x.fillStyle='#ff5a1f';x.beginPath();x.arc(shot.x,shot.y,14,0,Math.PI*2);x.fill();x.fillStyle='#fff';x.font='800 8px Arial';x.textAlign='center';x.fillText('Q',shot.x,shot.y+3)}if(state==='done'){x.fillStyle='rgba(17,17,17,.84)';x.fillRect(12,H/2-42,W-24,84);x.fillStyle='#f3f0e9';x.font='800 20px Arial';x.textAlign='center';x.fillText(hits+' / '+rs.length+' ТОЧНО',W/2,H/2+7)}}
function begin(e){e.preventDefault();if(state==='aim')charging=true}function stop(e){e.preventDefault();if(charging)fire()}fireBtn.addEventListener('pointerdown',begin,{passive:false});fireBtn.addEventListener('pointerup',stop,{passive:false});fireBtn.addEventListener('pointercancel',stop,{passive:false});fireBtn.addEventListener('pointerleave',e=>{if(charging)stop(e)});document.getElementById('reset').onclick=()=>{round=0;hits=0;state='aim';power=.25;charging=false;scoreEl.textContent='0 / 5';status.textContent='Удерживайте «Сила», затем отпустите. Задача — посадить запрос на правильную страницу.';setRound();track('reset')};if('ResizeObserver'in window)new ResizeObserver(fit).observe(c);else addEventListener('resize',fit);requestAnimationFrame(fit);last=performance.now();raf=requestAnimationFrame(loop)})();</script></body></html>