"""The "Web agent" view: give the agent any task and watch it work in a real browser.
One gr.HTML with its own markup, styles and script (like setup_bar.py and shot_tool.py).
The script drives the run through server functions: web_start, then web_plan / web_act
for every step, so it can animate the agent's cursor between the two calls.
"""
SUGGESTIONS = [ # short tasks first: a visitor's daily GPU quota covers a few minutes of steps
{"icon": "paper", "title": "The DeskForge paper",
"task": "Find the DeskForge paper on arXiv and tell me who its first author is."},
{"icon": "weather", "title": "Weekend weather",
"task": "What is the weather forecast for Zurich this weekend?"},
{"icon": "trend", "title": "Trending on Hugging Face",
"task": "Open huggingface.co, go to its Models page, and tell me which model is trending first right now."},
{"icon": "tag", "title": "Docling's latest release",
"task": "Find the Docling repository on GitHub and tell me its latest release."},
{"icon": "paddle", "title": "A table tennis racket",
"task": "Search for table tennis rackets and tell me the name and price of the first result."},
{"icon": "plane", "title": "Flights to Zurich", "long": True,
"task": "On Google Flights, find the cheapest one-way flight from San Francisco to Zurich on November 12."},
]
TEMPLATE = '
'
LOGO = (' ')
SCRIPT = r"""
const SUG = props.suggestions || [];
const LOGO = props.logo || '';
const root = element.querySelector('.dwa') || element;
const esc = s => String(s ?? '').replace(/[&<>"]/g, c => ({'&': '&', '<': '<', '>': '>', '"': '"'}[c]));
const sleep = ms => new Promise(r => setTimeout(r, ms));
let CLIENT = '';
try { CLIENT = sessionStorage.getItem('dwa-client') || ''; } catch (e) {}
if (!CLIENT) { CLIENT = Math.random().toString(36).slice(2) + Date.now().toString(36); try { sessionStorage.setItem('dwa-client', CLIENT); } catch (e) {} }
const S = { phase: 'idle', run: null, task: '', t0: 0, step: 0, maxSteps: 30, url: '', title: '', front: 0, last: null };
// Small stroke icons (24 x 24), drawn for this view so they look the same on every platform.
const svg = (d, extra) => `${extra || ''} `;
const ICONS = {
plane: svg('M21 15.5 13.5 12V6a1.5 1.5 0 0 0-3 0v6L3 15.5V17l7.5-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5l-2-1.5v-4.5L21 17z'),
paddle: svg('M14.3 14.3l5.2 5.2', ' '),
paper: svg('M6 2h9l5 5v15H6zM15 2v5h5M9 13h8M9 17h6'),
weather: svg('M8 2v1.5M2 8h1.5M3.8 3.8l1 1M12.2 3.8l-1 1M7 20h10a4 4 0 0 0 0-8 5 5 0 0 0-9.6 1.5A3.3 3.3 0 0 0 7 20z', ' '),
trend: svg('M3 17l6-6 4 4 8-8M15 7h6v6'),
star: svg('M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1-4.4-4.3 6.1-.9z'),
tag: svg('M20.6 13.4l-7.2 7.2a2 2 0 0 1-2.8 0L3 13V3h10l7.6 7.6a2 2 0 0 1 0 2.8zM7.5 7.5h.01'),
train: svg('M8 21l2-3M16 21l-2-3M7 14h.01M17 14h.01M6 3h12a2 2 0 0 1 2 2v9a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V5a2 2 0 0 1 2-2zM4 10h16'),
book: svg('M4 4.5A2.5 2.5 0 0 1 6.5 2H20v17H6.5A2.5 2.5 0 0 0 4 21.5zM4 21.5A2.5 2.5 0 0 0 6.5 22H20'),
planner: svg('M12 2l2.4 7.4L22 12l-7.6 2.6L12 22l-2.4-7.4L2 12l7.6-2.6z'),
clicker: svg('M12 1v4M12 19v4M1 12h4M19 12h4', ' '),
click: svg('M5 3l14 7-6 2-2 6z'),
type: svg('M6 10h.01M10 10h.01M14 10h.01M18 10h.01M7 14h10', ' '),
press: svg('M9 10l-5 5 5 5M20 4v7a4 4 0 0 1-4 4H4'),
scroll: svg('M12 3v18M8 7l4-4 4 4M8 17l4 4 4-4'),
back: svg('M19 12H5M12 19l-7-7 7-7'),
globe: svg('M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18', ' '),
wait: svg('M12 7v5l3 2', ' '),
finish: svg('M5 12l5 5 9-10'),
drag: svg('M5 9l-3 3 3 3M9 5l3-3 3 3M15 19l-3 3-3-3M19 9l3 3-3 3M2 12h20M12 2v20'),
error: svg('M12 8v5M12 16.5h.01', ' '),
};
const ICON = { click: ICONS.click, double_click: ICONS.click, right_click: ICONS.click, hover: ICONS.click, drag: ICONS.drag,
type: ICONS.type, press: ICONS.press, scroll: ICONS.scroll, back: ICONS.back, wait: ICONS.wait, finish: ICONS.finish,
goto: ICONS.globe };
const VERB = { click: 'Click', double_click: 'Double-click', right_click: 'Right-click', hover: 'Hover', drag: 'Drag',
type: 'Type', press: 'Press', scroll: 'Scroll', back: 'Go back', wait: 'Wait', finish: 'Finish', goto: 'Open' };
const KEYCAP = k => ({ enter: '⏎ Enter', return: '⏎ Enter', tab: '⇥ Tab', escape: 'Esc', esc: 'Esc', backspace: '⌫',
ctrl: 'Ctrl', control: 'Ctrl', shift: '⇧ Shift', alt: 'Alt' }[String(k).toLowerCase()] || String(k));
root.innerHTML = `
New tab
LIVE
${LOGO} DeskForge web agent
Give it a task. Watch it use the web.
Qwen3.5-9B plans every step and DeskForge-Qwen3.5-4B , fine-tuned on DeskForge-1M, finds exactly where to click, in a real browser running in this Space.
${SUG.map((s, i) => `${ICONS[s.icon] || ''}
${esc(s.title)}${s.long ? 'longer task ' : ''} ${esc(s.task)} `).join('')}
Reading the page…
Agent log
${ICONS.planner} Planner Qwen3.5-9B decides the next step
${ICONS.clicker} Clicker DeskForge-Qwen3.5-4B finds the spot
Steps appear here as the agent works: what it decided, where it clicked, and how long each step took.
`;
const $ = s => root.querySelector(s);
const ui = { input: $('.dwa-input'), go: $('.dwa-go'), stop: $('.dwa-stop'), timer: $('.dwa-timer'), ntp: $('.dwa-ntp'),
screen: $('.dwa-screen'), imgs: root.querySelectorAll('.dwa-img'), fx: $('.dwa-fx'), cursor: $('.dwa-cursor'),
think: $('.dwa-think'), thinkT: $('.dwa-think-t'), result: $('.dwa-result'), live: $('.dwa-live'), stepn: $('.dwa-stepn'),
tabT: $('.dwa-tab-t'), fav: $('.dwa-fav'), url: $('.dwa-url-t'), lock: $('.dwa-lock'), log: $('.dwa-log'), scan: $('.dwa-scan') };
function showImage(uri) {
if (!uri) return Promise.resolve();
return new Promise(res => {
const back = ui.imgs[1 - S.front], front = ui.imgs[S.front];
back.onload = () => { back.classList.add('on'); front.classList.remove('on'); S.front = 1 - S.front; res(); };
back.onerror = () => res();
back.src = uri;
});
}
function setPage(url, title) {
if (url == null) return;
S.url = url; S.title = title || '';
let host = '';
try { host = new URL(url).hostname.replace(/^www\./, ''); } catch (e) {}
ui.url.textContent = url.replace(/^https?:\/\//, '');
ui.lock.style.visibility = url.startsWith('https:') ? 'visible' : 'hidden';
ui.tabT.textContent = title || host || 'Loading…';
ui.fav.textContent = (host[0] || '•').toUpperCase();
}
function setThinking(on, text) {
ui.think.hidden = !on; ui.scan.classList.toggle('on', on);
if (text) ui.thinkT.textContent = text;
ui.cursor.classList.toggle('idle', on);
}
function tick() {
if (S.phase !== 'running') return;
const s = (Date.now() - S.t0) / 1000;
ui.timer.textContent = `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
setTimeout(tick, 250);
}
function describe(p) {
const a = p.action;
if (a === 'type') return `“${p.text}”` + (p.submit ? ' ⏎' : '');
if (a === 'press') return (p.keys || []).map(KEYCAP).join(' + ');
if (a === 'scroll') return `${p.direction} · ${p.target}`;
if (a === 'drag') return `${p.target} → ${p.destination}`;
if (a === 'finish') return p.answer || 'Done';
if (a === 'goto') return p.url || '';
return p.target || '';
}
function addCard(p) {
const empty = ui.log.querySelector('.dwa-empty'); if (empty) empty.remove();
const card = document.createElement('div');
const plan = p.plan || {};
const a = plan.action || 'error';
card.className = 'dwa-card' + (p.error ? ' warn' : '') + (a === 'finish' ? ' fin' : '');
card.innerHTML = `${String(p.step).padStart(2, '0')}
${p.error ? ICONS.error : (ICON[a] || '')} ${p.error ? 'Retrying' : esc(VERB[a] || a)}
${(p.t_total || 0).toFixed(1)}s
${p.error ? `${esc(p.error)}
` : `${esc(describe(plan))}
`}
${plan.memory ? `${esc(plan.memory)}
` : ''}
${p.error ? '' : `plan ${(p.t_plan || 0).toFixed(1)}s${plan.target ? ` · locate ${(p.t_ground || 0).toFixed(1)}s` : ''}
`}`;
ui.log.appendChild(card);
ui.log.scrollTop = ui.log.scrollHeight;
return card;
}
function place(el, x, y) { el.style.left = (x * 100) + '%'; el.style.top = (y * 100) + '%'; }
function bubble(x, y, html, cls) {
const b = document.createElement('div');
b.className = 'dwa-bub ' + (cls || '') + (y < 0.18 ? ' below' : '') + (x > 0.78 ? ' left' : x < 0.12 ? ' right' : '');
b.innerHTML = html; place(b, x, y); ui.fx.appendChild(b);
return b;
}
function ripple(x, y) {
const r = document.createElement('div'); r.className = 'dwa-ripple'; place(r, x, y); ui.fx.appendChild(r);
const r2 = document.createElement('div'); r2.className = 'dwa-ripple two'; place(r2, x, y); ui.fx.appendChild(r2);
setTimeout(() => { r.remove(); r2.remove(); }, 1100);
}
async function animate(p) {
ui.fx.innerHTML = '';
const plan = p.plan || {}, a = plan.action, pts = p.points || [];
if (pts.length) {
const [x, y] = pts[0];
const b = bubble(x, y, `${esc(VERB[a] || a)} ${esc(plan.target || '')}`, 'target');
place(ui.cursor, x, y);
await sleep(720);
if (a === 'click' || a === 'double_click' || a === 'right_click') { ripple(x, y); if (a === 'double_click') { await sleep(160); ripple(x, y); } }
if (a === 'drag' && pts[1]) { place(ui.cursor, pts[1][0], pts[1][1]); await sleep(700); }
if (a === 'scroll') { const s = bubble(x, y, plan.direction === 'up' ? '▲▲' : plan.direction === 'down' ? '▼▼' : plan.direction === 'left' ? '◀◀' : '▶▶', 'scroll'); }
setTimeout(() => b.classList.add('fade'), 900);
await sleep(380);
} else if (a === 'type') {
const cx = parseFloat(ui.cursor.style.left) / 100 || 0.5, cy = parseFloat(ui.cursor.style.top) / 100 || 0.5;
const b = bubble(cx, cy, 'Type ', 'typing');
const out = b.querySelector('.dwa-typed'), text = plan.text || '';
const per = Math.max(12, Math.min(45, 900 / Math.max(1, text.length)));
for (const ch of text) { out.textContent += ch; await sleep(per); }
if (plan.submit) { b.insertAdjacentHTML('beforeend', '⏎ '); }
await sleep(250);
} else if (a === 'press') {
const cx = parseFloat(ui.cursor.style.left) / 100 || 0.5, cy = parseFloat(ui.cursor.style.top) / 100 || 0.5;
bubble(cx, cy, (plan.keys || []).map(k => `${esc(KEYCAP(k))} `).join('+ '), 'keys');
await sleep(650);
} else if (a === 'goto') {
// the address is typed into the window's URL bar
const u = (plan.url || '').replace(/^https?:\/\//, ''), bar = ui.url.parentElement;
bar.classList.add('typing'); ui.url.textContent = '';
const per = Math.max(10, Math.min(35, 700 / Math.max(1, u.length)));
for (const ch of u) { ui.url.textContent += ch; await sleep(per); }
await sleep(250); bar.classList.remove('typing');
} else if (a === 'back') {
bubble(0.06, 0.04, '← Back', 'chip below right'); await sleep(500);
} else if (a === 'wait') {
await sleep(400);
}
}
function showResult(r) {
const quota = !r.answer && friendlyGpuError(r.note);
if (quota) { // the visitor's daily ZeroGPU quota ran out: say so plainly, with a way to get more
ui.result.className = 'dwa-result muted';
ui.result.innerHTML = `${esc(quota.title)}
${esc(quota.text)}
`;
ui.result.hidden = false;
const card = document.createElement('div');
card.className = 'dwa-final muted';
card.innerHTML = `${esc(quota.title)}
${esc(quota.text)}
${esc(quota.detail)}
`;
ui.log.appendChild(card); ui.log.scrollTop = ui.log.scrollHeight;
S.last = '';
return;
}
const ok = !r.note || r.answer;
ui.result.className = 'dwa-result' + (ok ? '' : ' muted');
ui.result.innerHTML = `${ok ? '✓ Done' : 'Stopped'}
${r.steps ?? S.step} steps · ${Math.round(r.seconds ?? (Date.now() - S.t0) / 1000)} s
${esc(r.answer || r.note || 'The run ended.')}
`;
ui.result.hidden = false;
const card = document.createElement('div');
card.className = 'dwa-final' + (ok ? '' : ' muted');
card.innerHTML = `${ok ? '✓ Result' : 'Stopped'}
${esc(r.answer || r.note || '')}
Try another task ${r.answer ? 'Copy ' : ''}
`;
ui.log.appendChild(card); ui.log.scrollTop = ui.log.scrollHeight;
S.last = r.answer || '';
}
function setPhase(ph) {
S.phase = ph;
const running = ph === 'running' || ph === 'starting' || ph === 'waiting';
ui.go.hidden = running; ui.stop.hidden = !running; ui.timer.hidden = ph === 'idle';
ui.input.readOnly = running; ui.live.hidden = ph !== 'running';
root.classList.toggle('is-running', running);
}
async function start(task) {
task = (task || ui.input.value || '').trim();
if (task.length < 4) { ui.input.focus(); ui.input.classList.add('shake'); setTimeout(() => ui.input.classList.remove('shake'), 400); return; }
ui.input.value = task; S.task = task; S.step = 0;
ui.log.innerHTML = ''; ui.result.hidden = true; ui.fx.innerHTML = '';
setPhase('starting'); S.t0 = Date.now(); tick();
ui.ntp.hidden = true; ui.screen.hidden = false;
setThinking(true, 'Opening a fresh browser…');
let r = null;
for (let tries = 0; tries < 60 && (S.phase === 'starting' || S.phase === 'waiting'); tries++) {
try { r = await server.web_start(task, CLIENT); } catch (e) { r = { error: String(e && e.message || e) }; }
if (!r.busy) break;
setPhase('waiting'); setThinking(true, 'All browsers are busy right now. Waiting for a free one…');
await sleep(3000);
}
if (S.phase !== 'starting' && S.phase !== 'waiting') return; // stopped while waiting
if (r && r.busy) { fail('The demo is very busy right now. Please try again in a minute.'); return; }
if (!r || r.error || !r.run) { fail(r && r.error || 'Could not start a browser.'); return; }
S.run = r.run; S.maxSteps = r.max_steps || 30;
await showImage(r.image); setPage(r.url, r.title);
setPhase('running'); S.t0 = Date.now(); tick();
loop();
}
async function loop() {
while (S.phase === 'running') {
setThinking(true, S.step === 0 ? 'Reading the page and planning the first step…' : 'Reading the page…');
let p;
try { p = await gpuCall('web_plan', { run: S.run }); } catch (e) { fail(String(e && e.message || e)); return; }
if (S.phase !== 'running') return;
if (p.image) await showImage(p.image);
setPage(p.url, p.title);
if (p.done) { setThinking(false); end(p); return; }
S.step = p.step; ui.stepn.textContent = ` · step ${p.step}`;
setThinking(false);
const card = addCard(p);
if (p.error) { continue; }
const actP = server.web_act(S.run);
let a;
try { [a] = await Promise.all([actP, animate(p)]); } catch (e) { fail(String(e && e.message || e)); return; }
if (S.phase !== 'running') return;
if (a.image) await showImage(a.image);
setPage(a.url, a.title);
if (a.error) { card.classList.add('warn'); card.insertAdjacentHTML('beforeend', `${esc(a.error)}
`); }
if (a.blocked) { card.classList.add('shield'); card.insertAdjacentHTML('beforeend', `🛡 ${esc(a.blocked)}
`); }
if (a.done) { end(a); return; }
}
}
function end(r) { setPhase('done'); ui.fx.innerHTML = ''; showResult(r); }
function fail(msg) {
setPhase('done'); setThinking(false);
showResult({ note: msg, answer: '', steps: S.step });
}
async function stop() {
const run = S.run; setPhase('done'); setThinking(false);
showResult({ note: 'Stopped by you.', answer: '', steps: S.step });
if (run) { try { await server.web_stop(run); } catch (e) {} }
}
root.addEventListener('submit', e => { e.preventDefault(); if (S.phase !== 'running' && S.phase !== 'starting') start(); });
root.addEventListener('click', e => {
const t = e.target.closest('[data-sug],[data-act],.dwa-stop');
if (!t) return;
if (t.classList.contains('dwa-stop')) { stop(); return; }
if (t.dataset.sug) { const s = SUG[+t.dataset.sug]; ui.input.value = s.task; start(s.task); return; }
if (t.dataset.act === 'again') {
setPhase('idle'); ui.screen.hidden = true; ui.ntp.hidden = false; ui.result.hidden = true; ui.fx.innerHTML = '';
ui.log.innerHTML = 'Steps appear here as the agent works.
';
setPage('deskforge://new-tab', 'New tab'); ui.input.value = ''; ui.input.focus();
}
if (t.dataset.act === 'copy' && S.last) { navigator.clipboard && navigator.clipboard.writeText(S.last); t.textContent = 'Copied'; }
});
window.addEventListener('pagehide', () => { if (S.run && S.phase === 'running') { try { server.web_stop(S.run); } catch (e) {} } });
"""
CSS = """
#df-view-web { background: #F3EDE2; padding: 0 !important; }
#df-view-web .dwa { font-family: 'Hanken Grotesk', system-ui, sans-serif; color: #2B2722; }
#df-view-web .dwa-shell { display: grid; grid-template-columns: minmax(0, 1fr) 380px; min-height: calc(100vh - 52px); }
@media (max-width: 1100px) { #df-view-web .dwa-shell { grid-template-columns: 1fr; } }
#df-view-web .dwa-main { padding: 18px 24px 16px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
#df-view-web .dwa-spacer { flex: 1; }
/* the task bar */
#df-view-web .dwa-ask { display: flex; align-items: center; gap: 10px; background: #fff; border: 1px solid #E0DCD6; border-radius: 14px;
padding: 7px 7px 7px 10px; box-shadow: 0 6px 22px rgba(60,40,20,.08); }
#df-view-web .dwa-ask:focus-within { border-color: #D2603A; box-shadow: 0 0 0 4px rgba(210,96,58,.10), 0 6px 22px rgba(60,40,20,.08); }
#df-view-web .dwa-ask-icon { width: 30px; height: 30px; flex: none; border-radius: 8px; background: #D2603A; display: flex; align-items: center; justify-content: center; }
#df-view-web .dwa-input { flex: 1; min-width: 0; font: 500 16px 'Hanken Grotesk', system-ui, sans-serif; color: #2B2722; border: none; outline: none;
background: transparent; padding: 8px 4px; }
#df-view-web .dwa-input::placeholder { color: #A39D94; }
#df-view-web .dwa-input.shake { animation: dwa-shake .35s; }
#df-view-web .dwa-go, #df-view-web .dwa-stop { font: 600 15px 'Hanken Grotesk', system-ui, sans-serif; border-radius: 10px; padding: 10px 18px; cursor: pointer; flex: none; }
#df-view-web .dwa-go { color: #fff; background: #D2603A; border: 1px solid #C0552F; }
#df-view-web .dwa-go:hover { filter: brightness(1.05); }
#df-view-web .dwa-stop { color: #A8452A; background: #FBF3EC; border: 1px solid #EBCDBF; }
#df-view-web .dwa-timer { font: 600 13px 'JetBrains Mono', monospace; color: #8a8378; font-variant-numeric: tabular-nums; }
#df-view-web [hidden] { display: none !important; }
/* the browser window */
#df-view-web .dwa-win { background: #fff; border: 1px solid #DDD8D1; border-radius: 12px; overflow: hidden;
box-shadow: 0 18px 50px rgba(60,40,20,.16), 0 2px 6px rgba(60,40,20,.06); }
#df-view-web .dwa-bar { height: 38px; display: flex; align-items: center; gap: 12px; padding: 0 12px; background: #EFEAE2; }
#df-view-web .dwa-dots { display: flex; gap: 7px; }
#df-view-web .dwa-dots i { width: 11px; height: 11px; border-radius: 50%; background: #E46A5E; display: block; }
#df-view-web .dwa-dots i + i { background: #E9B44C; } #df-view-web .dwa-dots i + i + i { background: #62C554; }
#df-view-web .dwa-tab { align-self: flex-end; height: 31px; max-width: 300px; display: flex; align-items: center; gap: 8px; padding: 0 14px;
background: #fff; border-radius: 9px 9px 0 0; font-size: 13px; color: #3a332b; min-width: 0; }
#df-view-web .dwa-tab-t { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#df-view-web .dwa-fav { width: 16px; height: 16px; flex: none; border-radius: 4px; background: #E9E4DC; color: #6f6a62; font: 700 10px/16px 'Hanken Grotesk', sans-serif; text-align: center; }
#df-view-web .dwa-live { display: flex; align-items: center; gap: 7px; font: 700 11px 'JetBrains Mono', monospace; letter-spacing: .08em; color: #C0552F;
background: #fff; border: 1px solid #EBCDBF; border-radius: 999px; padding: 4px 10px; }
#df-view-web .dwa-stepn { letter-spacing: 0; font-weight: 500; color: #8a8378; }
#df-view-web .dwa-led { width: 7px; height: 7px; border-radius: 50%; background: #D2603A; animation: dwa-pulse 1.4s ease-in-out infinite; }
#df-view-web .dwa-omni { height: 40px; display: flex; align-items: center; gap: 12px; padding: 0 12px; border-bottom: 1px solid #EEE9E2; background: #fff; }
#df-view-web .dwa-nav { font-size: 15px; color: #B3ADA4; letter-spacing: .25em; flex: none; }
#df-view-web .dwa-url { flex: 1; min-width: 0; height: 28px; display: flex; align-items: center; gap: 7px; padding: 0 12px; background: #F4F1EC; border-radius: 999px;
font: 13px 'Hanken Grotesk', system-ui, sans-serif; color: #4a443c; }
#df-view-web .dwa-url-t { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#df-view-web .dwa-url.typing { background: #fff; box-shadow: inset 0 0 0 2px #D2603A; }
#df-view-web .dwa-lock { display: inline-flex; color: #8a8378; }
#df-view-web .dwa-view { position: relative; aspect-ratio: 16 / 10; background: #FBF9F6; overflow: hidden; }
/* new tab page */
#df-view-web .dwa-ntp { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
padding: 28px; text-align: center; background: radial-gradient(1200px 500px at 50% -10%, #FBEDE3 0%, #FBF9F6 60%); overflow: auto; }
#df-view-web .dwa-ntp-mark { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 15px; color: #2B2722; }
#df-view-web .dwa-ntp-mark span:last-child { color: #8a8378; font-weight: 500; }
#df-view-web .dwa-ntp-logo { width: 28px; height: 28px; border-radius: 8px; background: #D2603A; display: inline-flex; align-items: center; justify-content: center; }
#df-view-web .dwa-ntp h2 { font: 600 clamp(22px, 3vw, 34px)/1.15 'Newsreader', Georgia, serif; letter-spacing: -.01em; margin: 0; color: #2B2722; }
#df-view-web .dwa-ntp p { max-width: 640px; margin: 0; font-size: 15px; line-height: 1.55; color: #5f594f; }
#df-view-web .dwa-acc { color: #C0552F; }
#df-view-web .dwa-sugs { width: 100%; max-width: 860px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 6px; }
@media (max-width: 760px) { #df-view-web .dwa-sugs { grid-template-columns: 1fr 1fr; } }
#df-view-web .dwa-sug { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 10px; row-gap: 2px; text-align: left;
background: #fff; border: 1px solid #E6E3DF; border-radius: 12px; padding: 11px 12px; cursor: pointer; font-family: inherit; color: inherit;
box-shadow: 0 1px 2px rgba(40,30,20,.04); transition: transform .15s, border-color .15s, box-shadow .15s; }
#df-view-web .dwa-sug:hover { transform: translateY(-2px); border-color: #D2603A; box-shadow: 0 8px 20px rgba(60,40,20,.10); }
#df-view-web .dwa-sug-i { grid-row: span 2; width: 34px; height: 34px; border-radius: 9px; background: #FBF3EC; color: #C0552F;
display: flex; align-items: center; justify-content: center; }
#df-view-web .dwa-sug-t { font-weight: 700; font-size: 14px; }
#df-view-web .dwa-long { margin-left: 7px; font: 600 10.5px 'JetBrains Mono', monospace; letter-spacing: .03em; color: #8a6f45;
background: #F6EFE2; border: 1px solid #E8DCC6; border-radius: 999px; padding: 1px 7px; vertical-align: 1px; white-space: nowrap; }
#df-view-web .dwa-signin { display: inline-block; margin-top: 8px; font: 600 13px 'Hanken Grotesk', sans-serif; color: #fff !important;
background: #D2603A; border-radius: 8px; padding: 7px 12px; text-decoration: none !important; }
#df-view-web .dwa-sug-d { font-size: 12.5px; line-height: 1.4; color: #7a7369; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* the live screen and its effects */
#df-view-web .dwa-screen { position: absolute; inset: 0; }
#df-view-web .dwa-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; opacity: 0; transition: opacity .22s ease; user-select: none; }
#df-view-web .dwa-img.on { opacity: 1; }
#df-view-web .dwa-scan { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity .3s;
background: linear-gradient(180deg, rgba(210,96,58,0) 0%, rgba(210,96,58,.10) 48%, rgba(210,96,58,.0) 52%); background-size: 100% 220%; }
#df-view-web .dwa-scan.on { opacity: 1; animation: dwa-scan 2.4s linear infinite; }
#df-view-web .dwa-fx { position: absolute; inset: 0; pointer-events: none; }
#df-view-web .dwa-cursor { position: absolute; z-index: 5; pointer-events: none; transform: translate(-4px, -3px);
transition: left .7s cubic-bezier(.45,.05,.2,1), top .7s cubic-bezier(.45,.05,.2,1); filter: drop-shadow(0 3px 5px rgba(0,0,0,.28)); }
#df-view-web .dwa-cursor.idle svg { animation: dwa-bob 1.6s ease-in-out infinite; }
#df-view-web .dwa-ctag { position: absolute; left: 20px; top: 21px; font: 700 11.5px 'Hanken Grotesk', sans-serif; color: #fff; background: #D2603A;
padding: 3px 8px; border-radius: 6px 6px 6px 2px; white-space: nowrap; }
#df-view-web .dwa-ripple { position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px; border-radius: 50%; border: 3px solid #D2603A;
animation: dwa-ripple .9s ease-out forwards; }
#df-view-web .dwa-ripple.two { animation-delay: .14s; border-color: rgba(210,96,58,.55); }
#df-view-web .dwa-bub { position: absolute; z-index: 4; transform: translate(-50%, calc(-100% - 22px)); max-width: 46%; background: rgba(43,39,34,.92); color: #fff;
font: 600 13px/1.35 'Hanken Grotesk', sans-serif; padding: 7px 11px; border-radius: 10px; box-shadow: 0 8px 22px rgba(0,0,0,.22);
animation: dwa-pop .22s ease-out; transition: opacity .4s; }
#df-view-web .dwa-bub.below { transform: translate(-50%, 26px); }
#df-view-web .dwa-bub.left { transform: translate(calc(-100% + 30px), calc(-100% - 22px)); }
#df-view-web .dwa-bub.right { transform: translate(-20px, calc(-100% - 22px)); }
#df-view-web .dwa-bub.below.left { transform: translate(calc(-100% + 30px), 26px); }
#df-view-web .dwa-bub.below.right { transform: translate(-20px, 26px); }
#df-view-web .dwa-bub.fade { opacity: 0; }
#df-view-web .dwa-bub-v { display: inline-block; font: 700 10.5px 'JetBrains Mono', monospace; letter-spacing: .06em; text-transform: uppercase;
color: #F2B49E; margin-right: 7px; }
#df-view-web .dwa-bub.typing { background: #fff; color: #2B2722; border: 2px solid #D2603A; font: 600 14px 'JetBrains Mono', monospace; }
#df-view-web .dwa-bub.typing .dwa-bub-v { color: #C0552F; }
#df-view-web .dwa-caret { display: inline-block; width: 2px; height: 1.05em; background: #D2603A; vertical-align: -2px; margin-left: 1px; animation: dwa-blink .9s step-end infinite; }
#df-view-web .dwa-bub.keys { background: transparent; box-shadow: none; padding: 0; }
#df-view-web kbd { display: inline-block; font: 700 14px 'JetBrains Mono', monospace; color: #2B2722; background: #fff; border: 1px solid #D6D1CA;
border-bottom-width: 3px; border-radius: 8px; padding: 6px 10px; box-shadow: 0 6px 16px rgba(0,0,0,.18); }
#df-view-web .dwa-plus { color: #fff; margin: 0 5px; font-weight: 700; text-shadow: 0 1px 3px rgba(0,0,0,.5); }
#df-view-web .dwa-enter { margin-left: 8px; padding: 1px 7px; font-size: 12px; box-shadow: none; }
#df-view-web .dwa-bub.scroll { background: #D2603A; font-size: 12px; letter-spacing: -.1em; transform: translate(-50%, 18px); }
#df-view-web .dwa-bub.chip { left: 12px !important; top: 12px !important; transform: none; }
#df-view-web .dwa-think { position: absolute; left: 50%; top: 14px; transform: translateX(-50%); z-index: 6; display: flex; align-items: center; gap: 9px;
background: rgba(255,255,255,.96); border: 1px solid #E6E3DF; border-radius: 999px; padding: 7px 14px 7px 10px; font-size: 13.5px; font-weight: 600;
color: #3a332b; box-shadow: 0 8px 24px rgba(60,40,20,.14); white-space: nowrap; }
#df-view-web .dwa-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid #EBCDBF; border-top-color: #D2603A; animation: df-spin .7s linear infinite; }
#df-view-web .dwa-result { position: absolute; left: 50%; bottom: 18px; transform: translateX(-50%); width: min(720px, calc(100% - 36px)); z-index: 7;
background: rgba(255,255,255,.97); border: 1px solid #E6E3DF; border-left: 4px solid #4D9A43; border-radius: 12px; padding: 14px 16px;
box-shadow: 0 16px 40px rgba(40,30,20,.22); animation: dwa-rise .35s ease-out; }
#df-view-web .dwa-result.muted { border-left-color: #C9B79A; }
#df-view-web .dwa-res-h { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 14px; color: #3f7a34; }
#df-view-web .dwa-result.muted .dwa-res-h { color: #8a6f45; }
#df-view-web .dwa-check { width: 20px; height: 20px; border-radius: 50%; background: #4D9A43; color: #fff; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; }
#df-view-web .dwa-res-m { margin-left: auto; font: 500 12px 'JetBrains Mono', monospace; color: #9a9389; }
#df-view-web .dwa-res-a { margin-top: 6px; font-size: 16px; line-height: 1.5; color: #2B2722; }
#df-view-web .dwa-foot { font: italic 13.5px/1.5 'Newsreader', Georgia, serif; color: #8a8378; padding: 0 4px; }
#df-view-web .dwa-thanks { display: block; margin-top: 4px; }
#df-view-web .dwa-foot a { color: #C0552F; text-decoration: underline; text-underline-offset: 2px; }
/* the agent log */
#df-view-web .dwa-side { background: #FBF6EE; border-left: 1px solid #E6E3DF; display: flex; flex-direction: column; min-height: 0; }
#df-view-web .dwa-side-head { height: 46px; display: flex; align-items: center; padding: 0 16px; border-bottom: 1px solid #E6E3DF; font-weight: 700; font-size: 14px; }
#df-view-web .dwa-models { display: grid; gap: 8px; padding: 12px 14px; border-bottom: 1px solid #E6E3DF; }
#df-view-web .dwa-model { display: flex; align-items: center; gap: 10px; background: #fff; border: 1px solid #E6E3DF; border-radius: 10px; padding: 8px 10px; font-size: 13px; color: #5f594f; }
#df-view-web .dwa-model b { display: block; font-size: 11px; letter-spacing: .07em; text-transform: uppercase; color: #3a332b; }
#df-view-web .dwa-model-df { border-color: #EBCDBF; background: #FFFBF8; }
#df-view-web .dwa-model-df b { color: #C0552F; }
#df-view-web .dwa-mi { width: 30px; height: 30px; flex: none; border-radius: 8px; background: #F4F1EC; color: #6f6a62; display: flex;
align-items: center; justify-content: center; }
#df-view-web .dwa-model-df .dwa-mi { background: #FBF3EC; color: #C0552F; }
#df-view-web .dwa-log { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 9px; max-height: calc(100vh - 210px); min-height: 240px; }
#df-view-web .dwa-log > * { flex-shrink: 0; }
#df-view-web .dwa-empty { font: italic 14px/1.5 'Newsreader', Georgia, serif; color: #8a8378; }
#df-view-web .dwa-card { background: #fff; border: 1px solid #E6E3DF; border-radius: 10px; padding: 9px 11px; animation: dwa-in .2s ease-out;
box-shadow: 0 1px 2px rgba(40,30,20,.04); }
#df-view-web .dwa-card.warn { border-color: #EED9B5; background: #FFFCF5; }
#df-view-web .dwa-card.fin { border-color: #CFE3C9; }
#df-view-web .dwa-card-h { display: flex; align-items: center; gap: 8px; }
#df-view-web .dwa-num { font: 600 11px 'JetBrains Mono', monospace; color: #A39D94; }
#df-view-web .dwa-ico { width: 24px; height: 24px; border-radius: 7px; background: #FBF3EC; color: #C0552F; display: inline-flex; align-items: center;
justify-content: center; }
#df-view-web .dwa-ico svg { width: 15px; height: 15px; }
#df-view-web .dwa-card.warn .dwa-ico { background: #FFF3DC; color: #9a6a1f; }
#df-view-web .dwa-card.fin .dwa-ico { background: #EAF3E6; color: #3f7a34; }
#df-view-web .dwa-verb { font-weight: 700; font-size: 13.5px; }
#df-view-web .dwa-t { font: 500 11.5px 'JetBrains Mono', monospace; color: #A39D94; }
#df-view-web .dwa-what { margin-top: 4px; font-size: 13.5px; line-height: 1.45; color: #3a332b; overflow-wrap: anywhere; }
#df-view-web .dwa-err { color: #9a6a1f; font-size: 12.5px; }
#df-view-web .dwa-shield { color: #3B6EBE; font-size: 12.5px; }
#df-view-web .dwa-mem { margin-top: 4px; font: italic 13.5px/1.45 'Newsreader', Georgia, serif; color: #7a7369; }
#df-view-web .dwa-tm { margin-top: 4px; font: 500 11px 'JetBrains Mono', monospace; color: #B0AAA1; }
#df-view-web .dwa-final { background: #fff; border: 1px solid #CFE3C9; border-left: 4px solid #4D9A43; border-radius: 10px; padding: 11px 12px; animation: dwa-in .25s ease-out; }
#df-view-web .dwa-final.muted { border-color: #E6E3DF; border-left-color: #C9B79A; }
#df-view-web .dwa-final-h { font-weight: 700; font-size: 13px; color: #3f7a34; }
#df-view-web .dwa-final.muted .dwa-final-h { color: #8a6f45; }
#df-view-web .dwa-final-a { margin-top: 5px; font-size: 14.5px; line-height: 1.5; }
#df-view-web .dwa-final-r { display: flex; gap: 8px; margin-top: 10px; }
#df-view-web .dwa-final-r button { font: 600 13px 'Hanken Grotesk', sans-serif; color: #C0552F; background: #FBF3EC; border: 1px solid #EBCDBF;
border-radius: 8px; padding: 6px 11px; cursor: pointer; }
@keyframes dwa-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@keyframes dwa-scan { from { background-position: 0 120%; } to { background-position: 0 -120%; } }
@keyframes dwa-ripple { from { transform: scale(.4); opacity: 1; } to { transform: scale(3.2); opacity: 0; } }
@keyframes dwa-pop { from { opacity: 0; margin-top: 6px; } to { opacity: 1; margin-top: 0; } }
@keyframes dwa-rise { from { opacity: 0; bottom: 4px; } to { opacity: 1; bottom: 18px; } }
@keyframes dwa-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes dwa-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
@keyframes dwa-blink { 50% { opacity: 0; } }
@keyframes dwa-shake { 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } }
@media (prefers-reduced-motion: reduce) { #df-view-web * { animation: none !important; transition: none !important; } }
"""