[{"data":1,"prerenderedAt":250},["ShallowReactive",2],{"profile":3,"source-emoji":213},{"name":4,"meta":5,"statement":6,"lede":9,"description":10,"origin":11,"role":12,"work":13,"trajectory":59,"notes":89,"lab":92,"about":95,"contact":101,"home":115},"Chris Wijnia","Engineer · Co-founder",{"text":7,"accent":8},"I build software that holds up.","holds up.","I work out what’s worth building next.","Dutch engineer and co-founder. I work out what’s worth building next.","Netherlands","Product \u002F R&D",{"title":14,"accent":15,"outro":16,"projects":17},"A few things I’ve worked on.","worked on.","The work has changed a lot over the years. The habit of making things hasn’t.",[18,24,30,36,42,48,53],{"no":19,"name":20,"area":21,"period":22,"slug":23},"01","Agio Digital","Financial software \u002F Compliance","2021–Now","agio-digital",{"no":25,"name":26,"area":27,"period":28,"slug":29},"02","AI systems","Agents \u002F Automation \u002F Developer tools","2022–Now","ai-systems",{"no":31,"name":32,"area":33,"period":34,"slug":35},"03","1GRAM","Wallet software","2020–2021","1gram",{"no":37,"name":38,"area":39,"period":40,"slug":41},"04","MVRDV","Digital platform \u002F Architecture","2015–2019","mvrdv",{"no":43,"name":44,"area":45,"period":46,"slug":47},"05","JIJ & Overijssel","Public participation platform","2014–2018","jij-overijssel",{"no":49,"name":50,"area":51,"period":52,"slug":50},"06","100-100-100","Sustainability campaign platform","2014–2019",{"no":54,"name":55,"area":56,"period":57,"slug":58},"07","Archive","Design \u002F Web \u002F Software","2007–2022","archive",{"title":60,"eras":61},"Moving down the stack as the web evolved.",[62,66,70,73,77,81,85],{"year":63,"name":64,"line":65},"2005","Design","Learning the web by making things.",{"year":67,"name":68,"line":69},"2013","Product","Design became code.",{"name":71,"line":72},"Engineering","Code became systems.",{"year":74,"name":75,"line":76},"2018","Wallets","Software for a new kind of money.",{"year":78,"name":79,"line":80},"2021","Financial infra","Building systems where software meets regulation.",{"year":82,"name":83,"line":84},"2022","AI","From using models to orchestrating them.",{"year":86,"name":87,"line":88},"Now","Building","All of it, at once.",{"title":90,"intro":91},"Notes","Things I’m working on, learning or trying to understand.",{"title":93,"intro":94},"Small working things.","Games and toys built to find something out. Two of them let Jev play: chess, and a shared canvas of a million emoji.",{"title":96,"paragraphs":97},"I’ve been making things on computers for most of my life.",[98,99,100],"I grew up in the Netherlands and built my first websites at thirteen.","I like small teams, ambitious projects, good engineering and people who can disagree without making it tedious. I still enjoy opening an empty repository and figuring something out.","I work internationally. Outside work I train Muay Thai and like [chess](\u002Flab\u002Fchess), which I played competitively in Zwolle and taught to primary-school children there. I also build things that occasionally have no sensible commercial purpose.",{"title":102,"accent":103,"intro":104,"links":105},"Making something interesting?","interesting?","For collaborations, technical discussions, or a good reason I haven’t thought of.",[106,109,112],{"label":107,"href":108},"LinkedIn","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fchris-wijnia-1364a29b",{"label":110,"href":111},"GitHub","https:\u002F\u002Fgithub.com\u002Fcwdx",{"label":113,"href":114},"X","https:\u002F\u002Fx.com\u002FChrisWijnia",{"hero":116,"chapters":123,"principles":187,"work":203,"contact":207},{"headline":117,"lede":121,"cue":122},[118,119,120],"Every system","is one bit,","repeated.","Chris Wijnia is a Dutch engineer and co-founder. He works on financial software and AI systems.","Scroll to scale up",[124,135,147,161,175],{"id":125,"power":126,"name":127,"label":128,"formation":125,"cell":129,"headline":130,"body":133,"prompt":134},"bit","2^0","Bit","The bit · 1 state of 2",16,[131,132],"Yes.","Or no.","Approve the payment or hold it. Trust the answer or ask again. Ship the pixel or move it one to the left. Everything I build ends in a decision small enough to fit in one bit. The work is making sure it is the right one.","Flip the bit. This page has two states.",{"id":136,"power":137,"name":138,"label":139,"formation":136,"cell":140,"headline":141,"body":145,"prompt":146},"byte","2^3","Byte","The byte · design",8,[142,143,144],"Eight bits","make a","letter.","I started in design, placing pixels one at a time until a shape read as a word. Software is built the same way: from the smallest unit up.","Flip a bit. Make a letter.",{"id":148,"power":149,"name":150,"label":151,"formation":148,"cell":152,"ink":153,"headline":154,"body":159,"prompt":160},"ledger","2^10","Ledger","The ledger · financial software",4,true,[155,156,157,158],"Money is","the least","forgiving","data type.","In financial software nothing is probably right. No rounding error is too small to matter, and no state is allowed to drift. Every debit meets its credit. Every action leaves a record that adds up, today and in the audit years from now.","Simulated ledger",{"id":162,"power":163,"name":164,"label":165,"formation":162,"cell":166,"headline":167,"body":172,"prompt":173,"note":174},"weights","2^20","Weights","The weights · AI systems",2,[168,169,170,171],"A million","decisions,","none made","on purpose.","A model is a vast field of numbers nobody set by hand. Useful, and never quite certain. I build the part around it: where it may act alone, where it has to ask, and how anyone can check afterwards what it did and why.","Confidence","ai-makes-software-cheaper",{"id":176,"power":177,"name":178,"label":179,"formation":180,"cell":152,"headline":181,"body":186},"company","2^n","Company","The company · co-founder","knot",[182,183,184,185],"A company","is a system","with people","inside.","A company runs on what good software runs on: clear states, clean interfaces, and nothing that only works as long as someone remembers.",[188,191,194,197,200],{"name":189,"line":190},"Resolution is a choice","Decide how much detail a problem deserves, then commit to it.",{"name":192,"line":193},"Two states beat five","If a feature cannot say yes or no, it is not finished.",{"name":195,"line":196},"The ledger balances","Every action leaves a record, and the records add up.",{"name":198,"line":199},"The model proposes","A person signs. The system always knows which is which.",{"name":201,"line":202},"Every pixel on purpose","Including the ones nobody will ever notice.",{"label":204,"headline":205},"Work · 2007–now",[206],"Work.",{"label":208,"headline":209,"intro":212},"Back to one bit",[210,211],"Send","one bit.","Building something where a wrong answer costs money, or where a machine’s answer has to be trusted? Tell me in a line. I will answer in more than one bit.",{"files":214},[215,220,225,230,235,240,245],{"path":216,"lines":217,"code":218,"html":219},"app\u002Fcomponents\u002Flab\u002FEmojiCanvas.vue",339,"\u003Cscript setup lang=\"ts\">\nimport { cellAt, cellLabel, line, SIZE, xy } from '@cw\u002Femoji\u002Fcanvas'\nimport { emojiInfo } from '@cw\u002Femoji'\nimport { painterOf } from '#core\u002Fdomain\u002Fpainters'\nimport type { EmojiRenderer, EmojiScene } from '#infra\u002Fgraphics\u002Femoji-render'\nimport type { CanvasCell } from '~\u002Fcomposables\u002FuseEmojiCanvas'\n\nconst props = defineProps\u003C{\n  cells: Map\u003Cnumber, CanvasCell>\n  version: number\n  arrived: Map\u003Cnumber, number>\n  brush: string\n  tool: 'paint' | 'move' | 'pick'\n  ink: number\n  label: string\n  \u002F** Jev weighing its options: each shown faintly, by how likely it is. *\u002F\n  ghosts?: { cell: number; emoji: string; p: number }[]\n}>()\nconst emit = defineEmits\u003C{ stroke: [number[]]; pick: [string]; need: [number, number, number, number]; busy: [boolean] }>()\n\u002F\u002F the cell at the centre (fractional) and a cell's size in CSS pixels\nconst view = defineModel\u003C{ x: number; y: number; size: number }>('view', { required: true })\n\nconst MIN = 3, MAX = 64\nconst box = ref\u003CHTMLElement>()\nconst canvas = ref\u003CHTMLCanvasElement>()\nconst width = ref(0), height = ref(0)\nconst hover = ref\u003Cnumber>()\nconst stroke = ref\u003Cnumber[]>([])\nconst status = refAutoReset('', 1600)\nconst colors: Record\u003Cstring, string> = {}\n\nconst toCell = (px: number, py: number) => {\n  const s = view.value.size\n  return cellAt(Math.floor(view.value.x + (px - width.value \u002F 2) \u002F s), Math.floor(view.value.y + (py - height.value \u002F 2) \u002F s))\n}\nconst bounds = () => {\n  const s = view.value.size, hw = width.value \u002F 2 \u002F s, hh = height.value \u002F 2 \u002F s\n  return { x0: Math.floor(view.value.x - hw), y0: Math.floor(view.value.y - hh), x1: Math.ceil(view.value.x + hw), y1: Math.ceil(view.value.y + hh) }\n}\n\nlet renderer: EmojiRenderer | undefined\n\u002F\u002F on touch screens the grid's hairlines vanish, so alternate cells are shaded instead\nconst coarse = useMediaQuery('(pointer: coarse)')\nwatch(coarse, () => redraw())\n\u002F\u002F a ring, not the arrival flash: a cell marked as arrived is one the tiles must not overwrite\nconst pinged = refAutoReset\u003C{ cell: number; key: number } | undefined>(undefined, 2000)\nconst centreOf = (cell: number) => {\n  const { x, y } = xy(cell), s = view.value.size\n  return { left: `${width.value \u002F 2 + (x + 0.5 - view.value.x) * s}px`, top: `${height.value \u002F 2 + (y + 0.5 - view.value.y) * s}px` }\n}\nconst pingStyle = computed(() => (pinged.value ? { ...centreOf(pinged.value.cell), '--ring': `${Math.max(view.value.size, 20)}px` } : {}))\nconst ghostStyle = (g: { cell: number; p: number }) => ({ ...centreOf(g.cell), '--cell': `${view.value.size}px`, 'opacity': 0.3 + 0.7 * g.p })\nconst ping = (cell: number) => { pinged.value = { cell, key: Date.now() } }\nconst renderMode = ref\u003C'webgpu' | 'canvas'>()\nconst canvasKey = ref(0)\nlet frame = 0\nconst redraw = () => { cancelAnimationFrame(frame); frame = requestAnimationFrame(draw) }\nfunction draw() {\n  if (!renderer) return\n  const scene: EmojiScene = {\n    ...view.value, width: width.value, height: height.value, cells: props.cells, arrived: props.arrived,\n    pending: new Set(stroke.value), brush: props.brush, hover: hover.value, colors: colors as EmojiScene['colors'], checker: coarse.value,\n  }\n  if (renderer.render(scene)) redraw()\n}\nfunction resize() {\n  const el = box.value\n  if (!el) return\n  width.value = el.clientWidth\n  height.value = el.clientHeight\n  renderer?.resize(width.value, height.value)\n  view.value = clampView(view.value)\n  redraw()\n}\nfunction clampView(v: { x: number; y: number; size: number }) {\n  const size = Math.min(MAX, Math.max(MIN, v.size))\n  const keep = (c: number, half: number) => (half * 2 >= SIZE ? SIZE \u002F 2 : Math.min(SIZE - half, Math.max(half, c)))\n  return { size, x: keep(v.x, width.value \u002F 2 \u002F size), y: keep(v.y, height.value \u002F 2 \u002F size) }\n}\n\u002F\u002F Like a map: buttons, keys and a double-click zoom with an ease, a released drag glides on and slows, and any touch\n\u002F\u002F of the canvas stops either. Under reduced motion every change lands at once.\nconst reduced = useReducedMotion()\nlet easing = 0\nconst stopEasing = () => cancelAnimationFrame(easing)\nfunction easeTo(to: { x: number; y: number; size: number }, ms = 260) {\n  stopEasing()\n  const end = clampView(to), from = { ...view.value }, t0 = performance.now()\n  if (reduced) { view.value = end; return }\n  const step = (t: number) => {\n    const k = Math.min(1, (t - t0) \u002F ms), e = 1 - (1 - k) ** 3\n    \u002F\u002F the size on a log scale, so each step of the zoom feels even\n    view.value = { x: from.x + (end.x - from.x) * e, y: from.y + (end.y - from.y) * e, size: Math.exp(Math.log(from.size) + (Math.log(end.size) - Math.log(from.size)) * e) }\n    if (k \u003C 1) easing = requestAnimationFrame(step)\n  }\n  easing = requestAnimationFrame(step)\n}\n\u002F** The view after zooming by `factor` about the point (px, py), which stays under the pointer. *\u002F\nfunction zoomed(factor: number, px: number, py: number) {\n  const v = view.value, size = Math.min(MAX, Math.max(MIN, v.size * factor))\n  const wx = v.x + (px - width.value \u002F 2) \u002F v.size, wy = v.y + (py - height.value \u002F 2) \u002F v.size\n  return { size, x: wx - (px - width.value \u002F 2) \u002F size, y: wy - (py - height.value \u002F 2) \u002F size }\n}\nfunction zoomAt(factor: number, px = width.value \u002F 2, py = height.value \u002F 2, smooth = false) {\n  if (smooth) easeTo(zoomed(factor, px, py))\n  else { stopEasing(); view.value = clampView(zoomed(factor, px, py)) }\n}\nconst panBy = (dx: number, dy: number) => { const v = view.value; view.value = clampView({ ...v, x: v.x - dx \u002F v.size, y: v.y - dy \u002F v.size }) }\n\u002F\u002F the drag's speed, in CSS pixels a millisecond, smoothed over its last moves\nlet fling = { vx: 0, vy: 0, at: 0 }\nfunction glideOn() {\n  let { vx, vy } = fling, last = performance.now()\n  if (reduced || Math.hypot(vx, vy) \u003C 0.15) return\n  const step = (t: number) => {\n    const dt = Math.min(48, t - last), decay = 0.94 ** (dt \u002F 16)\n    last = t\n    panBy(vx * dt, vy * dt)\n    vx *= decay; vy *= decay\n    if (Math.hypot(vx, vy) > 0.02) easing = requestAnimationFrame(step)\n  }\n  stopEasing()\n  easing = requestAnimationFrame(step)\n}\n\u002F\u002F A double tap or click zooms in about that point (with shift, out), in every tool and on touch, where no dblclick comes.\n\u002F\u002F So a single tap on one cell paints only once no second tap follows: a drag paints at once.\nconst TAP_GAP = 250\nlet lastTap = { at: 0, x: 0, y: 0 }, downAt = 0\nconst tapped = useTimeoutFn((cells: number[]) => { emit('stroke', cells); stroke.value = []; redraw() }, TAP_GAP, { immediate: false })\n\nconst pointers = new Map\u003Cnumber, { x: number; y: number }>()\nlet mode: 'paint' | 'pan' | undefined\nlet space = false\nlet start = { x: 0, y: 0 }\nconst local = (e: PointerEvent | WheelEvent) => { const r = canvas.value!.getBoundingClientRect(); return { x: e.clientX - r.left, y: e.clientY - r.top } }\nfunction pickAt(p: { x: number; y: number }) {\n  const cell = toCell(p.x, p.y), c = cell === undefined ? undefined : props.cells.get(cell)\n  if (!c) { status.value = 'Nothing to pick up there'; return }\n  emit('pick', c.emoji)\n  status.value = `Picked ${c.emoji}`\n  navigator.vibrate?.(12)\n}\n\u002F\u002F until a hold is ruled out the stroke does not grow, so a wobble across a cell edge is not a stroke\nconst HOLD = 400\nlet slack = 6\nlet holding = false\nconst press = useTimeoutFn((p: { x: number; y: number }) => { if (mode === 'paint' && holding) { mode = undefined; holding = false; stroke.value = []; pickAt(p); redraw() } }, HOLD, { immediate: false })\n\u002F\u002F `busy` lets the page follow new paint only when that disturbs nobody\nconst TOUCH_REST = 4000\nconst rest = useTimeoutFn(() => emit('busy', false), TOUCH_REST, { immediate: false })\nfunction busy(on: boolean, touch = false) {\n  rest.stop()\n  if (on || !touch) emit('busy', on)\n  else rest.start()\n}\nfunction onDown(e: PointerEvent) {\n  stopEasing()\n  fling = { vx: 0, vy: 0, at: performance.now() }\n  if (e.pointerType !== 'mouse') busy(true, true)\n  try { canvas.value!.setPointerCapture(e.pointerId) } catch {}\n  const p = local(e)\n  pointers.set(e.pointerId, p)\n  const now = performance.now()\n  if (pointers.size === 1 && now - lastTap.at \u003C TAP_GAP + 50 && Math.hypot(p.x - lastTap.x, p.y - lastTap.y) \u003C 24) {\n    tapped.stop(); stroke.value = []; lastTap.at = 0; mode = undefined\n    zoomAt(e.shiftKey ? 0.5 : 2, p.x, p.y, true)\n    return\n  }\n  downAt = now\n  start = p\n  if (pointers.size === 2) { tapped.stop(); mode = 'pan'; stroke.value = []; press.stop(); return }\n  if (props.tool === 'pick' || e.altKey) { pickAt(p); mode = undefined; return }\n  if (props.tool === 'move' || e.button === 1 || e.button === 2 || space) { mode = 'pan'; return }\n  const cell = toCell(p.x, p.y)\n  if (cell === undefined || props.ink \u003C 1) { mode = undefined; if (props.ink \u003C 1) status.value = 'Out of ink'; return }\n  mode = 'paint'\n  stroke.value = [cell]\n  slack = e.pointerType === 'mouse' ? 6 : 14\n  holding = true\n  press.start(p)\n  redraw()\n}\nfunction onMove(e: PointerEvent) {\n  const p = local(e), prev = pointers.get(e.pointerId)\n  hover.value = e.pointerType === 'mouse' ? toCell(p.x, p.y) : undefined\n  hoverAt.value = e.pointerType === 'mouse' ? p : undefined\n  if (!prev) { redraw(); return }\n  if (pointers.size === 2) {\n    const [a, b] = [...pointers.values()]\n    const other = a === prev ? b! : a!\n    const before = Math.hypot(prev.x - other.x, prev.y - other.y), after = Math.hypot(p.x - other.x, p.y - other.y)\n    pointers.set(e.pointerId, p)\n    panBy((p.x - prev.x) \u002F 2, (p.y - prev.y) \u002F 2)\n    if (before > 0) zoomAt(after \u002F before, (p.x + other.x) \u002F 2, (p.y + other.y) \u002F 2)\n    return\n  }\n  pointers.set(e.pointerId, p)\n  if (mode === 'pan') {\n    const now = performance.now(), dt = Math.max(1, now - fling.at)\n    fling = { vx: fling.vx * 0.6 + ((p.x - prev.x) \u002F dt) * 0.4, vy: fling.vy * 0.6 + ((p.y - prev.y) \u002F dt) * 0.4, at: now }\n    panBy(p.x - prev.x, p.y - prev.y)\n    return\n  }\n  if (mode === 'paint') {\n    if (holding && Math.hypot(p.x - start.x, p.y - start.y) \u003C= slack) return\n    if (holding) { holding = false; press.stop() }\n    const cell = toCell(p.x, p.y), last = stroke.value.at(-1)\n    if (cell === undefined || last === undefined || cell === last) return\n    const seen = new Set(stroke.value), next = [...stroke.value]\n    for (const c of line(last, cell).slice(1)) if (!seen.has(c) && next.length \u003C props.ink) { seen.add(c); next.push(c) }\n    stroke.value = next\n    redraw()\n  }\n}\nfunction onUp(e: PointerEvent) {\n  pointers.delete(e.pointerId)\n  if (e.pointerType !== 'mouse' && !pointers.size) busy(false, true)\n  press.stop()\n  holding = false\n  const p = local(e), now = performance.now()\n  const tap = e.type === 'pointerup' && now - downAt \u003C 300 && Math.hypot(p.x - start.x, p.y - start.y) \u003C 12\n  if (tap) lastTap = { at: now, x: p.x, y: p.y }\n  let waiting = false\n  if (mode === 'paint' && stroke.value.length && e.type === 'pointerup') {\n    if (tap && stroke.value.length === 1) { waiting = true; tapped.start([...stroke.value]) } else emit('stroke', stroke.value)\n  }\n  \u002F\u002F a drag that ends while still moving glides on; one that paused first stays put\n  if (mode === 'pan' && !pointers.size && e.type === 'pointerup' && performance.now() - fling.at \u003C 60) glideOn()\n  if (!pointers.size) { mode = undefined; if (!waiting) stroke.value = [] }\n  redraw()\n}\nfunction onWheel(e: WheelEvent) {\n  e.preventDefault()\n  stopEasing()\n  const p = local(e)\n  \u002F\u002F a pinch (ctrlKey) or a mouse wheel zooms; a trackpad's two-finger scroll pans\n  const mouse = e.deltaMode === 1 || (Math.abs(e.deltaY) >= 50 && e.deltaX === 0 && Number.isInteger(e.deltaY))\n  if (e.ctrlKey || mouse) zoomAt(Math.exp(-e.deltaY * (e.ctrlKey ? 0.01 : 0.0025)), p.x, p.y)\n  else panBy(-e.deltaX, -e.deltaY)\n}\nfunction onKey(e: KeyboardEvent) {\n  const step = 8 * view.value.size\n  const keys: Record\u003Cstring, () => void> = {\n    'ArrowLeft': () => panBy(step, 0), 'ArrowRight': () => panBy(-step, 0), 'ArrowUp': () => panBy(0, step), 'ArrowDown': () => panBy(0, -step),\n    '+': () => zoomAt(1.5, undefined, undefined, true), '=': () => zoomAt(1.5, undefined, undefined, true), '-': () => zoomAt(1 \u002F 1.5, undefined, undefined, true),\n  }\n  if (e.key === ' ') { space = e.type === 'keydown'; e.preventDefault(); return }\n  if (e.type === 'keydown' && keys[e.key]) { keys[e.key]!(); e.preventDefault() }\n}\n\nasync function mount(canvas2dOnly = false) {\n  const { mountEmojiRenderer } = await import('#infra\u002Fgraphics\u002Femoji-render')\n  try {\n    renderer = await mountEmojiRenderer(canvas.value!, { canvas2dOnly })\n  } catch {\n    \u002F\u002F WebGPU failed after claiming the canvas, which a 2D context then cannot use\n    canvasKey.value++\n    await nextTick()\n    return mount(true)\n  }\n  renderMode.value = renderer.mode\n  resize()\n}\nuseEventListener(() => (renderMode.value ? canvas.value : undefined), 'wheel', onWheel, { passive: false })\nuseResizeObserver(box, resize)\nonMounted(() => {\n  const css = getComputedStyle(document.documentElement)\n  for (const k of ['paper', 'enamel', 'stone', 'amber', 'amber-light']) colors[k] = css.getPropertyValue(`--color-${k}`).trim()\n  void mount()\n})\nonBeforeUnmount(() => { cancelAnimationFrame(frame); stopEasing(); renderer?.destroy() })\nwatch(() => props.version, redraw)\nwatch(() => props.brush, redraw)\nwatch([view, width, height], () => {\n  redraw()\n  const { x0, y0, x1, y1 } = bounds()\n  emit('need', x0, y0, x1, y1)\n}, { deep: true })\n\n\u002F\u002F Hovering a painted cell, a HUD card beside the pointer: the emoji by name, where, and (after a moment, from the server)\n\u002F\u002F who painted it last, when, and how often the cell has been painted. Mouse only; a touch paints.\ntype History = { emoji: string | null; painter?: 'human' | 'jev'; who?: string; at?: string; times?: number }\nconst histories = new Map\u003Cnumber, History>()\nconst history = ref\u003CHistory>()\nconst hoverAt = ref\u003C{ x: number; y: number }>()\nwatchDebounced(hover, async (cell) => {\n  history.value = undefined\n  if (cell === undefined || !props.cells.has(cell)) return\n  const known = histories.get(cell)\n  if (known && known.emoji === props.cells.get(cell)?.emoji) { history.value = known; return }\n  try {\n    const h = await $fetch\u003CHistory>('\u002Fapi\u002Femoji\u002Fcell', { query: { cell } })\n    histories.set(cell, h)\n    if (hover.value === cell) history.value = h\n  } catch {}\n}, { debounce: 250 })\nconst since = (at: string) => { const s = Math.max(0, (Date.now() - Date.parse(at)) \u002F 1000); return s \u003C 60 ? 'just now' : s \u003C 3600 ? `${Math.round(s \u002F 60)}m ago` : s \u003C 86400 ? `${Math.round(s \u002F 3600)}h ago` : `${Math.round(s \u002F 86400)}d ago` }\nconst card = computed(() => {\n  const cell = hover.value, c = cell === undefined ? undefined : props.cells.get(cell)\n  if (cell === undefined || !c || !hoverAt.value || mode) return\n  const h = history.value\n  const by = h?.painter === 'jev' || c.jev ? 'Jev' : h?.who ? painterOf(h.who).name : undefined\n  return {\n    head: `${c.emoji} ${emojiInfo(c.emoji).name}`,\n    where: cellLabel(cell),\n    by: h?.at ? `${by ?? 'Someone'} · ${since(h.at)}${h.times && h.times > 1 ? ` · painted ${h.times}×` : ''}` : by,\n    style: { left: `${Math.min(hoverAt.value.x + 16, width.value - 220)}px`, top: `${Math.min(hoverAt.value.y + 16, height.value - 70)}px` },\n  }\n})\nconst hoverLabel = computed(() => {\n  \u002F\u002F a painted cell has the card instead\n  if (hover.value === undefined || card.value) return ''\n  const c = props.cells.get(hover.value)\n  return `${cellLabel(hover.value)}${c ? ` ${c.emoji}${c.jev ? ' · Jev' : ''}` : ''}`\n})\n\u002F** The page's zoom buttons: eased, about the middle. *\u002F\nconst zoomBy = (factor: number) => zoomAt(factor, undefined, undefined, true)\ndefineExpose({ zoomBy, easeTo, renderMode, ping })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv ref=\"box\" class=\"canvas-box\" :class=\"`is-${tool}`\">\n    \u003Ccanvas\n      :key=\"canvasKey\" ref=\"canvas\" class=\"canvas\" tabindex=\"0\" role=\"img\" :aria-label=\"label\" :data-mode=\"renderMode\"\n      @pointerdown=\"onDown\" @pointermove=\"onMove\" @pointerup=\"onUp\" @pointercancel=\"onUp\" @pointerenter=\"$event.pointerType === 'mouse' && busy(true)\" @pointerleave=\"hover = undefined; redraw(); $event.pointerType === 'mouse' && busy(false)\"\n      @keydown=\"onKey\" @keyup=\"onKey\" @contextmenu.prevent\n    \u002F>\n    \u003Cdiv v-if=\"!renderMode\" class=\"canvas__loading mono-label\">\n      \u003Cimg src=\"\u002Fimg\u002F1000000-600.webp\" alt=\"\" width=\"300\" height=\"161\" class=\"canvas__logo\">\n      \u003Cp>\u003CGlyphSpinner set=\"shade\" \u002F> Drawing the canvas\u003C\u002Fp>\n    \u003C\u002Fdiv>\n    \u003Cspan v-if=\"pinged\" :key=\"pinged.key\" class=\"canvas__ping\" :style=\"pingStyle\" aria-hidden=\"true\" \u002F>\n    \u003Cp v-if=\"card\" class=\"canvas__card mono-label\" :style=\"card.style\" aria-hidden=\"true\">\n      \u003Cspan class=\"canvas__card-head\">{{ card.head }}\u003C\u002Fspan>\u003Cspan>{{ card.where }}\u003C\u002Fspan>\u003Cspan v-if=\"card.by\">{{ card.by }}\u003C\u002Fspan>\n    \u003C\u002Fp>\n    \u003Cspan v-for=\"g in ghosts\" :key=\"`${g.cell}-${g.emoji}`\" class=\"canvas__ghost\" :style=\"ghostStyle(g)\" aria-hidden=\"true\">{{ g.emoji }}\u003C\u002Fspan>\n    \u003C!-- on touch screens a lane down each side scrolls the page, so a thumb near the edge never paints -->\n    \u003Cspan class=\"canvas__lane canvas__lane--left\" aria-hidden=\"true\" \u002F>\u003Cspan class=\"canvas__lane canvas__lane--right\" aria-hidden=\"true\" \u002F>\n    \u003Cp class=\"canvas__at mono-label\" aria-live=\"polite\">{{ hoverLabel || status }}\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>","\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">setup\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">lang\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ts&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"language-javascript\">\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { cellAt, cellLabel, line, \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan>, xy } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji\u002Fcanvas&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { emojiInfo } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { painterOf } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;#core\u002Fdomain\u002Fpainters&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> type { \u003Cspan class=\"hljs-title class_\">EmojiRenderer\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">EmojiScene\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;#infra\u002Fgraphics\u002Femoji-render&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> type { \u003Cspan class=\"hljs-title class_\">CanvasCell\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;~\u002Fcomposables\u002FuseEmojiCanvas&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> props = defineProps&lt;{\n  \u003Cspan class=\"hljs-attr\">cells\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;number, \u003Cspan class=\"hljs-title class_\">CanvasCell\u003C\u002Fspan>&gt;\n  \u003Cspan class=\"hljs-attr\">version\u003C\u002Fspan>: number\n  \u003Cspan class=\"hljs-attr\">arrived\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;number, number&gt;\n  \u003Cspan class=\"hljs-attr\">brush\u003C\u002Fspan>: string\n  \u003Cspan class=\"hljs-attr\">tool\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;move&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;pick&#x27;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">ink\u003C\u002Fspan>: number\n  \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: string\n  \u003Cspan class=\"hljs-comment\">\u002F** Jev weighing its options: each shown faintly, by how likely it is. *\u002F\u003C\u002Fspan>\n  ghosts?: { \u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: string; \u003Cspan class=\"hljs-attr\">p\u003C\u002Fspan>: number }[]\n}&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> emit = defineEmits&lt;{ \u003Cspan class=\"hljs-attr\">stroke\u003C\u002Fspan>: [number[]]; \u003Cspan class=\"hljs-attr\">pick\u003C\u002Fspan>: [string]; \u003Cspan class=\"hljs-attr\">need\u003C\u002Fspan>: [number, number, number, number]; \u003Cspan class=\"hljs-attr\">busy\u003C\u002Fspan>: [boolean] }&gt;()\n\u003Cspan class=\"hljs-comment\">\u002F\u002F the cell at the centre (fractional) and a cell&#x27;s size in CSS pixels\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> view = defineModel&lt;{ \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>: number }&gt;(\u003Cspan class=\"hljs-string\">&#x27;view&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">required\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan> })\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">MIN\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">MAX\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">64\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> box = ref&lt;\u003Cspan class=\"hljs-title class_\">HTMLElement\u003C\u002Fspan>&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> canvas = ref&lt;\u003Cspan class=\"hljs-title class_\">HTMLCanvasElement\u003C\u002Fspan>&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> width = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>), height = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> hover = ref&lt;number&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> stroke = ref&lt;number[]&gt;([])\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> status = \u003Cspan class=\"hljs-title function_\">refAutoReset\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1600\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">colors\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, string&gt; = {}\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">toCell\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">px: number, py: number\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> s = view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">cellAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + (px - width.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) \u002F s), \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + (py - height.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) \u002F s))\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">bounds\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> s = view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, hw = width.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> \u002F s, hh = height.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> \u002F s\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">x0\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - hw), \u003Cspan class=\"hljs-attr\">y0\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - hh), \u003Cspan class=\"hljs-attr\">x1\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">ceil\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + hw), \u003Cspan class=\"hljs-attr\">y1\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">ceil\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + hh) }\n}\n\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">renderer\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">EmojiRenderer\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F on touch screens the grid&#x27;s hairlines vanish, so alternate cells are shaded instead\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> coarse = \u003Cspan class=\"hljs-title function_\">useMediaQuery\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;(pointer: coarse)&#x27;\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>(coarse, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>())\n\u003Cspan class=\"hljs-comment\">\u002F\u002F a ring, not the arrival flash: a cell marked as arrived is one the tiles must not overwrite\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> pinged = refAutoReset&lt;{ \u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">key\u003C\u002Fspan>: number } | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2000\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">centreOf\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">cell: number\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { x, y } = \u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(cell), s = view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">left\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${width.value \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + (x + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> - view.value.x) * s}\u003C\u002Fspan>px`\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">top\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${height.value \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + (y + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> - view.value.y) * s}\u003C\u002Fspan>px`\u003C\u002Fspan> }\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> pingStyle = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> (pinged.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> ? { ...\u003Cspan class=\"hljs-title function_\">centreOf\u003C\u002Fspan>(pinged.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>), \u003Cspan class=\"hljs-string\">&#x27;--ring&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.max(view.value.size, \u003Cspan class=\"hljs-number\">20\u003C\u002Fspan>)}\u003C\u002Fspan>px`\u003C\u002Fspan> } : {}))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">ghostStyle\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">g: { cell: number; p: number }\u003C\u002Fspan>) =&gt; ({ ...\u003Cspan class=\"hljs-title function_\">centreOf\u003C\u002Fspan>(g.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>), \u003Cspan class=\"hljs-string\">&#x27;--cell&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${view.value.size}\u003C\u002Fspan>px`\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;opacity&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0.3\u003C\u002Fspan> + \u003Cspan class=\"hljs-number\">0.7\u003C\u002Fspan> * g.\u003Cspan class=\"hljs-property\">p\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">ping\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">cell: number\u003C\u002Fspan>) =&gt; { pinged.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = { cell, \u003Cspan class=\"hljs-attr\">key\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>() } }\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> renderMode = ref&lt;\u003Cspan class=\"hljs-string\">&#x27;webgpu&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;canvas&#x27;\u003C\u002Fspan>&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> canvasKey = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> frame = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; { \u003Cspan class=\"hljs-title function_\">cancelAnimationFrame\u003C\u002Fspan>(frame); frame = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(draw) }\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">draw\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!renderer) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">scene\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">EmojiScene\u003C\u002Fspan> = {\n    ...view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">width\u003C\u002Fspan>: width.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">height\u003C\u002Fspan>: height.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">cells\u003C\u002Fspan>: props.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">arrived\u003C\u002Fspan>: props.\u003Cspan class=\"hljs-property\">arrived\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-attr\">pending\u003C\u002Fspan>: \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Set\u003C\u002Fspan>(stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>), \u003Cspan class=\"hljs-attr\">brush\u003C\u002Fspan>: props.\u003Cspan class=\"hljs-property\">brush\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">hover\u003C\u002Fspan>: hover.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">colors\u003C\u002Fspan>: colors \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">EmojiScene\u003C\u002Fspan>[\u003Cspan class=\"hljs-string\">&#x27;colors&#x27;\u003C\u002Fspan>], \u003Cspan class=\"hljs-attr\">checker\u003C\u002Fspan>: coarse.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>,\n  }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (renderer.\u003Cspan class=\"hljs-title function_\">render\u003C\u002Fspan>(scene)) \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>()\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">resize\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> el = box.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!el) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  width.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = el.\u003Cspan class=\"hljs-property\">clientWidth\u003C\u002Fspan>\n  height.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = el.\u003Cspan class=\"hljs-property\">clientHeight\u003C\u002Fspan>\n  renderer?.\u003Cspan class=\"hljs-title function_\">resize\u003C\u002Fspan>(width.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, height.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>)\n  view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_\">clampView\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>()\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">clampView\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">v: { x: number; y: number; size: number }\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> size = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">MAX\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">MIN\u003C\u002Fspan>, v.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>))\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">keep\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">c: number, half: number\u003C\u002Fspan>) =&gt; (half * \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> &gt;= \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> ? \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> : \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> - half, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(half, c)))\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { size, \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-title function_\">keep\u003C\u002Fspan>(v.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, width.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> \u002F size), \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-title function_\">keep\u003C\u002Fspan>(v.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>, height.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> \u002F size) }\n}\n\u003Cspan class=\"hljs-comment\">\u002F\u002F Like a map: buttons, keys and a double-click zoom with an ease, a released drag glides on and slows, and any touch\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F of the canvas stops either. Under reduced motion every change lands at once.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> reduced = \u003Cspan class=\"hljs-title function_\">useReducedMotion\u003C\u002Fspan>()\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> easing = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">stopEasing\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; \u003Cspan class=\"hljs-title function_\">cancelAnimationFrame\u003C\u002Fspan>(easing)\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">easeTo\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">to: { x: number; y: number; size: number }, ms = \u003Cspan class=\"hljs-number\">260\u003C\u002Fspan>\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-title function_\">stopEasing\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> end = \u003Cspan class=\"hljs-title function_\">clampView\u003C\u002Fspan>(to), \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> = { ...view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> }, t0 = performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (reduced) { view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = end; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">step\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">t: number\u003C\u002Fspan>) =&gt; {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> k = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, (t - t0) \u002F ms), e = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> - (\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> - k) ** \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F the size on a log scale, so each step of the zoom feels even\u003C\u002Fspan>\n    view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + (end.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>) * e, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + (end.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) * e, \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">exp\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>) + (\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(end.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>) - \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>)) * e) }\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (k &lt; \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) easing = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(step)\n  }\n  easing = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(step)\n}\n\u003Cspan class=\"hljs-comment\">\u002F** The view after zooming by `factor` about the point (px, py), which stays under the pointer. *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">zoomed\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">factor: number, px: number, py: number\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> v = view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, size = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">MAX\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">MIN\u003C\u002Fspan>, v.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan> * factor))\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> wx = v.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + (px - width.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) \u002F v.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, wy = v.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + (py - height.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) \u002F v.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { size, \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: wx - (px - width.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) \u002F size, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: wy - (py - height.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) \u002F size }\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">zoomAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">factor: number, px = width.value \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, py = height.value \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, smooth = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (smooth) \u003Cspan class=\"hljs-title function_\">easeTo\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_\">zoomed\u003C\u002Fspan>(factor, px, py))\n  \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> { \u003Cspan class=\"hljs-title function_\">stopEasing\u003C\u002Fspan>(); view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_\">clampView\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_\">zoomed\u003C\u002Fspan>(factor, px, py)) }\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">dx: number, dy: number\u003C\u002Fspan>) =&gt; { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> v = view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>; view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_\">clampView\u003C\u002Fspan>({ ...v, \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: v.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - dx \u002F v.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: v.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - dy \u002F v.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan> }) }\n\u003Cspan class=\"hljs-comment\">\u002F\u002F the drag&#x27;s speed, in CSS pixels a millisecond, smoothed over its last moves\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> fling = { \u003Cspan class=\"hljs-attr\">vx\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">vy\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> }\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">glideOn\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> { vx, vy } = fling, last = performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (reduced || \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">hypot\u003C\u002Fspan>(vx, vy) &lt; \u003Cspan class=\"hljs-number\">0.15\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">step\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">t: number\u003C\u002Fspan>) =&gt; {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> dt = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">48\u003C\u002Fspan>, t - last), decay = \u003Cspan class=\"hljs-number\">0.94\u003C\u002Fspan> ** (dt \u002F \u003Cspan class=\"hljs-number\">16\u003C\u002Fspan>)\n    last = t\n    \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan>(vx * dt, vy * dt)\n    vx *= decay; vy *= decay\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">hypot\u003C\u002Fspan>(vx, vy) &gt; \u003Cspan class=\"hljs-number\">0.02\u003C\u002Fspan>) easing = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(step)\n  }\n  \u003Cspan class=\"hljs-title function_\">stopEasing\u003C\u002Fspan>()\n  easing = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(step)\n}\n\u003Cspan class=\"hljs-comment\">\u002F\u002F A double tap or click zooms in about that point (with shift, out), in every tool and on touch, where no dblclick comes.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F So a single tap on one cell paints only once no second tap follows: a drag paints at once.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">TAP_GAP\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">250\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> lastTap = { \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> }, downAt = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> tapped = \u003Cspan class=\"hljs-title function_\">useTimeoutFn\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">cells: number[]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-title function_\">emit\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;stroke&#x27;\u003C\u002Fspan>, cells); stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = []; \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>() }, \u003Cspan class=\"hljs-variable constant_\">TAP_GAP\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">immediate\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> })\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> pointers = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;number, { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: number }&gt;()\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">mode\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;pan&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> space = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> start = { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> }\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">local\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">e: PointerEvent | WheelEvent\u003C\u002Fspan>) =&gt; { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> r = canvas.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>!.\u003Cspan class=\"hljs-title function_\">getBoundingClientRect\u003C\u002Fspan>(); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: e.\u003Cspan class=\"hljs-property\">clientX\u003C\u002Fspan> - r.\u003Cspan class=\"hljs-property\">left\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: e.\u003Cspan class=\"hljs-property\">clientY\u003C\u002Fspan> - r.\u003Cspan class=\"hljs-property\">top\u003C\u002Fspan> } }\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">pickAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">p: { x: number; y: number }\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cell = \u003Cspan class=\"hljs-title function_\">toCell\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>), c = cell === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> ? \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> : props.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!c) { status.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;Nothing to pick up there&#x27;\u003C\u002Fspan>; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-title function_\">emit\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;pick&#x27;\u003C\u002Fspan>, c.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan>)\n  status.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">`Picked \u003Cspan class=\"hljs-subst\">${c.emoji}\u003C\u002Fspan>`\u003C\u002Fspan>\n  navigator.\u003Cspan class=\"hljs-property\">vibrate\u003C\u002Fspan>?.(\u003Cspan class=\"hljs-number\">12\u003C\u002Fspan>)\n}\n\u003Cspan class=\"hljs-comment\">\u002F\u002F until a hold is ruled out the stroke does not grow, so a wobble across a cell edge is not a stroke\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">HOLD\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">400\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> slack = \u003Cspan class=\"hljs-number\">6\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> holding = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> press = \u003Cspan class=\"hljs-title function_\">useTimeoutFn\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">p: { x: number; y: number }\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (mode === \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan> &amp;&amp; holding) { mode = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>; holding = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>; stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = []; \u003Cspan class=\"hljs-title function_\">pickAt\u003C\u002Fspan>(p); \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>() } }, \u003Cspan class=\"hljs-variable constant_\">HOLD\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">immediate\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-comment\">\u002F\u002F `busy` lets the page follow new paint only when that disturbs nobody\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">TOUCH_REST\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">4000\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> rest = \u003Cspan class=\"hljs-title function_\">useTimeoutFn\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">emit\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;busy&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>), \u003Cspan class=\"hljs-variable constant_\">TOUCH_REST\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">immediate\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">busy\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">on: boolean, touch = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\u003C\u002Fspan>) {\n  rest.\u003Cspan class=\"hljs-title function_\">stop\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (on || !touch) \u003Cspan class=\"hljs-title function_\">emit\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;busy&#x27;\u003C\u002Fspan>, on)\n  \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> rest.\u003Cspan class=\"hljs-title function_\">start\u003C\u002Fspan>()\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">onDown\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">e: PointerEvent\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-title function_\">stopEasing\u003C\u002Fspan>()\n  fling = { \u003Cspan class=\"hljs-attr\">vx\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">vy\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>() }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (e.\u003Cspan class=\"hljs-property\">pointerType\u003C\u002Fspan> !== \u003Cspan class=\"hljs-string\">&#x27;mouse&#x27;\u003C\u002Fspan>) \u003Cspan class=\"hljs-title function_\">busy\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> { canvas.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>!.\u003Cspan class=\"hljs-title function_\">setPointerCapture\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">pointerId\u003C\u002Fspan>) } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> {}\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> p = \u003Cspan class=\"hljs-title function_\">local\u003C\u002Fspan>(e)\n  pointers.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">pointerId\u003C\u002Fspan>, p)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> now = performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (pointers.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> &amp;&amp; now - lastTap.\u003Cspan class=\"hljs-property\">at\u003C\u002Fspan> &lt; \u003Cspan class=\"hljs-variable constant_\">TAP_GAP\u003C\u002Fspan> + \u003Cspan class=\"hljs-number\">50\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">hypot\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - lastTap.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - lastTap.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) &lt; \u003Cspan class=\"hljs-number\">24\u003C\u002Fspan>) {\n    tapped.\u003Cspan class=\"hljs-title function_\">stop\u003C\u002Fspan>(); stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = []; lastTap.\u003Cspan class=\"hljs-property\">at\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>; mode = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-title function_\">zoomAt\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">shiftKey\u003C\u002Fspan> ? \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>)\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  }\n  downAt = now\n  start = p\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (pointers.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) { tapped.\u003Cspan class=\"hljs-title function_\">stop\u003C\u002Fspan>(); mode = \u003Cspan class=\"hljs-string\">&#x27;pan&#x27;\u003C\u002Fspan>; stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = []; press.\u003Cspan class=\"hljs-title function_\">stop\u003C\u002Fspan>(); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (props.\u003Cspan class=\"hljs-property\">tool\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;pick&#x27;\u003C\u002Fspan> || e.\u003Cspan class=\"hljs-property\">altKey\u003C\u002Fspan>) { \u003Cspan class=\"hljs-title function_\">pickAt\u003C\u002Fspan>(p); mode = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (props.\u003Cspan class=\"hljs-property\">tool\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;move&#x27;\u003C\u002Fspan> || e.\u003Cspan class=\"hljs-property\">button\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> || e.\u003Cspan class=\"hljs-property\">button\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> || space) { mode = \u003Cspan class=\"hljs-string\">&#x27;pan&#x27;\u003C\u002Fspan>; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cell = \u003Cspan class=\"hljs-title function_\">toCell\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (cell === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> || props.\u003Cspan class=\"hljs-property\">ink\u003C\u002Fspan> &lt; \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) { mode = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>; \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (props.\u003Cspan class=\"hljs-property\">ink\u003C\u002Fspan> &lt; \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) status.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;Out of ink&#x27;\u003C\u002Fspan>; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  mode = \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan>\n  stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = [cell]\n  slack = e.\u003Cspan class=\"hljs-property\">pointerType\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;mouse&#x27;\u003C\u002Fspan> ? \u003Cspan class=\"hljs-number\">6\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">14\u003C\u002Fspan>\n  holding = \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>\n  press.\u003Cspan class=\"hljs-title function_\">start\u003C\u002Fspan>(p)\n  \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>()\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">onMove\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">e: PointerEvent\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> p = \u003Cspan class=\"hljs-title function_\">local\u003C\u002Fspan>(e), prev = pointers.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">pointerId\u003C\u002Fspan>)\n  hover.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = e.\u003Cspan class=\"hljs-property\">pointerType\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;mouse&#x27;\u003C\u002Fspan> ? \u003Cspan class=\"hljs-title function_\">toCell\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) : \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n  hoverAt.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = e.\u003Cspan class=\"hljs-property\">pointerType\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;mouse&#x27;\u003C\u002Fspan> ? p : \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!prev) { \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>(); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (pointers.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> [a, b] = [...pointers.\u003Cspan class=\"hljs-title function_\">values\u003C\u002Fspan>()]\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> other = a === prev ? b! : a!\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> before = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">hypot\u003C\u002Fspan>(prev.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - other.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, prev.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - other.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>), after = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">hypot\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - other.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - other.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>)\n    pointers.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">pointerId\u003C\u002Fspan>, p)\n    \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan>((p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - prev.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>) \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, (p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - prev.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>)\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (before &gt; \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) \u003Cspan class=\"hljs-title function_\">zoomAt\u003C\u002Fspan>(after \u002F before, (p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + other.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>) \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, (p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + other.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>)\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  }\n  pointers.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">pointerId\u003C\u002Fspan>, p)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (mode === \u003Cspan class=\"hljs-string\">&#x27;pan&#x27;\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> now = performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>(), dt = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, now - fling.\u003Cspan class=\"hljs-property\">at\u003C\u002Fspan>)\n    fling = { \u003Cspan class=\"hljs-attr\">vx\u003C\u002Fspan>: fling.\u003Cspan class=\"hljs-property\">vx\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">0.6\u003C\u002Fspan> + ((p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - prev.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>) \u002F dt) * \u003Cspan class=\"hljs-number\">0.4\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">vy\u003C\u002Fspan>: fling.\u003Cspan class=\"hljs-property\">vy\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">0.6\u003C\u002Fspan> + ((p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - prev.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) \u002F dt) * \u003Cspan class=\"hljs-number\">0.4\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: now }\n    \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - prev.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - prev.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>)\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (mode === \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (holding &amp;&amp; \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">hypot\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - start.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - start.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) &lt;= slack) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (holding) { holding = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>; press.\u003Cspan class=\"hljs-title function_\">stop\u003C\u002Fspan>() }\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cell = \u003Cspan class=\"hljs-title function_\">toCell\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>), last = stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">at\u003C\u002Fspan>(-\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>)\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (cell === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> || last === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> || cell === last) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> seen = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Set\u003C\u002Fspan>(stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>), next = [...stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>]\n    \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> c \u003Cspan class=\"hljs-keyword\">of\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">line\u003C\u002Fspan>(last, cell).\u003Cspan class=\"hljs-title function_\">slice\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>)) \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!seen.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(c) &amp;&amp; next.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan> &lt; props.\u003Cspan class=\"hljs-property\">ink\u003C\u002Fspan>) { seen.\u003Cspan class=\"hljs-title function_\">add\u003C\u002Fspan>(c); next.\u003Cspan class=\"hljs-title function_\">push\u003C\u002Fspan>(c) }\n    stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = next\n    \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>()\n  }\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">onUp\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">e: PointerEvent\u003C\u002Fspan>) {\n  pointers.\u003Cspan class=\"hljs-title function_\">delete\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">pointerId\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (e.\u003Cspan class=\"hljs-property\">pointerType\u003C\u002Fspan> !== \u003Cspan class=\"hljs-string\">&#x27;mouse&#x27;\u003C\u002Fspan> &amp;&amp; !pointers.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>) \u003Cspan class=\"hljs-title function_\">busy\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>)\n  press.\u003Cspan class=\"hljs-title function_\">stop\u003C\u002Fspan>()\n  holding = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> p = \u003Cspan class=\"hljs-title function_\">local\u003C\u002Fspan>(e), now = performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> tap = e.\u003Cspan class=\"hljs-property\">type\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;pointerup&#x27;\u003C\u002Fspan> &amp;&amp; now - downAt &lt; \u003Cspan class=\"hljs-number\">300\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">hypot\u003C\u002Fspan>(p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - start.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - start.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) &lt; \u003Cspan class=\"hljs-number\">12\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (tap) lastTap = { \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: now, \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> waiting = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (mode === \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan> &amp;&amp; stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan> &amp;&amp; e.\u003Cspan class=\"hljs-property\">type\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;pointerup&#x27;\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (tap &amp;&amp; stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) { waiting = \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>; tapped.\u003Cspan class=\"hljs-title function_\">start\u003C\u002Fspan>([...stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>]) } \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">emit\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;stroke&#x27;\u003C\u002Fspan>, stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>)\n  }\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F a drag that ends while still moving glides on; one that paused first stays put\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (mode === \u003Cspan class=\"hljs-string\">&#x27;pan&#x27;\u003C\u002Fspan> &amp;&amp; !pointers.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan> &amp;&amp; e.\u003Cspan class=\"hljs-property\">type\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;pointerup&#x27;\u003C\u002Fspan> &amp;&amp; performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>() - fling.\u003Cspan class=\"hljs-property\">at\u003C\u002Fspan> &lt; \u003Cspan class=\"hljs-number\">60\u003C\u002Fspan>) \u003Cspan class=\"hljs-title function_\">glideOn\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!pointers.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>) { mode = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>; \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!waiting) stroke.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = [] }\n  \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>()\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">onWheel\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">e: WheelEvent\u003C\u002Fspan>) {\n  e.\u003Cspan class=\"hljs-title function_\">preventDefault\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-title function_\">stopEasing\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> p = \u003Cspan class=\"hljs-title function_\">local\u003C\u002Fspan>(e)\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F a pinch (ctrlKey) or a mouse wheel zooms; a trackpad&#x27;s two-finger scroll pans\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> mouse = e.\u003Cspan class=\"hljs-property\">deltaMode\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> || (\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">abs\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">deltaY\u003C\u002Fspan>) &gt;= \u003Cspan class=\"hljs-number\">50\u003C\u002Fspan> &amp;&amp; e.\u003Cspan class=\"hljs-property\">deltaX\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-title class_\">Number\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">isInteger\u003C\u002Fspan>(e.\u003Cspan class=\"hljs-property\">deltaY\u003C\u002Fspan>))\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (e.\u003Cspan class=\"hljs-property\">ctrlKey\u003C\u002Fspan> || mouse) \u003Cspan class=\"hljs-title function_\">zoomAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">exp\u003C\u002Fspan>(-e.\u003Cspan class=\"hljs-property\">deltaY\u003C\u002Fspan> * (e.\u003Cspan class=\"hljs-property\">ctrlKey\u003C\u002Fspan> ? \u003Cspan class=\"hljs-number\">0.01\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">0.0025\u003C\u002Fspan>)), p.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, p.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan>(-e.\u003Cspan class=\"hljs-property\">deltaX\u003C\u002Fspan>, -e.\u003Cspan class=\"hljs-property\">deltaY\u003C\u002Fspan>)\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">onKey\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">e: KeyboardEvent\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> step = \u003Cspan class=\"hljs-number\">8\u003C\u002Fspan> * view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">keys\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>&gt; = {\n    \u003Cspan class=\"hljs-string\">&#x27;ArrowLeft&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan>(step, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>), \u003Cspan class=\"hljs-string\">&#x27;ArrowRight&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan>(-step, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>), \u003Cspan class=\"hljs-string\">&#x27;ArrowUp&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, step), \u003Cspan class=\"hljs-string\">&#x27;ArrowDown&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">panBy\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, -step),\n    \u003Cspan class=\"hljs-string\">&#x27;+&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">zoomAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1.5\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>), \u003Cspan class=\"hljs-string\">&#x27;=&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">zoomAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1.5\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>), \u003Cspan class=\"hljs-string\">&#x27;-&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">zoomAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">1.5\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>),\n  }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (e.\u003Cspan class=\"hljs-property\">key\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27; &#x27;\u003C\u002Fspan>) { space = e.\u003Cspan class=\"hljs-property\">type\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;keydown&#x27;\u003C\u002Fspan>; e.\u003Cspan class=\"hljs-title function_\">preventDefault\u003C\u002Fspan>(); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (e.\u003Cspan class=\"hljs-property\">type\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;keydown&#x27;\u003C\u002Fspan> &amp;&amp; keys[e.\u003Cspan class=\"hljs-property\">key\u003C\u002Fspan>]) { keys[e.\u003Cspan class=\"hljs-property\">key\u003C\u002Fspan>]!(); e.\u003Cspan class=\"hljs-title function_\">preventDefault\u003C\u002Fspan>() }\n}\n\n\u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">mount\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">canvas2dOnly = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { mountEmojiRenderer } = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;#infra\u002Fgraphics\u002Femoji-render&#x27;\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n    renderer = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">mountEmojiRenderer\u003C\u002Fspan>(canvas.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>!, { canvas2dOnly })\n  } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F WebGPU failed after claiming the canvas, which a 2D context then cannot use\u003C\u002Fspan>\n    canvasKey.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>++\n    \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">nextTick\u003C\u002Fspan>()\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">mount\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>)\n  }\n  renderMode.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = renderer.\u003Cspan class=\"hljs-property\">mode\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-title function_\">resize\u003C\u002Fspan>()\n}\n\u003Cspan class=\"hljs-title function_\">useEventListener\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> (renderMode.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> ? canvas.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> : \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>), \u003Cspan class=\"hljs-string\">&#x27;wheel&#x27;\u003C\u002Fspan>, onWheel, { \u003Cspan class=\"hljs-attr\">passive\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-title function_\">useResizeObserver\u003C\u002Fspan>(box, resize)\n\u003Cspan class=\"hljs-title function_\">onMounted\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> css = \u003Cspan class=\"hljs-title function_\">getComputedStyle\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">documentElement\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> k \u003Cspan class=\"hljs-keyword\">of\u003C\u002Fspan> [\u003Cspan class=\"hljs-string\">&#x27;paper&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;enamel&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;stone&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;amber&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;amber-light&#x27;\u003C\u002Fspan>]) colors[k] = css.\u003Cspan class=\"hljs-title function_\">getPropertyValue\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">`--color-\u003Cspan class=\"hljs-subst\">${k}\u003C\u002Fspan>`\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">trim\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">mount\u003C\u002Fspan>()\n})\n\u003Cspan class=\"hljs-title function_\">onBeforeUnmount\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-title function_\">cancelAnimationFrame\u003C\u002Fspan>(frame); \u003Cspan class=\"hljs-title function_\">stopEasing\u003C\u002Fspan>(); renderer?.\u003Cspan class=\"hljs-title function_\">destroy\u003C\u002Fspan>() })\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> props.\u003Cspan class=\"hljs-property\">version\u003C\u002Fspan>, redraw)\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> props.\u003Cspan class=\"hljs-property\">brush\u003C\u002Fspan>, redraw)\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>([view, width, height], \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-title function_\">redraw\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { x0, y0, x1, y1 } = \u003Cspan class=\"hljs-title function_\">bounds\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-title function_\">emit\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;need&#x27;\u003C\u002Fspan>, x0, y0, x1, y1)\n}, { \u003Cspan class=\"hljs-attr\">deep\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan> })\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F Hovering a painted cell, a HUD card beside the pointer: the emoji by name, where, and (after a moment, from the server)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F who painted it last, when, and how often the cell has been painted. Mouse only; a touch paints.\u003C\u002Fspan>\ntype \u003Cspan class=\"hljs-title class_\">History\u003C\u002Fspan> = { \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: string | \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>; painter?: \u003Cspan class=\"hljs-string\">&#x27;human&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;jev&#x27;\u003C\u002Fspan>; who?: string; at?: string; times?: number }\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> histories = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;number, \u003Cspan class=\"hljs-title class_\">History\u003C\u002Fspan>&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> history = ref&lt;\u003Cspan class=\"hljs-title class_\">History\u003C\u002Fspan>&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> hoverAt = ref&lt;{ \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: number }&gt;()\n\u003Cspan class=\"hljs-title function_\">watchDebounced\u003C\u002Fspan>(hover, \u003Cspan class=\"hljs-title function_\">async\u003C\u002Fspan> (cell) =&gt; {\n  history.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (cell === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> || !props.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(cell)) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> known = histories.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (known &amp;&amp; known.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan> === props.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell)?.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan>) { history.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = known; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> h = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> $fetch&lt;\u003Cspan class=\"hljs-title class_\">History\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Femoji\u002Fcell&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">query\u003C\u002Fspan>: { cell } })\n    histories.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(cell, h)\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (hover.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> === cell) history.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = h\n  } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> {}\n}, { \u003Cspan class=\"hljs-attr\">debounce\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">250\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">since\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">at: string\u003C\u002Fspan>) =&gt; { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> s = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, (\u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>() - \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">parse\u003C\u002Fspan>(at)) \u002F \u003Cspan class=\"hljs-number\">1000\u003C\u002Fspan>); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> s &lt; \u003Cspan class=\"hljs-number\">60\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">&#x27;just now&#x27;\u003C\u002Fspan> : s &lt; \u003Cspan class=\"hljs-number\">3600\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.round(s \u002F \u003Cspan class=\"hljs-number\">60\u003C\u002Fspan>)}\u003C\u002Fspan>m ago`\u003C\u002Fspan> : s &lt; \u003Cspan class=\"hljs-number\">86400\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.round(s \u002F \u003Cspan class=\"hljs-number\">3600\u003C\u002Fspan>)}\u003C\u002Fspan>h ago`\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.round(s \u002F \u003Cspan class=\"hljs-number\">86400\u003C\u002Fspan>)}\u003C\u002Fspan>d ago`\u003C\u002Fspan> }\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> card = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cell = hover.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, c = cell === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> ? \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> : props.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (cell === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> || !c || !hoverAt.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> || mode) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> h = history.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> by = h?.\u003Cspan class=\"hljs-property\">painter\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;jev&#x27;\u003C\u002Fspan> || c.\u003Cspan class=\"hljs-property\">jev\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">&#x27;Jev&#x27;\u003C\u002Fspan> : h?.\u003Cspan class=\"hljs-property\">who\u003C\u002Fspan> ? \u003Cspan class=\"hljs-title function_\">painterOf\u003C\u002Fspan>(h.\u003Cspan class=\"hljs-property\">who\u003C\u002Fspan>).\u003Cspan class=\"hljs-property\">name\u003C\u002Fspan> : \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-attr\">head\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${c.emoji}\u003C\u002Fspan> \u003Cspan class=\"hljs-subst\">${emojiInfo(c.emoji).name}\u003C\u002Fspan>`\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-attr\">where\u003C\u002Fspan>: \u003Cspan class=\"hljs-title function_\">cellLabel\u003C\u002Fspan>(cell),\n    \u003Cspan class=\"hljs-attr\">by\u003C\u002Fspan>: h?.\u003Cspan class=\"hljs-property\">at\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${by ?? \u003Cspan class=\"hljs-string\">&#x27;Someone&#x27;\u003C\u002Fspan>}\u003C\u002Fspan> · \u003Cspan class=\"hljs-subst\">${since(h.at)}\u003C\u002Fspan>\u003Cspan class=\"hljs-subst\">${h.times &amp;&amp; h.times &gt; \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">` · painted \u003Cspan class=\"hljs-subst\">${h.times}\u003C\u002Fspan>×`\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">&#x27;&#x27;\u003C\u002Fspan>}\u003C\u002Fspan>`\u003C\u002Fspan> : by,\n    \u003Cspan class=\"hljs-attr\">style\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">left\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.min(hoverAt.value.x + \u003Cspan class=\"hljs-number\">16\u003C\u002Fspan>, width.value - \u003Cspan class=\"hljs-number\">220\u003C\u002Fspan>)}\u003C\u002Fspan>px`\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">top\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.min(hoverAt.value.y + \u003Cspan class=\"hljs-number\">16\u003C\u002Fspan>, height.value - \u003Cspan class=\"hljs-number\">70\u003C\u002Fspan>)}\u003C\u002Fspan>px`\u003C\u002Fspan> },\n  }\n})\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> hoverLabel = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F a painted cell has the card instead\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (hover.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> || card.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;&#x27;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> c = props.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(hover.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${cellLabel(hover.value)}\u003C\u002Fspan>\u003Cspan class=\"hljs-subst\">${c ? \u003Cspan class=\"hljs-string\">` \u003Cspan class=\"hljs-subst\">${c.emoji}\u003C\u002Fspan>\u003Cspan class=\"hljs-subst\">${c.jev ? \u003Cspan class=\"hljs-string\">&#x27; · Jev&#x27;\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">&#x27;&#x27;\u003C\u002Fspan>}\u003C\u002Fspan>`\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">&#x27;&#x27;\u003C\u002Fspan>}\u003C\u002Fspan>`\u003C\u002Fspan>\n})\n\u003Cspan class=\"hljs-comment\">\u002F** The page&#x27;s zoom buttons: eased, about the middle. *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">zoomBy\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">factor: number\u003C\u002Fspan>) =&gt; \u003Cspan class=\"hljs-title function_\">zoomAt\u003C\u002Fspan>(factor, \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-title function_\">defineExpose\u003C\u002Fspan>({ zoomBy, easeTo, renderMode, ping })\n\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">ref\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;box&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas-box&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`is-${tool}`&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">canvas\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvasKey&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">ref\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">tabindex\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;0&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;img&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:data-mode\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;renderMode&quot;\u003C\u002Fspan>\n      @\u003Cspan class=\"hljs-attr\">pointerdown\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;onDown&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pointermove\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;onMove&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pointerup\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;onUp&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pointercancel\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;onUp&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pointerenter\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;$event.pointerType === &#x27;mouse&#x27; &amp;&amp; busy(true)&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pointerleave\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;hover = undefined; redraw(); $event.pointerType === &#x27;mouse&#x27; &amp;&amp; busy(false)&quot;\u003C\u002Fspan>\n      @\u003Cspan class=\"hljs-attr\">keydown\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;onKey&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">keyup\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;onKey&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">contextmenu.prevent\u003C\u002Fspan>\n    \u002F&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;!renderMode&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__loading mono-label&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">img\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">src\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;\u002Fimg\u002F1000000-600.webp&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">alt\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">width\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;300&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">height\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;161&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__logo&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">GlyphSpinner\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">set\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;shade&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan> Drawing the canvas\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;pinged&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;pinged.key&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__ping&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:style\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;pingStyle&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;card&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__card mono-label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:style\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;card.style&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__card-head&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ card.head }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ card.where }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;card.by&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ card.by }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-for\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;g in ghosts&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`${g.cell}-${g.emoji}`&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__ghost&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:style\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ghostStyle(g)&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ g.emoji }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-comment\">&lt;!-- on touch screens a lane down each side scrolls the page, so a thumb near the edge never paints --&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__lane canvas__lane--left&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__lane canvas__lane--right&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__at mono-label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-live\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;polite&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ hoverLabel || status }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>",{"path":221,"lines":222,"code":223,"html":224},"app\u002Fcomponents\u002Flab\u002FEmojiMinimap.vue",76,"\u003Cscript setup lang=\"ts\">\nimport { BLOCK, BLOCKS, SIZE } from '@cw\u002Femoji\u002Fcanvas'\nimport { rgb, type PaletteColor } from '#core\u002Fdomain\u002Fpalette'\n\nconst props = defineProps\u003C{ blocks: Map\u003Cnumber, { n: number; emoji: string }>; view: { x: number; y: number; size: number }; span: { w: number; h: number } }>()\n\u002F\u002F `glide`: a click glides there; a drag follows at once\nconst emit = defineEmits\u003C{ go: [number, number, boolean] }>()\nconst PX = 2\nconst canvas = ref\u003CHTMLCanvasElement>()\n\n\u002F\u002F Like a map's land and water: each block in the colour of the emoji painted most there, drawn once at 16px and averaged,\n\u002F\u002F then snapped to the palette's nearest colour that shows on Paper, so the map keeps to the site's colours.\nconst MAP_COLORS: PaletteColor[] = ['amber', 'amber-light', 'rust', 'copper', 'sand', 'olive', 'olive-dark', 'steel', 'steel-dark', 'lilac', 'indigo', 'clay', 'dark', 'ash']\nconst choices = MAP_COLORS.map((c) => [c, rgb(c)] as const)\nconst colours = new Map\u003Cstring, PaletteColor>()\nlet probe: CanvasRenderingContext2D | null | undefined\nfunction colourOf(emoji: string): PaletteColor {\n  const known = colours.get(emoji)\n  if (known) return known\n  probe ??= document.createElement('canvas').getContext('2d', { willReadFrequently: true })\n  let pick: PaletteColor = 'amber'\n  if (probe && emoji) {\n    probe.canvas.width = probe.canvas.height = 20\n    probe.clearRect(0, 0, 20, 20)\n    probe.font = '16px sans-serif'\n    probe.textBaseline = 'middle'\n    probe.textAlign = 'center'\n    probe.fillText(emoji, 10, 11)\n    const d = probe.getImageData(0, 0, 20, 20).data\n    let r = 0, g = 0, b = 0, n = 0\n    for (let i = 0; i \u003C d.length; i += 4) if (d[i + 3]! > 160) { r += d[i]!; g += d[i + 1]!; b += d[i + 2]!; n++ }\n    if (n) {\n      const [ar, ag, ab] = [r \u002F n, g \u002F n, b \u002F n]\n      pick = choices.reduce((best, [c, [cr, cg, cb]]) => {\n        const dist = (cr - ar) ** 2 + (cg - ag) ** 2 + (cb - ab) ** 2\n        return dist \u003C best[1] ? [c, dist] as [PaletteColor, number] : best\n      }, ['amber', Infinity] as [PaletteColor, number])[0]\n    }\n  }\n  colours.set(emoji, pick)\n  return pick\n}\n\nfunction draw() {\n  const ctx = canvas.value?.getContext('2d')\n  if (!ctx) return\n  const css = getComputedStyle(document.documentElement)\n  ctx.fillStyle = css.getPropertyValue('--color-paper').trim()\n  ctx.fillRect(0, 0, BLOCKS * PX, BLOCKS * PX)\n  for (const [b, { n, emoji }] of props.blocks) {\n    ctx.fillStyle = css.getPropertyValue(`--color-${colourOf(emoji)}`).trim()\n    ctx.globalAlpha = Math.min(1, 0.35 + n \u002F (BLOCK * BLOCK) * 3)\n    ctx.fillRect((b % BLOCKS) * PX, Math.floor(b \u002F BLOCKS) * PX, PX, PX)\n  }\n  ctx.globalAlpha = 1\n  ctx.strokeStyle = css.getPropertyValue('--color-ink').trim()\n  ctx.lineWidth = 1\n  const k = (BLOCKS * PX) \u002F SIZE\n  const w = Math.max(3, props.span.w * k), h = Math.max(3, props.span.h * k)\n  ctx.strokeRect(props.view.x * k - w \u002F 2 + 0.5, props.view.y * k - h \u002F 2 + 0.5, w, h)\n}\nconst dragging = ref(false)\nfunction go(e: PointerEvent, glide = false) {\n  const r = canvas.value!.getBoundingClientRect()\n  emit('go', ((e.clientX - r.left) \u002F r.width) * SIZE, ((e.clientY - r.top) \u002F r.height) * SIZE, glide)\n}\nonMounted(draw)\nwatch(() => [props.blocks, props.view, props.span], draw, { deep: true })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Ccanvas\n    ref=\"canvas\" class=\"minimap\" :width=\"BLOCKS * PX\" :height=\"BLOCKS * PX\" role=\"img\" aria-label=\"The whole canvas: where the paint is, coloured by its emoji, and the view\"\n    @pointerdown=\"dragging = true; go($event, true)\" @pointermove=\"dragging && go($event)\" @pointerup=\"dragging = false\" @pointerleave=\"dragging = false\"\n  \u002F>\n\u003C\u002Ftemplate>","\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">setup\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">lang\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ts&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"language-javascript\">\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-variable constant_\">BLOCK\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">BLOCKS\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji\u002Fcanvas&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { rgb, type \u003Cspan class=\"hljs-title class_\">PaletteColor\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;#core\u002Fdomain\u002Fpalette&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> props = defineProps&lt;{ \u003Cspan class=\"hljs-attr\">blocks\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;number, { \u003Cspan class=\"hljs-attr\">n\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: string }&gt;; \u003Cspan class=\"hljs-attr\">view\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>: number }; \u003Cspan class=\"hljs-attr\">span\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">w\u003C\u002Fspan>: number; \u003Cspan class=\"hljs-attr\">h\u003C\u002Fspan>: number } }&gt;()\n\u003Cspan class=\"hljs-comment\">\u002F\u002F `glide`: a click glides there; a drag follows at once\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> emit = defineEmits&lt;{ \u003Cspan class=\"hljs-attr\">go\u003C\u002Fspan>: [number, number, boolean] }&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">PX\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> canvas = ref&lt;\u003Cspan class=\"hljs-title class_\">HTMLCanvasElement\u003C\u002Fspan>&gt;()\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F Like a map&#x27;s land and water: each block in the colour of the emoji painted most there, drawn once at 16px and averaged,\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F then snapped to the palette&#x27;s nearest colour that shows on Paper, so the map keeps to the site&#x27;s colours.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">MAP_COLORS\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">PaletteColor\u003C\u002Fspan>[] = [\u003Cspan class=\"hljs-string\">&#x27;amber&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;amber-light&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;rust&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;copper&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;sand&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;olive&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;olive-dark&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;steel&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;steel-dark&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;lilac&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;indigo&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;clay&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;dark&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;ash&#x27;\u003C\u002Fspan>]\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> choices = \u003Cspan class=\"hljs-variable constant_\">MAP_COLORS\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">c\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> [c, \u003Cspan class=\"hljs-title function_\">rgb\u003C\u002Fspan>(c)] \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> colours = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;string, \u003Cspan class=\"hljs-title class_\">PaletteColor\u003C\u002Fspan>&gt;()\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">probe\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">CanvasRenderingContext2D\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">colourOf\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">emoji: string\u003C\u002Fspan>): \u003Cspan class=\"hljs-title class_\">PaletteColor\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> known = colours.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(emoji)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (known) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> known\n  probe ??= \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">createElement\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;canvas&#x27;\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">getContext\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;2d&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">willReadFrequently\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan> })\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">pick\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">PaletteColor\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;amber&#x27;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (probe &amp;&amp; emoji) {\n    probe.\u003Cspan class=\"hljs-property\">canvas\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan> = probe.\u003Cspan class=\"hljs-property\">canvas\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">20\u003C\u002Fspan>\n    probe.\u003Cspan class=\"hljs-title function_\">clearRect\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">20\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">20\u003C\u002Fspan>)\n    probe.\u003Cspan class=\"hljs-property\">font\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;16px sans-serif&#x27;\u003C\u002Fspan>\n    probe.\u003Cspan class=\"hljs-property\">textBaseline\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;middle&#x27;\u003C\u002Fspan>\n    probe.\u003Cspan class=\"hljs-property\">textAlign\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;center&#x27;\u003C\u002Fspan>\n    probe.\u003Cspan class=\"hljs-title function_\">fillText\u003C\u002Fspan>(emoji, \u003Cspan class=\"hljs-number\">10\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">11\u003C\u002Fspan>)\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> d = probe.\u003Cspan class=\"hljs-title function_\">getImageData\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">20\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">20\u003C\u002Fspan>).\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> r = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, g = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, b = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, n = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> i = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>; i &lt; d.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>; i += \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (d[i + \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>]! &gt; \u003Cspan class=\"hljs-number\">160\u003C\u002Fspan>) { r += d[i]!; g += d[i + \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>]!; b += d[i + \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>]!; n++ }\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (n) {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> [ar, ag, ab] = [r \u002F n, g \u002F n, b \u002F n]\n      pick = choices.\u003Cspan class=\"hljs-title function_\">reduce\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">best, [c, [cr, cg, cb]]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> dist = (cr - ar) ** \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + (cg - ag) ** \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + (cb - ab) ** \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> dist &lt; best[\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>] ? [c, dist] \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> [\u003Cspan class=\"hljs-title class_\">PaletteColor\u003C\u002Fspan>, number] : best\n      }, [\u003Cspan class=\"hljs-string\">&#x27;amber&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Infinity\u003C\u002Fspan>] \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> [\u003Cspan class=\"hljs-title class_\">PaletteColor\u003C\u002Fspan>, number])[\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>]\n    }\n  }\n  colours.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(emoji, pick)\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> pick\n}\n\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">draw\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> ctx = canvas.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>?.\u003Cspan class=\"hljs-title function_\">getContext\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;2d&#x27;\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!ctx) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> css = \u003Cspan class=\"hljs-title function_\">getComputedStyle\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">documentElement\u003C\u002Fspan>)\n  ctx.\u003Cspan class=\"hljs-property\">fillStyle\u003C\u002Fspan> = css.\u003Cspan class=\"hljs-title function_\">getPropertyValue\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;--color-paper&#x27;\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">trim\u003C\u002Fspan>()\n  ctx.\u003Cspan class=\"hljs-title function_\">fillRect\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">BLOCKS\u003C\u002Fspan> * \u003Cspan class=\"hljs-variable constant_\">PX\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">BLOCKS\u003C\u002Fspan> * \u003Cspan class=\"hljs-variable constant_\">PX\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> [b, { n, emoji }] \u003Cspan class=\"hljs-keyword\">of\u003C\u002Fspan> props.\u003Cspan class=\"hljs-property\">blocks\u003C\u002Fspan>) {\n    ctx.\u003Cspan class=\"hljs-property\">fillStyle\u003C\u002Fspan> = css.\u003Cspan class=\"hljs-title function_\">getPropertyValue\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">`--color-\u003Cspan class=\"hljs-subst\">${colourOf(emoji)}\u003C\u002Fspan>`\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">trim\u003C\u002Fspan>()\n    ctx.\u003Cspan class=\"hljs-property\">globalAlpha\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0.35\u003C\u002Fspan> + n \u002F (\u003Cspan class=\"hljs-variable constant_\">BLOCK\u003C\u002Fspan> * \u003Cspan class=\"hljs-variable constant_\">BLOCK\u003C\u002Fspan>) * \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>)\n    ctx.\u003Cspan class=\"hljs-title function_\">fillRect\u003C\u002Fspan>((b % \u003Cspan class=\"hljs-variable constant_\">BLOCKS\u003C\u002Fspan>) * \u003Cspan class=\"hljs-variable constant_\">PX\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(b \u002F \u003Cspan class=\"hljs-variable constant_\">BLOCKS\u003C\u002Fspan>) * \u003Cspan class=\"hljs-variable constant_\">PX\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">PX\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">PX\u003C\u002Fspan>)\n  }\n  ctx.\u003Cspan class=\"hljs-property\">globalAlpha\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>\n  ctx.\u003Cspan class=\"hljs-property\">strokeStyle\u003C\u002Fspan> = css.\u003Cspan class=\"hljs-title function_\">getPropertyValue\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;--color-ink&#x27;\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">trim\u003C\u002Fspan>()\n  ctx.\u003Cspan class=\"hljs-property\">lineWidth\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> k = (\u003Cspan class=\"hljs-variable constant_\">BLOCKS\u003C\u002Fspan> * \u003Cspan class=\"hljs-variable constant_\">PX\u003C\u002Fspan>) \u002F \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> w = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, props.\u003Cspan class=\"hljs-property\">span\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">w\u003C\u002Fspan> * k), h = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, props.\u003Cspan class=\"hljs-property\">span\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">h\u003C\u002Fspan> * k)\n  ctx.\u003Cspan class=\"hljs-title function_\">strokeRect\u003C\u002Fspan>(props.\u003Cspan class=\"hljs-property\">view\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> * k - w \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, props.\u003Cspan class=\"hljs-property\">view\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> * k - h \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, w, h)\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> dragging = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">go\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">e: PointerEvent, glide = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> r = canvas.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>!.\u003Cspan class=\"hljs-title function_\">getBoundingClientRect\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-title function_\">emit\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;go&#x27;\u003C\u002Fspan>, ((e.\u003Cspan class=\"hljs-property\">clientX\u003C\u002Fspan> - r.\u003Cspan class=\"hljs-property\">left\u003C\u002Fspan>) \u002F r.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan>) * \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan>, ((e.\u003Cspan class=\"hljs-property\">clientY\u003C\u002Fspan> - r.\u003Cspan class=\"hljs-property\">top\u003C\u002Fspan>) \u002F r.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan>) * \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan>, glide)\n}\n\u003Cspan class=\"hljs-title function_\">onMounted\u003C\u002Fspan>(draw)\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> [props.\u003Cspan class=\"hljs-property\">blocks\u003C\u002Fspan>, props.\u003Cspan class=\"hljs-property\">view\u003C\u002Fspan>, props.\u003Cspan class=\"hljs-property\">span\u003C\u002Fspan>], draw, { \u003Cspan class=\"hljs-attr\">deep\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan> })\n\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">canvas\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-attr\">ref\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;minimap&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:width\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;BLOCKS * PX&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:height\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;BLOCKS * PX&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;img&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;The whole canvas: where the paint is, coloured by its emoji, and the view&quot;\u003C\u002Fspan>\n    @\u003Cspan class=\"hljs-attr\">pointerdown\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;dragging = true; go($event, true)&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pointermove\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;dragging &amp;&amp; go($event)&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pointerup\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;dragging = false&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pointerleave\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;dragging = false&quot;\u003C\u002Fspan>\n  \u002F&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>",{"path":226,"lines":227,"code":228,"html":229},"app\u002Fcomponents\u002Flab\u002FEmojiPalette.vue",37,"\u003Cscript setup lang=\"ts\">\nimport { GROUPS, NAMES } from '@cw\u002Femoji\u002Fpalette-names'\n\nconst brush = defineModel\u003Cstring>({ required: true })\nconst props = defineProps\u003C{ recent: string[]; large?: boolean }>()\nconst emit = defineEmits\u003C{ picked: [string] }>()\n\nconst SHORT: Record\u003Cstring, string> = {\n  'Smileys & Emotion': 'Faces', 'People & Body': 'People', 'Animals & Nature': 'Nature', 'Food & Drink': 'Food',\n  'Travel & Places': 'Places', 'Activities': 'Play', 'Objects': 'Things', 'Symbols': 'Symbols', 'Flags': 'Flags',\n}\nconst NAME = new Map(NAMES.map(([e, n]) => [e, n]))\nconst tab = ref\u003Cstring>(props.recent.length ? 'recent' : GROUPS[0])\nconst query = ref('')\nconst shown = computed(() => {\n  const q = query.value.trim().toLowerCase()\n  if (q) { const words = q.split(\u002F\\s+\u002F); return NAMES.filter(([, n]) => words.every((w) => n.includes(w))).map(([e]) => e) }\n  if (tab.value === 'recent') return props.recent\n  const g = GROUPS.indexOf(tab.value as (typeof GROUPS)[number])\n  return NAMES.filter(([, , i]) => i === g).map(([e]) => e)\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"palette\" :class=\"{ 'palette--large': large }\">\n    \u003Cp class=\"palette__now\">\u003Cspan class=\"palette__brush\" aria-hidden=\"true\">{{ brush }}\u003C\u002Fspan>\u003Cspan class=\"mono-label\">{{ NAME.get(brush) }}\u003C\u002Fspan>\u003C\u002Fp>\n    \u003Cinput v-model=\"query\" class=\"palette__search\" type=\"search\" placeholder=\"Find an emoji: wave, cat, heart…\" aria-label=\"Find an emoji by name\" autocomplete=\"off\">\n    \u003Cdiv v-if=\"!query\" class=\"palette__tabs mono-label\" role=\"tablist\" aria-label=\"Emoji groups\">\n      \u003Cbutton v-if=\"recent.length\" type=\"button\" role=\"tab\" :aria-selected=\"tab === 'recent'\" @click=\"tab = 'recent'\">Recent\u003C\u002Fbutton>\n      \u003Cbutton v-for=\"g in GROUPS\" :key=\"g\" type=\"button\" role=\"tab\" :aria-selected=\"tab === g\" @click=\"tab = g\">{{ SHORT[g] ?? g }}\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"palette__grid\" role=\"group\" :aria-label=\"query ? `Emoji named ${query}` : 'Emoji'\">\n      \u003Cbutton v-for=\"e in shown\" :key=\"e\" type=\"button\" class=\"palette__e bare\" :aria-pressed=\"e === brush\" :aria-label=\"NAME.get(e)\" :title=\"NAME.get(e)\" @click=\"brush = e; emit('picked', e)\">{{ e }}\u003C\u002Fbutton>\n      \u003Cp v-if=\"!shown.length\" class=\"palette__none mono-label\">Nothing by that name.\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>","\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">setup\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">lang\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ts&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"language-javascript\">\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-variable constant_\">GROUPS\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">NAMES\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji\u002Fpalette-names&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> brush = defineModel&lt;string&gt;({ \u003Cspan class=\"hljs-attr\">required\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> props = defineProps&lt;{ \u003Cspan class=\"hljs-attr\">recent\u003C\u002Fspan>: string[]; large?: boolean }&gt;()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> emit = defineEmits&lt;{ \u003Cspan class=\"hljs-attr\">picked\u003C\u002Fspan>: [string] }&gt;()\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">SHORT\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, string&gt; = {\n  \u003Cspan class=\"hljs-string\">&#x27;Smileys &amp; Emotion&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Faces&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;People &amp; Body&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;People&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;Animals &amp; Nature&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Nature&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;Food &amp; Drink&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Food&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-string\">&#x27;Travel &amp; Places&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Places&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;Activities&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Play&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;Objects&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Things&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;Symbols&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Symbols&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;Flags&#x27;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Flags&#x27;\u003C\u002Fspan>,\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">NAME\u003C\u002Fspan> = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">NAMES\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">[e, n]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> [e, n]))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> tab = ref&lt;string&gt;(props.\u003Cspan class=\"hljs-property\">recent\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">&#x27;recent&#x27;\u003C\u002Fspan> : \u003Cspan class=\"hljs-variable constant_\">GROUPS\u003C\u002Fspan>[\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>])\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> query = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;&#x27;\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> shown = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> q = query.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">trim\u003C\u002Fspan>().\u003Cspan class=\"hljs-title function_\">toLowerCase\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (q) { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> words = q.\u003Cspan class=\"hljs-title function_\">split\u003C\u002Fspan>(\u003Cspan class=\"hljs-regexp\">\u002F\\s+\u002F\u003C\u002Fspan>); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">NAMES\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">filter\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">[, n]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> words.\u003Cspan class=\"hljs-title function_\">every\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">w\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> n.\u003Cspan class=\"hljs-title function_\">includes\u003C\u002Fspan>(w))).\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">[e]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> e) }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (tab.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;recent&#x27;\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> props.\u003Cspan class=\"hljs-property\">recent\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> g = \u003Cspan class=\"hljs-variable constant_\">GROUPS\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">indexOf\u003C\u002Fspan>(tab.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">as\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">typeof\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">GROUPS\u003C\u002Fspan>)[number])\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">NAMES\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">filter\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">[, , i]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> i === g).\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">[e]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> e)\n})\n\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;{ &#x27;palette--large&#x27;: large }&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette__now&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette__brush&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ brush }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;mono-label&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ NAME.get(brush) }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">input\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-model\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;query&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette__search&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;search&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">placeholder\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Find an emoji: wave, cat, heart…&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Find an emoji by name&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">autocomplete\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;off&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;!query&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette__tabs mono-label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tablist&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Emoji groups&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;recent.length&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tab&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-selected\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tab === &#x27;recent&#x27;&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tab = &#x27;recent&#x27;&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>Recent\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-for\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;g in GROUPS&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;g&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tab&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-selected\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tab === g&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tab = g&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ SHORT[g] ?? g }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette__grid&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;group&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;query ? `Emoji named ${query}` : &#x27;Emoji&#x27;&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-for\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;e in shown&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;e&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette__e bare&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-pressed\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;e === brush&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;NAME.get(e)&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:title\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;NAME.get(e)&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;brush = e; emit(&#x27;picked&#x27;, e)&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ e }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;!shown.length&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette__none mono-label&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>Nothing by that name.\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>",{"path":231,"lines":232,"code":233,"html":234},"app\u002Fcomposables\u002FuseEmojiCanvas.ts",165,"import { blockOf, INK, TILE, TILES, type Painter } from '@cw\u002Femoji\u002Fcanvas'\nimport type { JevJoined } from '@cw\u002Femoji'\n\n\u002F\u002F Cells live in a plain Map (a million would be too many to make reactive); `version` moves on whenever one changes.\nexport type CanvasCell = { emoji: string; jev: boolean }\n\u002F** `mine` also when Jev answered this visitor's stroke. *\u002F\nexport type Activity = { cell: number; emoji: string; jev: boolean; at: number; mine?: boolean; who?: string }\nexport type CanvasStats = { filled: number; painting: number; latest?: { id: number; cell: number; emoji: string; painter: Painter; at: string; who?: string } }\n\nconst FEED = 8\n\u002F** How long Jev's top candidates show on the canvas before its move lands. *\u002F\nconst WEIGH_MS = 450\nexport const PACE_MS = 30_000, PACE_SLOTS = 12\n\nexport function useEmojiCanvas(initial?: { stats?: CanvasStats | null }) {\n  const cells = new Map\u003Cnumber, CanvasCell>()\n  const version = ref(0)\n  const stats = initial?.stats\n  const filled = ref(stats?.filled ?? 0)\n  const painting = ref(stats?.painting ?? 0)\n  const activity = ref\u003CActivity[]>(stats?.latest ? [{ cell: stats.latest.cell, emoji: stats.latest.emoji, jev: stats.latest.painter === 'jev', at: Date.parse(stats.latest.at), who: stats.latest.who }] : [])\n  const live = ref\u003C'connecting' | 'live' | 'off'>('connecting')\n  const error = ref\u003Cstring>()\n  const ink = ref(INK)\n  const unlimited = ref(false)\n  \u002F** block → cells filled, and the emoji painted most there (the minimap's colour) *\u002F\n  const blocks = shallowRef(new Map\u003Cnumber, { n: number; emoji: string }>())\n  \u002F\u002F a tile read from the (briefly cached) API never overwrites these\n  const arrived = new Map\u003Cnumber, number>()\n  \u002F\u002F Jev's cells while its candidates show: the stream's echo waits for the reveal\n  const held = new Set\u003Cnumber>()\n  const ghosts = ref\u003CJevJoined['candidates']>([])\n  const motion = usePreferredReducedMotion()\n  const loaded = new Set\u003Cnumber>(), loading = new Set\u003Cnumber>()\n  \u002F\u002F oldest first; the window rolls on even when nothing arrives\n  const pace = ref\u003Cnumber[]>(Array.from({ length: PACE_SLOTS }, () => 0))\n  let paceSlot = 0\n  function roll() {\n    const slot = Math.floor(Date.now() \u002F PACE_MS), gap = paceSlot ? Math.min(PACE_SLOTS, slot - paceSlot) : 0\n    paceSlot = slot\n    if (gap > 0) pace.value = [...pace.value.slice(gap), ...Array.from({ length: gap }, () => 0)]\n  }\n  function tally(n: number) {\n    roll()\n    pace.value = [...pace.value.slice(0, -1), Math.max(0, pace.value.at(-1)! + n)]\n  }\n  useIntervalFn(roll, 5000)\n\n  function set(cell: number, c: CanvasCell | undefined) {\n    const was = cells.has(cell)\n    if (c) cells.set(cell, c); else cells.delete(cell)\n    if (c && !was) filled.value++\n    if (!c && was) filled.value--\n    version.value++\n  }\n  function note(cell: number, emoji: string, jev: boolean, mine = false, who?: string) {\n    activity.value = [{ cell, emoji, jev, at: Date.now(), mine, who }, ...activity.value].slice(0, FEED)\n  }\n  \u002F\u002F a new block takes the colour of its first emoji until the next full read\n  function bump(cell: number, emoji: string) {\n    const b = blockOf(cell), next = new Map(blocks.value), was = next.get(b)\n    next.set(b, { n: (was?.n ?? 0) + 1, emoji: was?.emoji || emoji })\n    blocks.value = next\n  }\n\n  async function load(x0: number, y0: number, x1: number, y1: number) {\n    const wanted: [number, number, number][] = []\n    const clamp = (v: number) => Math.min(TILES - 1, Math.max(0, Math.floor(v \u002F TILE)))\n    for (let ty = clamp(y0); ty \u003C= clamp(y1); ty++) for (let tx = clamp(x0); tx \u003C= clamp(x1); tx++) {\n      const k = ty * TILES + tx\n      if (!loaded.has(k) && !loading.has(k)) wanted.push([tx, ty, k])\n    }\n    await Promise.all(wanted.map(async ([tx, ty, k]) => {\n      loading.add(k)\n      try {\n        const r = await $fetch\u003C{ cells: [number, string, 0 | 1][] }>('\u002Fapi\u002Femoji\u002Ftile', { query: { tx, ty } })\n        for (const [cell, emoji, jev] of r.cells) if (!arrived.has(cell)) cells.set(cell, { emoji, jev: !!jev })\n        loaded.add(k)\n        version.value++\n      } catch {} finally { loading.delete(k) }\n    }))\n  }\n\n  let events: EventSource | undefined\n  const refill = useIntervalFn(() => { if (ink.value \u003C INK) ink.value++ }, 1000, { immediate: false })\n  async function connect() {\n    try {\n      const [s, map, me] = await Promise.all([\n        stats ? Promise.resolve(stats) : $fetch\u003CCanvasStats>('\u002Fapi\u002Femoji\u002Fstats'),\n        $fetch\u003C[number, number, string][]>('\u002Fapi\u002Femoji\u002Fmap'),\n        $fetch\u003C{ ink: number; unlimited: boolean }>('\u002Fapi\u002Femoji\u002Fink'),\n      ])\n      if (gone) return\n      filled.value = s.filled\n      painting.value = s.painting\n      blocks.value = new Map(map.map(([b, n, emoji]) => [b, { n, emoji }]))\n      ink.value = me.ink\n      unlimited.value = me.unlimited\n      \u002F\u002F on a reconnect EventSource sends the last id it saw itself\n      events = new EventSource(`\u002Fapi\u002Femoji\u002Fstream?after=${s.latest?.id ?? 0}`)\n      events.addEventListener('open', () => { live.value = 'live' })\n      events.addEventListener('error', () => { live.value = events?.readyState === EventSource.CLOSED ? 'off' : 'connecting' })\n      events.addEventListener('place', (e) => {\n        const [cell, emoji, jev, fresh, who] = JSON.parse((e as MessageEvent).data) as [number, string, 0 | 1, 0 | 1, string?]\n        if (held.has(cell)) return\n        \u002F\u002F an echo of this visitor's own stroke is already shown: it must not flash again\n        const at = arrived.get(cell)\n        const mine = at !== undefined && Date.now() - at \u003C 10000 && cells.get(cell)?.emoji === emoji\n        if (!mine) arrived.set(cell, Date.now())\n        if (fresh && !cells.has(cell)) filled.value++\n        if (fresh) bump(cell, emoji)\n        cells.set(cell, { emoji, jev: !!jev })\n        version.value++\n        if (!mine) { note(cell, emoji, !!jev, false, who || undefined); tally(1) }\n      })\n    } catch { live.value = 'off' }\n    refill.resume()\n  }\n  let gone = false\n  onMounted(connect)\n  onBeforeUnmount(() => { gone = true; events?.close() })\n\n  async function paint(stroke: number[], emoji: string) {\n    if (!stroke.length) return\n    error.value = undefined\n    const before = stroke.map((c) => cells.get(c))\n    const overJev = before.filter((c) => c?.jev).length\n    for (const c of stroke) { arrived.set(c, Date.now()); set(c, { emoji, jev: false }) }\n    note(stroke.at(-1)!, emoji, false, true)\n    tally(stroke.length)\n    ink.value = Math.max(0, ink.value - stroke.length)\n    try {\n      const r = await $fetch\u003C{ ink: number }>('\u002Fapi\u002Femoji\u002Fpaint', { method: 'POST', body: { cells: stroke, emoji, device: deviceId() } })\n      track('emoji_stroke', { cells: stroke.length, emoji, overJev })\n      ink.value = r.ink\n    } catch (e) {\n      stroke.forEach((c, i) => set(c, before[i]))\n      tally(-stroke.length)\n      const err = e as { statusCode?: number; data?: { data?: { ink?: number } } }\n      if (err.statusCode === 429) { ink.value = err.data?.data?.ink ?? 0; error.value = 'Out of ink: it comes back one cell a second.' } else error.value = 'That stroke didn’t land. Try again.'\n    }\n  }\n\n  async function jevJoin(stroke: number[]): Promise\u003CJevJoined | undefined> {\n    try {\n      const r = await $fetch\u003CJevJoined>('\u002Fapi\u002Femoji\u002Fjev', { method: 'POST', body: { stroke } })\n      if (motion.value !== 'reduce') {\n        for (const c of r.cells) held.add(c)\n        ghosts.value = r.candidates\n        await new Promise((done) => setTimeout(done, WEIGH_MS))\n        ghosts.value = []\n      }\n      for (const c of r.cells) { held.delete(c); arrived.set(c, Date.now()); set(c, { emoji: r.emoji, jev: true }) }\n      note(r.cell, r.emoji, true, true)\n      tally(r.cells.length)\n      track('emoji_jev', { emoji: r.emoji, p: Math.round(r.p * 100), cells: r.cells.length })\n      return r\n    } catch (e) {\n      const status = (e as { statusCode?: number }).statusCode\n      error.value = status === 429 ? 'Jev is resting a moment.' : 'Jev didn’t answer. Try again.'\n    }\n  }\n\n  return { cells, ghosts, version, filled, painting, activity, pace, live, error, ink, unlimited, blocks, arrived, load, paint, jevJoin }\n}","\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { blockOf, \u003Cspan class=\"hljs-variable constant_\">INK\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">TILE\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">TILES\u003C\u002Fspan>, \u003Cspan class=\"hljs-keyword\">type\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Painter\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji\u002Fcanvas&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">type\u003C\u002Fspan> { \u003Cspan class=\"hljs-title class_\">JevJoined\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F Cells live in a plain Map (a million would be too many to make reactive); `version` moves on whenever one changes.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">type\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">CanvasCell\u003C\u002Fspan> = { \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan> }\n\u003Cspan class=\"hljs-comment\">\u002F** `mine` also when Jev answered this visitor&#x27;s stroke. *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">type\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Activity\u003C\u002Fspan> = { \u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">mine\u003C\u002Fspan>?: \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">who\u003C\u002Fspan>?: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan> }\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">type\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">CanvasStats\u003C\u002Fspan> = { \u003Cspan class=\"hljs-attr\">filled\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">painting\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">latest\u003C\u002Fspan>?: { \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">painter\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Painter\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">who\u003C\u002Fspan>?: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan> } }\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">FEED\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">8\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F** How long Jev&#x27;s top candidates show on the canvas before its move lands. *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">WEIGH_MS\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">450\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">PACE_MS\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">30_000\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">PACE_SLOTS\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">12\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">useEmojiCanvas\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">initial\u003C\u002Fspan>?: { stats?: CanvasStats | \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan> }\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cells = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">CanvasCell\u003C\u002Fspan>&gt;()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> version = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> stats = initial?.\u003Cspan class=\"hljs-property\">stats\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> filled = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(stats?.\u003Cspan class=\"hljs-property\">filled\u003C\u002Fspan> ?? \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> painting = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(stats?.\u003Cspan class=\"hljs-property\">painting\u003C\u002Fspan> ?? \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> activity = ref&lt;\u003Cspan class=\"hljs-title class_\">Activity\u003C\u002Fspan>[]&gt;(stats?.\u003Cspan class=\"hljs-property\">latest\u003C\u002Fspan> ? [{ \u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: stats.\u003Cspan class=\"hljs-property\">latest\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: stats.\u003Cspan class=\"hljs-property\">latest\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: stats.\u003Cspan class=\"hljs-property\">latest\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">painter\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;jev&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">parse\u003C\u002Fspan>(stats.\u003Cspan class=\"hljs-property\">latest\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">at\u003C\u002Fspan>), \u003Cspan class=\"hljs-attr\">who\u003C\u002Fspan>: stats.\u003Cspan class=\"hljs-property\">latest\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">who\u003C\u002Fspan> }] : [])\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> live = ref&lt;\u003Cspan class=\"hljs-string\">&#x27;connecting&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;live&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;off&#x27;\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-string\">&#x27;connecting&#x27;\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> error = ref&lt;\u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>&gt;()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> ink = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">INK\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> unlimited = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-comment\">\u002F** block → cells filled, and the emoji painted most there (the minimap&#x27;s colour) *\u002F\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> blocks = \u003Cspan class=\"hljs-title function_\">shallowRef\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">n\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan> }&gt;())\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F a tile read from the (briefly cached) API never overwrites these\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> arrived = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>&gt;()\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F Jev&#x27;s cells while its candidates show: the stream&#x27;s echo waits for the reveal\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> held = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Set\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>&gt;()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> ghosts = ref&lt;\u003Cspan class=\"hljs-title class_\">JevJoined\u003C\u002Fspan>[\u003Cspan class=\"hljs-string\">&#x27;candidates&#x27;\u003C\u002Fspan>]&gt;([])\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> motion = \u003Cspan class=\"hljs-title function_\">usePreferredReducedMotion\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> loaded = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Set\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>&gt;(), loading = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Set\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>&gt;()\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F oldest first; the window rolls on even when nothing arrives\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> pace = ref&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>[]&gt;(\u003Cspan class=\"hljs-title class_\">Array\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">from\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">length\u003C\u002Fspan>: \u003Cspan class=\"hljs-variable constant_\">PACE_SLOTS\u003C\u002Fspan> }, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>))\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> paceSlot = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">roll\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> slot = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>() \u002F \u003Cspan class=\"hljs-variable constant_\">PACE_MS\u003C\u002Fspan>), gap = paceSlot ? \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">PACE_SLOTS\u003C\u002Fspan>, slot - paceSlot) : \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>\n    paceSlot = slot\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (gap &gt; \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) pace.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = [...pace.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">slice\u003C\u002Fspan>(gap), ...\u003Cspan class=\"hljs-title class_\">Array\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">from\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">length\u003C\u002Fspan>: gap }, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>)]\n  }\n  \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">tally\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">n\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-title function_\">roll\u003C\u002Fspan>()\n    pace.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = [...pace.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">slice\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, -\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>), \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, pace.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">at\u003C\u002Fspan>(-\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>)! + n)]\n  }\n  \u003Cspan class=\"hljs-title function_\">useIntervalFn\u003C\u002Fspan>(roll, \u003Cspan class=\"hljs-number\">5000\u003C\u002Fspan>)\n\n  \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">c\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">CanvasCell\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> was = cells.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(cell)\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (c) cells.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(cell, c); \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> cells.\u003Cspan class=\"hljs-title function_\">delete\u003C\u002Fspan>(cell)\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (c &amp;&amp; !was) filled.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>++\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!c &amp;&amp; was) filled.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>--\n    version.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>++\n  }\n  \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">note\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan>, mine = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">who\u003C\u002Fspan>?: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>\u003C\u002Fspan>) {\n    activity.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = [{ cell, emoji, jev, \u003Cspan class=\"hljs-attr\">at\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>(), mine, who }, ...activity.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>].\u003Cspan class=\"hljs-title function_\">slice\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">FEED\u003C\u002Fspan>)\n  }\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F a new block takes the colour of its first emoji until the next full read\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">bump\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> b = \u003Cspan class=\"hljs-title function_\">blockOf\u003C\u002Fspan>(cell), next = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>(blocks.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>), was = next.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(b)\n    next.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(b, { \u003Cspan class=\"hljs-attr\">n\u003C\u002Fspan>: (was?.\u003Cspan class=\"hljs-property\">n\u003C\u002Fspan> ?? \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) + \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: was?.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan> || emoji })\n    blocks.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = next\n  }\n\n  \u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">load\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">x0\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y0\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">x1\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y1\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">wanted\u003C\u002Fspan>: [\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>][] = []\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">clamp\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">v\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\u003C\u002Fspan>) =&gt; \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">TILES\u003C\u002Fspan> - \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(v \u002F \u003Cspan class=\"hljs-variable constant_\">TILE\u003C\u002Fspan>)))\n    \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> ty = \u003Cspan class=\"hljs-title function_\">clamp\u003C\u002Fspan>(y0); ty &lt;= \u003Cspan class=\"hljs-title function_\">clamp\u003C\u002Fspan>(y1); ty++) \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> tx = \u003Cspan class=\"hljs-title function_\">clamp\u003C\u002Fspan>(x0); tx &lt;= \u003Cspan class=\"hljs-title function_\">clamp\u003C\u002Fspan>(x1); tx++) {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> k = ty * \u003Cspan class=\"hljs-variable constant_\">TILES\u003C\u002Fspan> + tx\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!loaded.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(k) &amp;&amp; !loading.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(k)) wanted.\u003Cspan class=\"hljs-title function_\">push\u003C\u002Fspan>([tx, ty, k])\n    }\n    \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">all\u003C\u002Fspan>(wanted.\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_\">async\u003C\u002Fspan> ([tx, ty, k]) =&gt; {\n      loading.\u003Cspan class=\"hljs-title function_\">add\u003C\u002Fspan>(k)\n      \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> r = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> $fetch&lt;{ \u003Cspan class=\"hljs-attr\">cells\u003C\u002Fspan>: [\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> | \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>][] }&gt;(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Femoji\u002Ftile&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">query\u003C\u002Fspan>: { tx, ty } })\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> [cell, emoji, jev] \u003Cspan class=\"hljs-keyword\">of\u003C\u002Fspan> r.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!arrived.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(cell)) cells.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(cell, { emoji, \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: !!jev })\n        loaded.\u003Cspan class=\"hljs-title function_\">add\u003C\u002Fspan>(k)\n        version.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>++\n      } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> {} \u003Cspan class=\"hljs-keyword\">finally\u003C\u002Fspan> { loading.\u003Cspan class=\"hljs-title function_\">delete\u003C\u002Fspan>(k) }\n    }))\n  }\n\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">events\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">EventSource\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> refill = \u003Cspan class=\"hljs-title function_\">useIntervalFn\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (ink.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> &lt; \u003Cspan class=\"hljs-variable constant_\">INK\u003C\u002Fspan>) ink.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>++ }, \u003Cspan class=\"hljs-number\">1000\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">immediate\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> })\n  \u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">connect\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> [s, map, me] = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">all\u003C\u002Fspan>([\n        stats ? \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">resolve\u003C\u002Fspan>(stats) : $fetch&lt;\u003Cspan class=\"hljs-title class_\">CanvasStats\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Femoji\u002Fstats&#x27;\u003C\u002Fspan>),\n        $fetch&lt;[\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>][]&gt;(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Femoji\u002Fmap&#x27;\u003C\u002Fspan>),\n        $fetch&lt;{ \u003Cspan class=\"hljs-attr\">ink\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">unlimited\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan> }&gt;(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Femoji\u002Fink&#x27;\u003C\u002Fspan>),\n      ])\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (gone) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n      filled.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">filled\u003C\u002Fspan>\n      painting.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">painting\u003C\u002Fspan>\n      blocks.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>(map.\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">[b, n, emoji]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> [b, { n, emoji }]))\n      ink.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = me.\u003Cspan class=\"hljs-property\">ink\u003C\u002Fspan>\n      unlimited.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = me.\u003Cspan class=\"hljs-property\">unlimited\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-comment\">\u002F\u002F on a reconnect EventSource sends the last id it saw itself\u003C\u002Fspan>\n      events = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">EventSource\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">`\u002Fapi\u002Femoji\u002Fstream?after=\u003Cspan class=\"hljs-subst\">${s.latest?.id ?? \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>}\u003C\u002Fspan>`\u003C\u002Fspan>)\n      events.\u003Cspan class=\"hljs-title function_\">addEventListener\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;open&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { live.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;live&#x27;\u003C\u002Fspan> })\n      events.\u003Cspan class=\"hljs-title function_\">addEventListener\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;error&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { live.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = events?.\u003Cspan class=\"hljs-property\">readyState\u003C\u002Fspan> === \u003Cspan class=\"hljs-title class_\">EventSource\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">CLOSED\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">&#x27;off&#x27;\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">&#x27;connecting&#x27;\u003C\u002Fspan> })\n      events.\u003Cspan class=\"hljs-title function_\">addEventListener\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;place&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">e\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> [cell, emoji, jev, fresh, who] = \u003Cspan class=\"hljs-title class_\">JSON\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">parse\u003C\u002Fspan>((e \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">MessageEvent\u003C\u002Fspan>).\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> [\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> | \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> | \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>?]\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (held.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(cell)) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F an echo of this visitor&#x27;s own stroke is already shown: it must not flash again\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> at = arrived.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell)\n        \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> mine = at !== \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>() - at &lt; \u003Cspan class=\"hljs-number\">10000\u003C\u002Fspan> &amp;&amp; cells.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell)?.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan> === emoji\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!mine) arrived.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(cell, \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>())\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (fresh &amp;&amp; !cells.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(cell)) filled.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>++\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (fresh) \u003Cspan class=\"hljs-title function_\">bump\u003C\u002Fspan>(cell, emoji)\n        cells.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(cell, { emoji, \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: !!jev })\n        version.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>++\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!mine) { \u003Cspan class=\"hljs-title function_\">note\u003C\u002Fspan>(cell, emoji, !!jev, \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>, who || \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>); \u003Cspan class=\"hljs-title function_\">tally\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) }\n      })\n    } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> { live.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;off&#x27;\u003C\u002Fspan> }\n    refill.\u003Cspan class=\"hljs-title function_\">resume\u003C\u002Fspan>()\n  }\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> gone = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-title function_\">onMounted\u003C\u002Fspan>(connect)\n  \u003Cspan class=\"hljs-title function_\">onBeforeUnmount\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { gone = \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>; events?.\u003Cspan class=\"hljs-title function_\">close\u003C\u002Fspan>() })\n\n  \u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">paint\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">stroke\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>[], \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!stroke.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n    error.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> before = stroke.\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">c\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> cells.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(c))\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> overJev = before.\u003Cspan class=\"hljs-title function_\">filter\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">c\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> c?.\u003Cspan class=\"hljs-property\">jev\u003C\u002Fspan>).\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> c \u003Cspan class=\"hljs-keyword\">of\u003C\u002Fspan> stroke) { arrived.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(c, \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()); \u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(c, { emoji, \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> }) }\n    \u003Cspan class=\"hljs-title function_\">note\u003C\u002Fspan>(stroke.\u003Cspan class=\"hljs-title function_\">at\u003C\u002Fspan>(-\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>)!, emoji, \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>)\n    \u003Cspan class=\"hljs-title function_\">tally\u003C\u002Fspan>(stroke.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>)\n    ink.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, ink.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> - stroke.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>)\n    \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> r = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> $fetch&lt;{ \u003Cspan class=\"hljs-attr\">ink\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan> }&gt;(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Femoji\u002Fpaint&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">method\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;POST&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">body\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">cells\u003C\u002Fspan>: stroke, emoji, \u003Cspan class=\"hljs-attr\">device\u003C\u002Fspan>: \u003Cspan class=\"hljs-title function_\">deviceId\u003C\u002Fspan>() } })\n      \u003Cspan class=\"hljs-title function_\">track\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;emoji_stroke&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">cells\u003C\u002Fspan>: stroke.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>, emoji, overJev })\n      ink.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = r.\u003Cspan class=\"hljs-property\">ink\u003C\u002Fspan>\n    } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> (e) {\n      stroke.\u003Cspan class=\"hljs-title function_\">forEach\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">c, i\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(c, before[i]))\n      \u003Cspan class=\"hljs-title function_\">tally\u003C\u002Fspan>(-stroke.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>)\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> err = e \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">statusCode\u003C\u002Fspan>?: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>?: { \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>?: { \u003Cspan class=\"hljs-attr\">ink\u003C\u002Fspan>?: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan> } } }\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (err.\u003Cspan class=\"hljs-property\">statusCode\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">429\u003C\u002Fspan>) { ink.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = err.\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>?.\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>?.\u003Cspan class=\"hljs-property\">ink\u003C\u002Fspan> ?? \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>; error.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;Out of ink: it comes back one cell a second.&#x27;\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> error.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;That stroke didn’t land. Try again.&#x27;\u003C\u002Fspan>\n    }\n  }\n\n  \u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">jevJoin\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">stroke\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>[]\u003C\u002Fspan>): \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-title class_\">JevJoined\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>&gt; {\n    \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> r = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> $fetch&lt;\u003Cspan class=\"hljs-title class_\">JevJoined\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Femoji\u002Fjev&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">method\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;POST&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">body\u003C\u002Fspan>: { stroke } })\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (motion.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> !== \u003Cspan class=\"hljs-string\">&#x27;reduce&#x27;\u003C\u002Fspan>) {\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> c \u003Cspan class=\"hljs-keyword\">of\u003C\u002Fspan> r.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>) held.\u003Cspan class=\"hljs-title function_\">add\u003C\u002Fspan>(c)\n        ghosts.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = r.\u003Cspan class=\"hljs-property\">candidates\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">done\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-built_in\">setTimeout\u003C\u002Fspan>(done, \u003Cspan class=\"hljs-variable constant_\">WEIGH_MS\u003C\u002Fspan>))\n        ghosts.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = []\n      }\n      \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> c \u003Cspan class=\"hljs-keyword\">of\u003C\u002Fspan> r.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>) { held.\u003Cspan class=\"hljs-title function_\">delete\u003C\u002Fspan>(c); arrived.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(c, \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()); \u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>(c, { \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: r.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan> }) }\n      \u003Cspan class=\"hljs-title function_\">note\u003C\u002Fspan>(r.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>, r.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>)\n      \u003Cspan class=\"hljs-title function_\">tally\u003C\u002Fspan>(r.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>)\n      \u003Cspan class=\"hljs-title function_\">track\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;emoji_jev&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: r.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">p\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">round\u003C\u002Fspan>(r.\u003Cspan class=\"hljs-property\">p\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">100\u003C\u002Fspan>), \u003Cspan class=\"hljs-attr\">cells\u003C\u002Fspan>: r.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan> })\n      \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> r\n    } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> (e) {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> status = (e \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">statusCode\u003C\u002Fspan>?: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan> }).\u003Cspan class=\"hljs-property\">statusCode\u003C\u002Fspan>\n      error.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = status === \u003Cspan class=\"hljs-number\">429\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">&#x27;Jev is resting a moment.&#x27;\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">&#x27;Jev didn’t answer. Try again.&#x27;\u003C\u002Fspan>\n    }\n  }\n\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { cells, ghosts, version, filled, painting, activity, pace, live, error, ink, unlimited, blocks, arrived, load, paint, jevJoin }\n}",{"path":236,"lines":237,"code":238,"html":239},"app\u002Fpages\u002Flab\u002Femoji\u002Findex.vue",310,"\u003Cscript setup lang=\"ts\">\nimport type { ComponentPublicInstance } from 'vue'\nimport { PhArrowsOutCardinal, PhBoundingBox, PhCrosshairSimple, PhEyedropper, PhPencilSimple, PhShuffle, PhSparkle } from '@phosphor-icons\u002Fvue'\nimport { BLOCK, cellAt, cellLabel, CELLS, INK, isCell, isPaletteEmoji, largestEmpty, PALETTE, SIZE, xy } from '@cw\u002Femoji\u002Fcanvas'\nimport { emojiInfo } from '@cw\u002Femoji'\nimport type { JevJoined } from '@cw\u002Femoji'\nimport { PACE_MS, PACE_SLOTS, type Activity, type CanvasStats } from '~\u002Fcomposables\u002FuseEmojiCanvas'\nimport { painterOf } from '#core\u002Fdomain\u002Fpainters'\n\ndefinePageMeta({ jevvie: 'emoji' })\nconst { data: stats } = await useFetch\u003CCanvasStats>('\u002Fapi\u002Femoji\u002Fstats', { key: 'emoji-stats' })\nconst canvas = useEmojiCanvas({ stats: stats.value })\nconst presence = usePresence('emoji')\nconst board = ref\u003C{ zoomBy: (factor: number) => void; easeTo: (to: { x: number; y: number; size: number }, ms?: number) => void; ping: (cell: number) => void; renderMode?: 'webgpu' | 'canvas' }>()\n\nconst route = useRoute()\nfunction opening() {\n  const at = String(route.query.at ?? '').split(',').map(Number)\n  if (at.length === 2 && cellAt(Math.floor(at[0]!), Math.floor(at[1]!)) !== undefined) return { x: at[0]! + 0.5, y: at[1]! + 0.5 }\n  const cell = Number(route.query.cell)\n  const start = route.query.cell !== undefined && isCell(cell) ? cell : stats.value?.latest?.cell\n  return start === undefined ? { x: SIZE \u002F 2, y: SIZE \u002F 2 } : { x: xy(start).x + 0.5, y: xy(start).y + 0.5 }\n}\nconst view = ref({ ...opening(), size: 28 })\nconst span = ref({ w: 40, h: 25 })\n\u002F\u002F during a fly-to only the last view's tiles load, on landing, so they don't queue behind the dozens passed over\nlet flying = false, held: [number, number, number, number] | undefined\nfunction need(x0: number, y0: number, x1: number, y1: number) {\n  span.value = { w: x1 - x0, h: y1 - y0 }\n  if (flying) { held = [x0, y0, x1, y1]; return }\n  void canvas.load(x0, y0, x1, y1)\n}\nfunction land() { flying = false; if (held) { const h = held; held = undefined; need(...h) } }\nconst stopGlide = () => { cancelAnimationFrame(glide); if (flying) land() }\nconst centre = () => cellAt(Math.floor(view.value.x), Math.floor(view.value.y))\n\u002F\u002F a jump to a cell glides there and pings it, like a map search result\nfunction go(cell: number) { const { x, y } = xy(cell); void flyTo(x + 0.5, y + 0.5, Math.max(view.value.size, 24)).then(() => board.value?.ping(cell)) }\n\nconst tool = ref\u003C'paint' | 'move' | 'pick'>('paint')\nconst paletteOpen = ref(false)\n\u002F\u002F typed plainly: Win's full component type is too complex for the outside-click check\nconst pop = useTemplateRef\u003CComponentPublicInstance>('pop'), brushButton = useTemplateRef\u003CHTMLButtonElement>('brushButton')\n\u002F\u002F the brush button toggles it itself\nonKeyStroke('Escape', () => { paletteOpen.value = false })\nonClickOutside(() => unrefElement(pop), () => { paletteOpen.value = false }, { ignore: [brushButton] })\nconst TOOLS = [{ id: 'paint', label: 'Paint', tip: 'Paint · drag', icon: PhPencilSimple }, { id: 'move', label: 'Move', tip: 'Move · space · double-click zooms', icon: PhArrowsOutCardinal }, { id: 'pick', label: 'Pick', tip: 'Pick up · ⌥ click · double-click zooms', icon: PhEyedropper }] as const\nconst brush = useStored('emojiBrush', '🌊', isPaletteEmoji)\nconst recent = useStored\u003Cstring[]>('emojiRecent', [], (v): v is string[] => Array.isArray(v) && v.every(isPaletteEmoji))\nfunction useBrush(e: string) { brush.value = e; tool.value = 'paint' }\nconst randomBrush = () => useBrush(PALETTE[Math.floor(Math.random() * PALETTE.length)]!)\nwatch(brush, () => { tool.value = 'paint' })\n\n\u002F\u002F Glide to new paint only while nobody is using the canvas, after a delay and at most every few seconds.\nconst follow = useStored('emojiFollow', true, oneOf(true, false))\nconst FOLLOW = [{ value: 'on', label: 'Follow new paint', note: 'glide there' }, { value: 'off', label: 'Stay put', note: 'the view is yours' }] as const\nconst FOLLOW_DELAY = 1500, FOLLOW_COOLDOWN = 4000, GLIDE = 700\nlet busy = false, freeSince = 0, lastGlide = 0, pending: number | undefined, followTimer = 0, glide = 0\nconst motion = usePreferredReducedMotion()\nfunction onBusy(on: boolean) {\n  busy = on\n  if (on) { stopGlide(); clearTimeout(followTimer) } else { freeSince = Date.now(); tryFollow() }\n}\nfunction tryFollow() {\n  clearTimeout(followTimer)\n  if (!follow.value || pending === undefined || busy) return\n  const wait = Math.max(freeSince + FOLLOW_DELAY, lastGlide + FOLLOW_COOLDOWN) - Date.now()\n  if (wait > 0) { followTimer = window.setTimeout(tryFollow, wait); return }\n  const { x, y } = xy(pending)\n  pending = undefined\n  lastGlide = Date.now()\n  glideTo(x + 0.5, y + 0.5)\n}\nconst latest = computed(() => canvas.activity.value[0])\nfunction toLatest() {\n  if (!latest.value) return\n  const { x, y } = xy(latest.value.cell), cell = latest.value.cell\n  void flyTo(x + 0.5, y + 0.5, Math.max(view.value.size, 24)).then(() => board.value?.ping(cell))\n}\n\u002F\u002F the middle of the largest empty rectangle of blocks\nfunction toSpace() {\n  const { bx, by, w, h } = largestEmpty((b) => canvas.blocks.value.has(b))\n  const x = (bx + w \u002F 2) * BLOCK, y = (by + h \u002F 2) * BLOCK, cell = cellAt(Math.floor(x), Math.floor(y))\n  track('emoji_space', { blocks: w * h })\n  void flyTo(x, y, Math.max(view.value.size, 24)).then(() => { if (cell !== undefined) board.value?.ping(cell) })\n}\nfunction flyTo(x: number, y: number, size: number): Promise\u003Cvoid> {\n  stopGlide()\n  const from = { ...view.value }\n  if (motion.value === 'reduce') { view.value = { ...from, x, y, size }; return Promise.resolve() }\n  const dist = Math.hypot(x - from.x, y - from.y)\n  const span = Math.max(1, Math.min(innerWidth, innerHeight))\n  \u002F\u002F no further out than 6px a cell, since every tile in view on the way is fetched (1px would be hundreds)\n  const far = Math.max(6, Math.min(from.size, size, span \u002F Math.max(1, dist * 1.4)))\n  const ms = 700 + Math.min(900, dist * 1.2)\n  const start = performance.now()\n  flying = true\n  return new Promise((done) => {\n    const step = (t: number) => {\n      const k = Math.min(1, (t - start) \u002F ms), e = k \u003C 0.5 ? 4 * k ** 3 : 1 - (-2 * k + 2) ** 3 \u002F 2\n      \u002F\u002F on a log scale so the zoom feels even\n      const ends = Math.exp(Math.log(from.size) + (Math.log(size) - Math.log(from.size)) * e)\n      const pull = Math.sin(Math.PI * k)\n      view.value = { x: from.x + (x - from.x) * e, y: from.y + (y - from.y) * e, size: Math.exp(Math.log(ends) + (Math.log(far) - Math.log(ends)) * pull) }\n      if (k \u003C 1) glide = requestAnimationFrame(step); else { land(); done() }\n    }\n    glide = requestAnimationFrame(step)\n  })\n}\nfunction glideTo(x: number, y: number) {\n  stopGlide()\n  const from = { ...view.value }, start = performance.now()\n  if (motion.value === 'reduce') { view.value = { ...from, x, y }; return }\n  const step = (t: number) => {\n    const k = Math.min(1, (t - start) \u002F GLIDE), e = 1 - (1 - k) ** 3\n    view.value = { ...view.value, x: from.x + (x - from.x) * e, y: from.y + (y - from.y) * e }\n    if (k \u003C 1) glide = requestAnimationFrame(step)\n  }\n  glide = requestAnimationFrame(step)\n}\nwatch(() => canvas.activity.value[0], (a) => { if (a && !a.mine) { pending = a.cell; tryFollow() } })\nwatch(follow, tryFollow)\nonBeforeUnmount(() => { stopGlide(); clearTimeout(followTimer) })\n\nconst joinIn = useStored('emojiJevJoins', true, oneOf(true, false))\nconst jevThinking = ref(false)\nconst jevLast = ref\u003CJevJoined>()\nlet lastStroke: number[] = []\nasync function jev(stroke: number[]) {\n  jevThinking.value = true\n  const r = await canvas.jevJoin(stroke)\n  jevThinking.value = false\n  if (r) jevLast.value = r\n}\nasync function onStroke(stroke: number[]) {\n  lastStroke = stroke\n  recent.value = [brush.value, ...recent.value.filter((e) => e !== brush.value)].slice(0, 24)\n  await canvas.paint(stroke, brush.value)\n  if (joinIn.value && !canvas.error.value) await jev(stroke)\n}\n\nconst EMOJI = PALETTE.map((e) => ({ value: e, label: `${e} ${emojiInfo(e).name}` }))\nconst jevTurn = () => jev(lastStroke.length ? lastStroke : [centre()!])\nuseSiteTool(() => ({\n  name: 'emoji_paint',\n  description: 'Paint an emoji on the shared canvas, in the middle of the view',\n  arg: { name: 'emoji', description: 'One emoji from the canvas’s palette', values: EMOJI },\n  doing: (label) => `painting ${label?.split(' ')[0]}`,\n  target: () => document.querySelector('.canvas-win'),\n  run: async (value) => {\n    const cell = centre()\n    if (!value || cell === undefined) return\n    useBrush(value)\n    await onStroke([cell])\n    return `Painted ${value} at ${cellLabel(cell)}. Ink left: ${canvas.ink.value}.`\n  },\n}))\nuseSiteTool(() => ({\n  name: 'emoji_latest',\n  description: 'Show the latest emoji painted on the canvas, by anyone',\n  doing: () => 'going to the latest paint',\n  target: () => document.querySelector('.canvas-win'),\n  run: () => { toLatest(); return latest.value ? `${latest.value.emoji} at ${cellLabel(latest.value.cell)}.` : 'Nothing painted yet.' },\n  readOnly: true,\n}))\nuseSiteTool(() => ({\n  name: 'emoji_space',\n  description: 'Go to the largest empty area of the canvas, to paint where nobody has',\n  doing: () => 'looking for room',\n  target: () => document.querySelector('.canvas-win'),\n  run: () => { toSpace(); return 'Gliding to the largest empty area.' },\n  readOnly: true,\n}))\nuseSiteTool(() => ({\n  name: 'emoji_jev',\n  description: 'Let Jev paint an emoji next to the last stroke, or in the middle of the view',\n  doing: () => 'asking Jev to paint',\n  target: () => document.querySelector('.canvas-win'),\n  run: jevTurn,\n}))\nconst pct = (p: number) => `${Math.round(p * 100)}%`\n\n\u002F\u002F The stream reconnects every few minutes: a drop shows only once it has lasted, so that never flashes Offline.\nconst shownLive = ref(canvas.live.value)\nconst settle = useTimeoutFn(() => { shownLive.value = canvas.live.value }, 3000, { immediate: false })\nwatch(canvas.live, (state) => {\n  if (state === 'live') { settle.stop(); shownLive.value = 'live' } else if (shownLive.value === 'live') settle.start()\n  else shownLive.value = state\n})\nconst liveLabel = computed(() => ({ live: 'Live', connecting: 'Connecting', off: 'Offline' })[shownLive.value])\nconst paceTotal = computed(() => canvas.pace.value.reduce((a, b) => a + b, 0))\n\u002F\u002F a floor of 1, so a quiet stretch sits on the baseline instead of the sparkline's middle\nconst paceMax = computed(() => Math.max(1, ...canvas.pace.value))\nconst paceMinutes = PACE_MS * PACE_SLOTS \u002F 60000\nconst activityTab = ref('latest')\n\u002F\u002F the pattern bar: recent emoji first, then a few from the palette so it is never empty\n\u002F\u002F (the stored ones only once mounted: the server cannot see them)\nconst quick = computed(() => [...new Set([...(mounted.value ? recent.value : []), ...PALETTE.slice(0, 12)])].filter((e) => !mounted.value || e !== brush.value).slice(0, 12))\nconst ACTIVITY_TABS = [{ id: 'latest', label: 'Latest' }, { id: 'here', label: 'Here' }]\nconst KEYS_TOUCH = [{ key: 'drag', does: 'paint' }, { key: 'two fingers', does: 'pan and zoom' }, { key: 'hold', does: 'pick up' }]\nconst inkLabel = computed(() => (canvas.unlimited.value ? 'Unlimited ink' : `${canvas.ink.value} \u002F ${INK} ink`))\n\u002F\u002F relative times show only in the browser: the server's now is not the browser's\nconst mounted = useMounted()\nconst now = useTimestamp({ interval: 5000 })\nconst whoOf = (a: Activity) => {\n  if (a.jev) return { name: 'Jev', color: 'var(--color-amber)' }\n  if (a.mine) return { name: 'You', color: 'var(--color-ink)' }\n  const p = a.who ? painterOf(a.who) : undefined\n  return p ? { name: p.name, color: `var(--color-${p.color})` } : { name: 'Someone', color: 'var(--color-ash)' }\n}\nconst ago = (at: number) => { const s = Math.max(0, Math.round((now.value - at) \u002F 1000)); return s \u003C 60 ? `${s}s` : s \u003C 3600 ? `${Math.round(s \u002F 60)}m` : `${Math.round(s \u002F 3600)}h` }\n\nconst { canonical } = usePageSeo({\n  title: () => '1 Million Emojis',\n  description: () => 'A shared 1,000 × 1,000 emoji canvas where humans paint and Jev paints alongside them.',\n  \u002F\u002F a 1.91:1 card of the canvas and its logo (scripts\u002Fshots.ts), so link previews don't crop it\n  image: '\u002Flab\u002Femoji-card.png',\n})\nconst shareUrl = computed(() => `${canonical.value}?at=${Math.floor(view.value.x)},${Math.floor(view.value.y)}`)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Carticle class=\"lab-stage bg-paper\">\n      \u003CWin title=\"1 MILLION EMOJIS\" as=\"figure\" label=\"The canvas\" class=\"canvas-win\" flush data-shot :draggable=\"false\" :minimizable=\"false\">\n        \u003Ctemplate #meta>\n          \u003Cspan class=\"canvas__live\" :class=\"`is-${shownLive}`\">\u003CGlyphSpinner v-if=\"shownLive === 'connecting'\" set=\"shade\" \u002F>\u003Ctemplate v-else>■\u003C\u002Ftemplate> {{ liveLabel }}\u003C\u002Fspan>\u003Ctemplate v-if=\"presence?.here\"> · {{ presence.here }} here\u003C\u002Ftemplate> · {{ canvas.filled.value.toLocaleString('en') }} \u002F {{ CELLS.toLocaleString('en') }}\n        \u003C\u002Ftemplate>\n        \u003Cdiv class=\"paint\">\n          \u003C!-- MacPaint's tool column: the tools, the view, and Jev's turn -->\n          \u003CHudTips class=\"tools\" side=\"right\" role=\"toolbar\" aria-label=\"Tools\" aria-orientation=\"vertical\">\n            \u003Cdiv class=\"tools__group\" role=\"group\" aria-label=\"Tool\">\n              \u003CBtn v-for=\"t in TOOLS\" :key=\"t.id\" :variant=\"tool === t.id ? 'solid' : 'ghost'\" size=\"md\" :aria-label=\"t.label\" :aria-pressed=\"tool === t.id\" :data-tip=\"t.tip\" @click=\"tool = t.id\">\u003Ccomponent :is=\"t.icon\" :size=\"14\" weight=\"bold\" aria-hidden=\"true\" \u002F>\u003C\u002FBtn>\n            \u003C\u002Fdiv>\n            \u003Cdiv class=\"tools__group\" role=\"group\" aria-label=\"Zoom\">\n              \u003CBtn variant=\"ghost\" size=\"md\" aria-label=\"Zoom in\" data-tip=\"Zoom in · wheel\" @click=\"board?.zoomBy(1.5)\">+\u003C\u002FBtn>\n              \u003CBtn variant=\"ghost\" size=\"md\" aria-label=\"Zoom out\" data-tip=\"Zoom out · wheel\" @click=\"board?.zoomBy(1 \u002F 1.5)\">−\u003C\u002FBtn>\n              \u003CBtn variant=\"ghost\" size=\"md\" :disabled=\"!latest\" :aria-label=\"latest ? `Go to the latest paint: ${latest.emoji} at ${cellLabel(latest.cell)}` : 'No paint yet'\" data-tip=\"Latest paint\" @click=\"toLatest\">\u003CPhCrosshairSimple :size=\"14\" weight=\"bold\" aria-hidden=\"true\" \u002F>\u003C\u002FBtn>\n              \u003CBtn variant=\"ghost\" size=\"md\" aria-label=\"Go to the largest empty area\" data-tip=\"Largest empty area\" @click=\"toSpace\">\u003CPhBoundingBox :size=\"14\" weight=\"bold\" aria-hidden=\"true\" \u002F>\u003C\u002FBtn>\n            \u003C\u002Fdiv>\n            \u003CBtn variant=\"amber\" size=\"md\" class=\"tools__jev\" :disabled=\"jevThinking\" aria-label=\"Jev’s turn\" data-tip=\"Jev’s turn\" @click=\"jevTurn\">\u003CGlyphSpinner v-if=\"jevThinking\" \u002F>\u003CPhSparkle v-else :size=\"14\" weight=\"bold\" aria-hidden=\"true\" \u002F>\u003C\u002FBtn>\n          \u003C\u002FHudTips>\n          \u003Cdiv class=\"stage\">\n            \u003CEmojiCanvas\n              ref=\"board\" v-model:view=\"view\" :cells=\"canvas.cells\" :ghosts=\"canvas.ghosts.value\" :version=\"canvas.version.value\" :arrived=\"canvas.arrived\" :brush=\"brush\" :tool=\"tool\"\n              :ink=\"canvas.unlimited.value ? INK : canvas.ink.value\" :label=\"`The canvas: 1000 by 1000 cells of emoji, centred on ${cellLabel(centre() ?? 0)}`\"\n              @stroke=\"onStroke\" @pick=\"useBrush\" @need=\"need\" @busy=\"onBusy\"\n            \u002F>\n            \u003CWin v-if=\"paletteOpen\" ref=\"pop\" title=\"CW\u002FEMOJI\" as=\"section\" label=\"Choose an emoji\" class=\"palette-pop\" :draggable=\"false\" :minimizable=\"false\" closable @close=\"paletteOpen = false\">\n              \u003CEmojiPalette v-model=\"brush\" :recent=\"recent\" large @picked=\"useBrush($event); paletteOpen = false\" \u002F>\n            \u003C\u002FWin>\n            \u003CWin title=\"CW\u002FMAP\" as=\"section\" label=\"Map\" class=\"map-win\" flush bounds-selector=\".stage\" persist=\"emoji-map\">\n              \u003Ctemplate #meta>{{ Math.floor(view.x) }},{{ Math.floor(view.y) }}\u003C\u002Ftemplate>\n              \u003CEmojiMinimap :blocks=\"canvas.blocks.value\" :view=\"view\" :span=\"span\" @go=\"(x, y, glide) => (glide ? glideTo(x, y) : (view = { ...view, x, y }))\" \u002F>\n            \u003C\u002FWin>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003C!-- MacPaint's pattern bar: the brush, what was used last, ink, and Jev's latest move -->\n        \u003CHudTips class=\"strip\">\n          \u003Cbutton ref=\"brushButton\" type=\"button\" class=\"brush-btn\" :aria-expanded=\"paletteOpen\" aria-haspopup=\"dialog\" :aria-label=\"`Brush: ${brush}. Choose an emoji`\" data-tip=\"Choose an emoji\" @click=\"paletteOpen = !paletteOpen\">{{ brush }}\u003Cspan aria-hidden=\"true\">▾\u003C\u002Fspan>\u003C\u002Fbutton>\n          \u003Cdiv class=\"quick\" role=\"group\" aria-label=\"Recent emoji\">\n            \u003Cbutton v-for=\"e in quick\" :key=\"e\" type=\"button\" class=\"quick__e bare\" :aria-pressed=\"e === brush\" :aria-label=\"`Paint with ${e}`\" @click=\"useBrush(e)\">{{ e }}\u003C\u002Fbutton>\n          \u003C\u002Fdiv>\n          \u003Cbutton type=\"button\" class=\"strip__icon bare\" aria-label=\"Random emoji\" data-tip=\"Random emoji\" @click=\"randomBrush\">\u003CPhShuffle :size=\"14\" weight=\"bold\" aria-hidden=\"true\" \u002F>\u003C\u002Fbutton>\n          \u003Cp class=\"ink mono-label\" :class=\"{ 'is-empty': !canvas.unlimited.value && canvas.ink.value \u003C 1 }\">\n            \u003CGlyphBar :value=\"canvas.unlimited.value ? 1 : canvas.ink.value \u002F INK\" :cells=\"10\" \u002F> {{ inkLabel }}\n          \u003C\u002Fp>\n          \u003Cp class=\"strip__jev mono-label\" aria-live=\"polite\">\n            \u003Cbutton type=\"button\" class=\"strip__join bare\" :aria-pressed=\"joinIn\" data-tip=\"Jev paints after each stroke\" @click=\"joinIn = !joinIn\">{{ joinIn ? '■' : '□' }} Jev joins in\u003C\u002Fbutton>\n            \u003Ctemplate v-if=\"jevLast\">\n              \u003Cbutton v-for=\"c in jevLast.candidates.slice(0, 3)\" :key=\"`${c.cell}-${c.emoji}`\" type=\"button\" class=\"strip__cand bare\" :class=\"{ 'is-picked': c.cell === jevLast.cell && c.emoji === jevLast.emoji }\" :aria-label=\"`${c.emoji} at ${cellLabel(c.cell)}, ${pct(c.p)}`\" @click=\"go(c.cell)\">{{ c.emoji }} {{ pct(c.p) }}\u003C\u002Fbutton>\n            \u003C\u002Ftemplate>\n          \u003C\u002Fp>\n        \u003C\u002FHudTips>\n        \u003Cp v-if=\"canvas.error.value\" class=\"hint is-error mono-label\" role=\"status\">{{ canvas.error.value }}\u003C\u002Fp>\n        \u003Cdiv class=\"hint hint--touch mono-label\">\u003CTuiKeys :keys=\"KEYS_TOUCH\" \u002F>\u003C\u002Fdiv>\n      \u003C\u002FWin>\n    \u003C\u002Farticle>\n\n    \u003Csection class=\"bg-paper pb-section\">\n      \u003Cdiv class=\"wrap about\">\n        \u003CPageHead :back=\"{ to: '\u002Flab', label: 'Lab' }\" :rule=\"false\">\n          \u003Ctemplate #meta>\u003Cspan>Canvas\u003C\u002Fspan>\u003Cspan v-if=\"board?.renderMode\">{{ board.renderMode === 'webgpu' ? 'WebGPU' : 'Canvas 2D' }}\u003C\u002Fspan>\u003C\u002Ftemplate>\n          \u003Cimg src=\"\u002Fimg\u002F1000000-1200.webp\" alt=\"\" width=\"1200\" height=\"642\" loading=\"lazy\" class=\"emoji-logo\">\n          \u003Ch1 class=\"statement-lg\">1 Million \u003CAccentText text=\"Emojis.\" accent=\"Emojis.\" \u002F>\u003C\u002Fh1>\n          \u003Cp class=\"lede\">A thousand by a thousand cells, open to everyone. Pick an emoji and drag to paint; you have sixty cells of ink, and it comes back one a second. Everyone else’s strokes arrive as they land. Jev, TypeSafe AI’s System One model, joins in: after each stroke it reads what is around it by name, weighs emoji that belong with it, and paints one, or finishes a shape it is sure you meant.\u003C\u002Fp>\n          \u003Cdiv class=\"emoji-actions\">\n            \u003CShareBar :url=\"shareUrl\" title=\"1 Million Emojis\" \u002F>\n            \u003CBtn to=\"\u002Flab\u002Femoji\u002Fsource\" variant=\"ghost\" size=\"md\">{ } Source\u003C\u002FBtn>\n          \u003C\u002Fdiv>\n        \u003C\u002FPageHead>\n        \u003CWin v-model:tab=\"activityTab\" title=\"CW\u002FACTIVITY\" as=\"section\" label=\"Activity\" class=\"activity\" :tabs=\"ACTIVITY_TABS\" persist=\"emoji-activity\" :draggable=\"false\" :minimizable=\"false\">\n          \u003Ctemplate #meta>\u003CGlyphSparkline :values=\"canvas.pace.value\" :min=\"0\" :max=\"paceMax\" live :label=\"`Cells painted each half minute, the last ${paceMinutes} minutes`\" \u002F> {{ paceTotal.toLocaleString('en') }} in {{ paceMinutes }} min\u003C\u002Ftemplate>\n          \u003Ctemplate #tab-here>\u003CPresenceReadout :figures=\"presence\" \u002F>\u003C\u002Ftemplate>\n          \u003Ctemplate #tab-latest>\n            \u003CTuiSelect :model-value=\"follow ? 'on' : 'off'\" :options=\"FOLLOW\" label=\"Following new paint\" marks @update:model-value=\"follow = $event === 'on'\" \u002F>\n            \u003Col v-if=\"canvas.activity.value.length\" class=\"feed mono-label\" aria-label=\"Latest paint\">\n              \u003Cli v-for=\"a in canvas.activity.value\" :key=\"`${a.cell}-${a.at}`\">\n                \u003Cbutton type=\"button\" class=\"bare\" :aria-label=\"`${a.emoji} at ${cellLabel(a.cell)}, by ${whoOf(a).name}`\" @click=\"go(a.cell)\">{{ a.emoji }} {{ cellLabel(a.cell) }}\u003C\u002Fbutton>\n                \u003Cspan class=\"feed__who\" aria-hidden=\"true\">\u003Cspan class=\"feed__swatch\" :style=\"{ color: whoOf(a).color }\">{{ a.jev ? '✦' : '■' }}\u003C\u002Fspan> {{ whoOf(a).name }}\u003C\u002Fspan>\n                \u003Cspan v-if=\"mounted\">{{ ago(a.at) }}\u003C\u002Fspan>\n              \u003C\u002Fli>\n            \u003C\u002Fol>\n            \u003Cp v-else class=\"note\">New paint shows here as it lands.\u003C\u002Fp>\n          \u003C\u002Ftemplate>\n        \u003C\u002FWin>\n      \u003C\u002Fdiv>\n    \u003C\u002Fsection>\n    \u003CExperimentPager current=\"\u002Flab\u002Femoji\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>","\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">setup\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">lang\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ts&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"language-javascript\">\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> type { \u003Cspan class=\"hljs-title class_\">ComponentPublicInstance\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;vue&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-title class_\">PhArrowsOutCardinal\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">PhBoundingBox\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">PhCrosshairSimple\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">PhEyedropper\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">PhPencilSimple\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">PhShuffle\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">PhSparkle\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@phosphor-icons\u002Fvue&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-variable constant_\">BLOCK\u003C\u002Fspan>, cellAt, cellLabel, \u003Cspan class=\"hljs-variable constant_\">CELLS\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">INK\u003C\u002Fspan>, isCell, isPaletteEmoji, largestEmpty, \u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan>, xy } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji\u002Fcanvas&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { emojiInfo } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> type { \u003Cspan class=\"hljs-title class_\">JevJoined\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-variable constant_\">PACE_MS\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">PACE_SLOTS\u003C\u002Fspan>, type \u003Cspan class=\"hljs-title class_\">Activity\u003C\u002Fspan>, type \u003Cspan class=\"hljs-title class_\">CanvasStats\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;~\u002Fcomposables\u002FuseEmojiCanvas&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { painterOf } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;#core\u002Fdomain\u002Fpainters&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-title function_\">definePageMeta\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">jevvie\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji&#x27;\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: stats } = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> useFetch&lt;\u003Cspan class=\"hljs-title class_\">CanvasStats\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-string\">&#x27;\u002Fapi\u002Femoji\u002Fstats&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">key\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji-stats&#x27;\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> canvas = \u003Cspan class=\"hljs-title function_\">useEmojiCanvas\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">stats\u003C\u002Fspan>: stats.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> presence = \u003Cspan class=\"hljs-title function_\">usePresence\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;emoji&#x27;\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> board = ref&lt;{ \u003Cspan class=\"hljs-attr\">zoomBy\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">factor: number\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">easeTo\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">to: { x: number; y: number; size: number }, ms?: number\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">ping\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">cell: number\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>; renderMode?: \u003Cspan class=\"hljs-string\">&#x27;webgpu&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;canvas&#x27;\u003C\u002Fspan> }&gt;()\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> route = \u003Cspan class=\"hljs-title function_\">useRoute\u003C\u002Fspan>()\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">opening\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> at = \u003Cspan class=\"hljs-title class_\">String\u003C\u002Fspan>(route.\u003Cspan class=\"hljs-property\">query\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">at\u003C\u002Fspan> ?? \u003Cspan class=\"hljs-string\">&#x27;&#x27;\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">split\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;,&#x27;\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Number\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (at.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan> === \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-title function_\">cellAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(at[\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>]!), \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(at[\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>]!)) !== \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: at[\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>]! + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: at[\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>]! + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cell = \u003Cspan class=\"hljs-title class_\">Number\u003C\u002Fspan>(route.\u003Cspan class=\"hljs-property\">query\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> start = route.\u003Cspan class=\"hljs-property\">query\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan> !== \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-title function_\">isCell\u003C\u002Fspan>(cell) ? cell : stats.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>?.\u003Cspan class=\"hljs-property\">latest\u003C\u002Fspan>?.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> start === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> ? { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> } : { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(start).\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(start).\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> }\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> view = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>({ ...\u003Cspan class=\"hljs-title function_\">opening\u003C\u002Fspan>(), \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">28\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> span = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">w\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">40\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">h\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">25\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-comment\">\u002F\u002F during a fly-to only the last view&#x27;s tiles load, on landing, so they don&#x27;t queue behind the dozens passed over\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> flying = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">held\u003C\u002Fspan>: [number, number, number, number] | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">need\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">x0: number, y0: number, x1: number, y1: number\u003C\u002Fspan>) {\n  span.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = { \u003Cspan class=\"hljs-attr\">w\u003C\u002Fspan>: x1 - x0, \u003Cspan class=\"hljs-attr\">h\u003C\u002Fspan>: y1 - y0 }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (flying) { held = [x0, y0, x1, y1]; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> canvas.\u003Cspan class=\"hljs-title function_\">load\u003C\u002Fspan>(x0, y0, x1, y1)\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">land\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) { flying = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>; \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (held) { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> h = held; held = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>; \u003Cspan class=\"hljs-title function_\">need\u003C\u002Fspan>(...h) } }\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">stopGlide\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; { \u003Cspan class=\"hljs-title function_\">cancelAnimationFrame\u003C\u002Fspan>(glide); \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (flying) \u003Cspan class=\"hljs-title function_\">land\u003C\u002Fspan>() }\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">centre\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; \u003Cspan class=\"hljs-title function_\">cellAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>), \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>))\n\u003Cspan class=\"hljs-comment\">\u002F\u002F a jump to a cell glides there and pings it, like a map search result\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">go\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">cell: number\u003C\u002Fspan>) { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { x, y } = \u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(cell); \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">flyTo\u003C\u002Fspan>(x + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, y + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">24\u003C\u002Fspan>)).\u003Cspan class=\"hljs-title function_\">then\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> board.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>?.\u003Cspan class=\"hljs-title function_\">ping\u003C\u002Fspan>(cell)) }\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> tool = ref&lt;\u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;move&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;pick&#x27;\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> paletteOpen = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-comment\">\u002F\u002F typed plainly: Win&#x27;s full component type is too complex for the outside-click check\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> pop = useTemplateRef&lt;\u003Cspan class=\"hljs-title class_\">ComponentPublicInstance\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-string\">&#x27;pop&#x27;\u003C\u002Fspan>), brushButton = useTemplateRef&lt;\u003Cspan class=\"hljs-title class_\">HTMLButtonElement\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-string\">&#x27;brushButton&#x27;\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-comment\">\u002F\u002F the brush button toggles it itself\u003C\u002Fspan>\n\u003Cspan class=\"hljs-title function_\">onKeyStroke\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;Escape&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { paletteOpen.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-title function_\">onClickOutside\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">unrefElement\u003C\u002Fspan>(pop), \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { paletteOpen.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> }, { \u003Cspan class=\"hljs-attr\">ignore\u003C\u002Fspan>: [brushButton] })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">TOOLS\u003C\u002Fspan> = [{ \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Paint&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">tip\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Paint · drag&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">icon\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">PhPencilSimple\u003C\u002Fspan> }, { \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;move&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Move&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">tip\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Move · space · double-click zooms&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">icon\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">PhArrowsOutCardinal\u003C\u002Fspan> }, { \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;pick&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Pick&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">tip\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Pick up · ⌥ click · double-click zooms&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">icon\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">PhEyedropper\u003C\u002Fspan> }] \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> brush = \u003Cspan class=\"hljs-title function_\">useStored\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;emojiBrush&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;🌊&#x27;\u003C\u002Fspan>, isPaletteEmoji)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> recent = useStored&lt;string[]&gt;(\u003Cspan class=\"hljs-string\">&#x27;emojiRecent&#x27;\u003C\u002Fspan>, [], (v): v is string[] =&gt; \u003Cspan class=\"hljs-title class_\">Array\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">isArray\u003C\u002Fspan>(v) &amp;&amp; v.\u003Cspan class=\"hljs-title function_\">every\u003C\u002Fspan>(isPaletteEmoji))\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">useBrush\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">e: string\u003C\u002Fspan>) { brush.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = e; tool.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan> }\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">randomBrush\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; \u003Cspan class=\"hljs-title function_\">useBrush\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>[\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">random\u003C\u002Fspan>() * \u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>)]!)\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>(brush, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { tool.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan> })\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F Glide to new paint only while nobody is using the canvas, after a delay and at most every few seconds.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> follow = \u003Cspan class=\"hljs-title function_\">useStored\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;emojiFollow&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>, \u003Cspan class=\"hljs-title function_\">oneOf\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">FOLLOW\u003C\u002Fspan> = [{ \u003Cspan class=\"hljs-attr\">value\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;on&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Follow new paint&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">note\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;glide there&#x27;\u003C\u002Fspan> }, { \u003Cspan class=\"hljs-attr\">value\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;off&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Stay put&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">note\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;the view is yours&#x27;\u003C\u002Fspan> }] \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">FOLLOW_DELAY\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1500\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">FOLLOW_COOLDOWN\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">4000\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">GLIDE\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">700\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> busy = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>, freeSince = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, lastGlide = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">pending\u003C\u002Fspan>: number | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>, followTimer = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, glide = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> motion = \u003Cspan class=\"hljs-title function_\">usePreferredReducedMotion\u003C\u002Fspan>()\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">onBusy\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">on: boolean\u003C\u002Fspan>) {\n  busy = on\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (on) { \u003Cspan class=\"hljs-title function_\">stopGlide\u003C\u002Fspan>(); \u003Cspan class=\"hljs-built_in\">clearTimeout\u003C\u002Fspan>(followTimer) } \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> { freeSince = \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>(); \u003Cspan class=\"hljs-title function_\">tryFollow\u003C\u002Fspan>() }\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">tryFollow\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-built_in\">clearTimeout\u003C\u002Fspan>(followTimer)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!follow.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> || pending === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> || busy) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> wait = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(freeSince + \u003Cspan class=\"hljs-variable constant_\">FOLLOW_DELAY\u003C\u002Fspan>, lastGlide + \u003Cspan class=\"hljs-variable constant_\">FOLLOW_COOLDOWN\u003C\u002Fspan>) - \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (wait &gt; \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) { followTimer = \u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-built_in\">setTimeout\u003C\u002Fspan>(tryFollow, wait); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { x, y } = \u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(pending)\n  pending = \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n  lastGlide = \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-title function_\">glideTo\u003C\u002Fspan>(x + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, y + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>)\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> latest = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> canvas.\u003Cspan class=\"hljs-property\">activity\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>[\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>])\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">toLatest\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!latest.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { x, y } = \u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(latest.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>), cell = latest.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">flyTo\u003C\u002Fspan>(x + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, y + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">24\u003C\u002Fspan>)).\u003Cspan class=\"hljs-title function_\">then\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> board.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>?.\u003Cspan class=\"hljs-title function_\">ping\u003C\u002Fspan>(cell))\n}\n\u003Cspan class=\"hljs-comment\">\u002F\u002F the middle of the largest empty rectangle of blocks\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">toSpace\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { bx, by, w, h } = \u003Cspan class=\"hljs-title function_\">largestEmpty\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">b\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> canvas.\u003Cspan class=\"hljs-property\">blocks\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(b))\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> x = (bx + w \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) * \u003Cspan class=\"hljs-variable constant_\">BLOCK\u003C\u002Fspan>, y = (by + h \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) * \u003Cspan class=\"hljs-variable constant_\">BLOCK\u003C\u002Fspan>, cell = \u003Cspan class=\"hljs-title function_\">cellAt\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(x), \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(y))\n  \u003Cspan class=\"hljs-title function_\">track\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;emoji_space&#x27;\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">blocks\u003C\u002Fspan>: w * h })\n  \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">flyTo\u003C\u002Fspan>(x, y, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">24\u003C\u002Fspan>)).\u003Cspan class=\"hljs-title function_\">then\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (cell !== \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>) board.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>?.\u003Cspan class=\"hljs-title function_\">ping\u003C\u002Fspan>(cell) })\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">flyTo\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">x: number, y: number, size: number\u003C\u002Fspan>): \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>&gt; {\n  \u003Cspan class=\"hljs-title function_\">stopGlide\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> = { ...view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (motion.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;reduce&#x27;\u003C\u002Fspan>) { view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = { ...\u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>, x, y, size }; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">resolve\u003C\u002Fspan>() }\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> dist = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">hypot\u003C\u002Fspan>(x - \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, y - \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> span = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(innerWidth, innerHeight))\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F no further out than 6px a cell, since every tile in view on the way is fetched (1px would be hundreds)\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> far = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">6\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, size, span \u002F \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, dist * \u003Cspan class=\"hljs-number\">1.4\u003C\u002Fspan>)))\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> ms = \u003Cspan class=\"hljs-number\">700\u003C\u002Fspan> + \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">900\u003C\u002Fspan>, dist * \u003Cspan class=\"hljs-number\">1.2\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> start = performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n  flying = \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">done\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">step\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">t: number\u003C\u002Fspan>) =&gt; {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> k = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, (t - start) \u002F ms), e = k &lt; \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> ? \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan> * k ** \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> - (-\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> * k + \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) ** \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-comment\">\u002F\u002F on a log scale so the zoom feels even\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> ends = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">exp\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>) + (\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(size) - \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>)) * e)\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> pull = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">sin\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">PI\u003C\u002Fspan> * k)\n      view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = { \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + (x - \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>) * e, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + (y - \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) * e, \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">exp\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(ends) + (\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(far) - \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(ends)) * pull) }\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (k &lt; \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) glide = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(step); \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> { \u003Cspan class=\"hljs-title function_\">land\u003C\u002Fspan>(); \u003Cspan class=\"hljs-title function_\">done\u003C\u002Fspan>() }\n    }\n    glide = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(step)\n  })\n}\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">glideTo\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">x: number, y: number\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-title function_\">stopGlide\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> = { ...view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> }, start = performance.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (motion.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;reduce&#x27;\u003C\u002Fspan>) { view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = { ...\u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>, x, y }; \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">step\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">t: number\u003C\u002Fspan>) =&gt; {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> k = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, (t - start) \u002F \u003Cspan class=\"hljs-variable constant_\">GLIDE\u003C\u002Fspan>), e = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> - (\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> - k) ** \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>\n    view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = { ...view.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + (x - \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>) * e, \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + (y - \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>) * e }\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (k &lt; \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) glide = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(step)\n  }\n  glide = \u003Cspan class=\"hljs-title function_\">requestAnimationFrame\u003C\u002Fspan>(step)\n}\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> canvas.\u003Cspan class=\"hljs-property\">activity\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>[\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>], \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">a\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (a &amp;&amp; !a.\u003Cspan class=\"hljs-property\">mine\u003C\u002Fspan>) { pending = a.\u003Cspan class=\"hljs-property\">cell\u003C\u002Fspan>; \u003Cspan class=\"hljs-title function_\">tryFollow\u003C\u002Fspan>() } })\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>(follow, tryFollow)\n\u003Cspan class=\"hljs-title function_\">onBeforeUnmount\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-title function_\">stopGlide\u003C\u002Fspan>(); \u003Cspan class=\"hljs-built_in\">clearTimeout\u003C\u002Fspan>(followTimer) })\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> joinIn = \u003Cspan class=\"hljs-title function_\">useStored\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;emojiJevJoins&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>, \u003Cspan class=\"hljs-title function_\">oneOf\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> jevThinking = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> jevLast = ref&lt;\u003Cspan class=\"hljs-title class_\">JevJoined\u003C\u002Fspan>&gt;()\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">lastStroke\u003C\u002Fspan>: number[] = []\n\u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">jev\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">stroke: number[]\u003C\u002Fspan>) {\n  jevThinking.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> r = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> canvas.\u003Cspan class=\"hljs-title function_\">jevJoin\u003C\u002Fspan>(stroke)\n  jevThinking.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (r) jevLast.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = r\n}\n\u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">onStroke\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">stroke: number[]\u003C\u002Fspan>) {\n  lastStroke = stroke\n  recent.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = [brush.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>, ...recent.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">filter\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">e\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> e !== brush.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>)].\u003Cspan class=\"hljs-title function_\">slice\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">24\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> canvas.\u003Cspan class=\"hljs-title function_\">paint\u003C\u002Fspan>(stroke, brush.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (joinIn.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> &amp;&amp; !canvas.\u003Cspan class=\"hljs-property\">error\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">jev\u003C\u002Fspan>(stroke)\n}\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">EMOJI\u003C\u002Fspan> = \u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">e\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> ({ \u003Cspan class=\"hljs-attr\">value\u003C\u002Fspan>: e, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${e}\u003C\u002Fspan> \u003Cspan class=\"hljs-subst\">${emojiInfo(e).name}\u003C\u002Fspan>`\u003C\u002Fspan> }))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">jevTurn\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) =&gt; \u003Cspan class=\"hljs-title function_\">jev\u003C\u002Fspan>(lastStroke.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan> ? lastStroke : [\u003Cspan class=\"hljs-title function_\">centre\u003C\u002Fspan>()!])\n\u003Cspan class=\"hljs-title function_\">useSiteTool\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> ({\n  \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji_paint&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">description\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Paint an emoji on the shared canvas, in the middle of the view&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">arg\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">description\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;One emoji from the canvas’s palette&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">values\u003C\u002Fspan>: \u003Cspan class=\"hljs-variable constant_\">EMOJI\u003C\u002Fspan> },\n  \u003Cspan class=\"hljs-attr\">doing\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">label\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">`painting \u003Cspan class=\"hljs-subst\">${label?.split(\u003Cspan class=\"hljs-string\">&#x27; &#x27;\u003C\u002Fspan>)[\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>]}\u003C\u002Fspan>`\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">target\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">querySelector\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;.canvas-win&#x27;\u003C\u002Fspan>),\n  \u003Cspan class=\"hljs-attr\">run\u003C\u002Fspan>: \u003Cspan class=\"hljs-title function_\">async\u003C\u002Fspan> (value) =&gt; {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cell = \u003Cspan class=\"hljs-title function_\">centre\u003C\u002Fspan>()\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!value || cell === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-title function_\">useBrush\u003C\u002Fspan>(value)\n    \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">onStroke\u003C\u002Fspan>([cell])\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">`Painted \u003Cspan class=\"hljs-subst\">${value}\u003C\u002Fspan> at \u003Cspan class=\"hljs-subst\">${cellLabel(cell)}\u003C\u002Fspan>. Ink left: \u003Cspan class=\"hljs-subst\">${canvas.ink.value}\u003C\u002Fspan>.`\u003C\u002Fspan>\n  },\n}))\n\u003Cspan class=\"hljs-title function_\">useSiteTool\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> ({\n  \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji_latest&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">description\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Show the latest emoji painted on the canvas, by anyone&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">doing\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;going to the latest paint&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">target\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">querySelector\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;.canvas-win&#x27;\u003C\u002Fspan>),\n  \u003Cspan class=\"hljs-attr\">run\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-title function_\">toLatest\u003C\u002Fspan>(); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> latest.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${latest.value.emoji}\u003C\u002Fspan> at \u003Cspan class=\"hljs-subst\">${cellLabel(latest.value.cell)}\u003C\u002Fspan>.`\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">&#x27;Nothing painted yet.&#x27;\u003C\u002Fspan> },\n  \u003Cspan class=\"hljs-attr\">readOnly\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>,\n}))\n\u003Cspan class=\"hljs-title function_\">useSiteTool\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> ({\n  \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji_space&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">description\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Go to the largest empty area of the canvas, to paint where nobody has&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">doing\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;looking for room&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">target\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">querySelector\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;.canvas-win&#x27;\u003C\u002Fspan>),\n  \u003Cspan class=\"hljs-attr\">run\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-title function_\">toSpace\u003C\u002Fspan>(); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;Gliding to the largest empty area.&#x27;\u003C\u002Fspan> },\n  \u003Cspan class=\"hljs-attr\">readOnly\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>,\n}))\n\u003Cspan class=\"hljs-title function_\">useSiteTool\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> ({\n  \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji_jev&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">description\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Let Jev paint an emoji next to the last stroke, or in the middle of the view&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">doing\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;asking Jev to paint&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">target\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">querySelector\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;.canvas-win&#x27;\u003C\u002Fspan>),\n  \u003Cspan class=\"hljs-attr\">run\u003C\u002Fspan>: jevTurn,\n}))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">pct\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">p: number\u003C\u002Fspan>) =&gt; \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.round(p * \u003Cspan class=\"hljs-number\">100\u003C\u002Fspan>)}\u003C\u002Fspan>%`\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F The stream reconnects every few minutes: a drop shows only once it has lasted, so that never flashes Offline.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> shownLive = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(canvas.\u003Cspan class=\"hljs-property\">live\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> settle = \u003Cspan class=\"hljs-title function_\">useTimeoutFn\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> { shownLive.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = canvas.\u003Cspan class=\"hljs-property\">live\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> }, \u003Cspan class=\"hljs-number\">3000\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">immediate\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-title function_\">watch\u003C\u002Fspan>(canvas.\u003Cspan class=\"hljs-property\">live\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">state\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (state === \u003Cspan class=\"hljs-string\">&#x27;live&#x27;\u003C\u002Fspan>) { settle.\u003Cspan class=\"hljs-title function_\">stop\u003C\u002Fspan>(); shownLive.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;live&#x27;\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (shownLive.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&#x27;live&#x27;\u003C\u002Fspan>) settle.\u003Cspan class=\"hljs-title function_\">start\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> shownLive.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> = state\n})\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> liveLabel = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> ({ \u003Cspan class=\"hljs-attr\">live\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Live&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">connecting\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Connecting&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">off\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Offline&#x27;\u003C\u002Fspan> })[shownLive.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>])\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> paceTotal = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> canvas.\u003Cspan class=\"hljs-property\">pace\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">reduce\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">a, b\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> a + b, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>))\n\u003Cspan class=\"hljs-comment\">\u002F\u002F a floor of 1, so a quiet stretch sits on the baseline instead of the sparkline&#x27;s middle\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> paceMax = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, ...canvas.\u003Cspan class=\"hljs-property\">pace\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> paceMinutes = \u003Cspan class=\"hljs-variable constant_\">PACE_MS\u003C\u002Fspan> * \u003Cspan class=\"hljs-variable constant_\">PACE_SLOTS\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">60000\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> activityTab = \u003Cspan class=\"hljs-title function_\">ref\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;latest&#x27;\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-comment\">\u002F\u002F the pattern bar: recent emoji first, then a few from the palette so it is never empty\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F (the stored ones only once mounted: the server cannot see them)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> quick = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> [...\u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Set\u003C\u002Fspan>([...(mounted.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> ? recent.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> : []), ...\u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">slice\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">12\u003C\u002Fspan>)])].\u003Cspan class=\"hljs-title function_\">filter\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">e\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> !mounted.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> || e !== brush.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">slice\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">12\u003C\u002Fspan>))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">ACTIVITY_TABS\u003C\u002Fspan> = [{ \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;latest&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Latest&#x27;\u003C\u002Fspan> }, { \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;here&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Here&#x27;\u003C\u002Fspan> }]\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">KEYS_TOUCH\u003C\u002Fspan> = [{ \u003Cspan class=\"hljs-attr\">key\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;drag&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">does\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;paint&#x27;\u003C\u002Fspan> }, { \u003Cspan class=\"hljs-attr\">key\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;two fingers&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">does\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;pan and zoom&#x27;\u003C\u002Fspan> }, { \u003Cspan class=\"hljs-attr\">key\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;hold&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">does\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;pick up&#x27;\u003C\u002Fspan> }]\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> inkLabel = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> (canvas.\u003Cspan class=\"hljs-property\">unlimited\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">&#x27;Unlimited ink&#x27;\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${canvas.ink.value}\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-subst\">${INK}\u003C\u002Fspan> ink`\u003C\u002Fspan>))\n\u003Cspan class=\"hljs-comment\">\u002F\u002F relative times show only in the browser: the server&#x27;s now is not the browser&#x27;s\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> mounted = \u003Cspan class=\"hljs-title function_\">useMounted\u003C\u002Fspan>()\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> now = \u003Cspan class=\"hljs-title function_\">useTimestamp\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">interval\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">5000\u003C\u002Fspan> })\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">whoOf\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">a: Activity\u003C\u002Fspan>) =&gt; {\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (a.\u003Cspan class=\"hljs-property\">jev\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Jev&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">color\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;var(--color-amber)&#x27;\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (a.\u003Cspan class=\"hljs-property\">mine\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;You&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">color\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;var(--color-ink)&#x27;\u003C\u002Fspan> }\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> p = a.\u003Cspan class=\"hljs-property\">who\u003C\u002Fspan> ? \u003Cspan class=\"hljs-title function_\">painterOf\u003C\u002Fspan>(a.\u003Cspan class=\"hljs-property\">who\u003C\u002Fspan>) : \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> p ? { \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: p.\u003Cspan class=\"hljs-property\">name\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">color\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">`var(--color-\u003Cspan class=\"hljs-subst\">${p.color}\u003C\u002Fspan>)`\u003C\u002Fspan> } : { \u003Cspan class=\"hljs-attr\">name\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;Someone&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">color\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;var(--color-ash)&#x27;\u003C\u002Fspan> }\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">ago\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">at: number\u003C\u002Fspan>) =&gt; { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> s = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">round\u003C\u002Fspan>((now.\u003Cspan class=\"hljs-property\">value\u003C\u002Fspan> - at) \u002F \u003Cspan class=\"hljs-number\">1000\u003C\u002Fspan>)); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> s &lt; \u003Cspan class=\"hljs-number\">60\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${s}\u003C\u002Fspan>s`\u003C\u002Fspan> : s &lt; \u003Cspan class=\"hljs-number\">3600\u003C\u002Fspan> ? \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.round(s \u002F \u003Cspan class=\"hljs-number\">60\u003C\u002Fspan>)}\u003C\u002Fspan>m`\u003C\u002Fspan> : \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.round(s \u002F \u003Cspan class=\"hljs-number\">3600\u003C\u002Fspan>)}\u003C\u002Fspan>h`\u003C\u002Fspan> }\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { canonical } = \u003Cspan class=\"hljs-title function_\">usePageSeo\u003C\u002Fspan>({\n  \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;1 Million Emojis&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">description\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;A shared 1,000 × 1,000 emoji canvas where humans paint and Jev paints alongside them.&#x27;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F a 1.91:1 card of the canvas and its logo (scripts\u002Fshots.ts), so link previews don&#x27;t crop it\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">image\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;\u002Flab\u002Femoji-card.png&#x27;\u003C\u002Fspan>,\n})\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> shareUrl = \u003Cspan class=\"hljs-title function_\">computed\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${canonical.value}\u003C\u002Fspan>?at=\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.floor(view.value.x)}\u003C\u002Fspan>,\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.floor(view.value.y)}\u003C\u002Fspan>`\u003C\u002Fspan>)\n\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">article\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;lab-stage bg-paper&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Win\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;1 MILLION EMOJIS&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">as\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;figure&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;The canvas&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas-win&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">flush\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-shot\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:draggable\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;false&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:minimizable\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;false&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> #\u003Cspan class=\"hljs-attr\">meta\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas__live&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`is-${shownLive}`&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">GlyphSpinner\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;shownLive === &#x27;connecting&#x27;&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">set\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;shade&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-else\u003C\u002Fspan>&gt;\u003C\u002Fspan>■\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan> {{ liveLabel }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;presence?.here&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan> · {{ presence.here }} here\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan> · {{ canvas.filled.value.toLocaleString(&#x27;en&#x27;) }} \u002F {{ CELLS.toLocaleString(&#x27;en&#x27;) }}\n        \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;paint&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-comment\">&lt;!-- MacPaint&#x27;s tool column: the tools, the view, and Jev&#x27;s turn --&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">HudTips\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tools&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">side\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;right&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;toolbar&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Tools&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-orientation\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;vertical&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tools__group&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;group&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Tool&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-for\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;t in TOOLS&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;t.id&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:variant\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tool === t.id ? &#x27;solid&#x27; : &#x27;ghost&#x27;&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;md&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;t.label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-pressed\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tool === t.id&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;t.tip&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tool = t.id&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">component\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:is\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;t.icon&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;14&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">weight\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;bold&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tools__group&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;group&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Zoom&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">variant\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ghost&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;md&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Zoom in&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Zoom in · wheel&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;board?.zoomBy(1.5)&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>+\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">variant\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ghost&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;md&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Zoom out&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Zoom out · wheel&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;board?.zoomBy(1 \u002F 1.5)&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>−\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">variant\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ghost&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;md&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:disabled\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;!latest&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;latest ? `Go to the latest paint: ${latest.emoji} at ${cellLabel(latest.cell)}` : &#x27;No paint yet&#x27;&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Latest paint&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;toLatest&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">PhCrosshairSimple\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;14&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">weight\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;bold&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">variant\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ghost&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;md&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Go to the largest empty area&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Largest empty area&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;toSpace&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">PhBoundingBox\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;14&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">weight\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;bold&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">variant\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;amber&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;md&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tools__jev&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:disabled\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;jevThinking&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Jev’s turn&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Jev’s turn&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;jevTurn&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">GlyphSpinner\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;jevThinking&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">PhSparkle\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-else\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;14&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">weight\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;bold&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">HudTips\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;stage&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">EmojiCanvas\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-attr\">ref\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;board&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-model:view\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;view&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:cells\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.cells&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:ghosts\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.ghosts.value&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:version\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.version.value&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:arrived\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.arrived&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:brush\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;brush&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:tool\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;tool&quot;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-attr\">:ink\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.unlimited.value ? INK : canvas.ink.value&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`The canvas: 1000 by 1000 cells of emoji, centred on ${cellLabel(centre() ?? 0)}`&quot;\u003C\u002Fspan>\n              @\u003Cspan class=\"hljs-attr\">stroke\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;onStroke&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">pick\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;useBrush&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">need\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;need&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">busy\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;onBusy&quot;\u003C\u002Fspan>\n            \u002F&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Win\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;paletteOpen&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">ref\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;pop&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;CW\u002FEMOJI&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">as\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;section&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Choose an emoji&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;palette-pop&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:draggable\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;false&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:minimizable\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;false&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">closable\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">close\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;paletteOpen = false&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">EmojiPalette\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-model\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;brush&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:recent\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;recent&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">large\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">picked\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;useBrush($event); paletteOpen = false&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Win\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Win\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;CW\u002FMAP&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">as\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;section&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Map&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;map-win&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">flush\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">bounds-selector\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;.stage&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">persist\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;emoji-map&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> #\u003Cspan class=\"hljs-attr\">meta\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ Math.floor(view.x) }},{{ Math.floor(view.y) }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">EmojiMinimap\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:blocks\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.blocks.value&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:view\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;view&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:span\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;span&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">go\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;(x, y, glide) =&gt; (glide ? glideTo(x, y) : (view = { ...view, x, y }))&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Win\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-comment\">&lt;!-- MacPaint&#x27;s pattern bar: the brush, what was used last, ink, and Jev&#x27;s latest move --&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">HudTips\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;strip&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">ref\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;brushButton&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;brush-btn&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-expanded\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;paletteOpen&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-haspopup\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;dialog&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`Brush: ${brush}. Choose an emoji`&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Choose an emoji&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;paletteOpen = !paletteOpen&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ brush }}\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>▾\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;quick&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;group&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Recent emoji&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-for\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;e in quick&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;e&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;quick__e bare&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-pressed\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;e === brush&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`Paint with ${e}`&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;useBrush(e)&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ e }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;strip__icon bare&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Random emoji&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Random emoji&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;randomBrush&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">PhShuffle\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;14&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">weight\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;bold&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ink mono-label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;{ &#x27;is-empty&#x27;: !canvas.unlimited.value &amp;&amp; canvas.ink.value &lt; 1 }&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">GlyphBar\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:value\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.unlimited.value ? 1 : canvas.ink.value \u002F INK&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:cells\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;10&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan> {{ inkLabel }}\n          \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;strip__jev mono-label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-live\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;polite&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;strip__join bare&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-pressed\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;joinIn&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-tip\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Jev paints after each stroke&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;joinIn = !joinIn&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ joinIn ? &#x27;■&#x27; : &#x27;□&#x27; }} Jev joins in\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;jevLast&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-for\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;c in jevLast.candidates.slice(0, 3)&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`${c.cell}-${c.emoji}`&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;strip__cand bare&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;{ &#x27;is-picked&#x27;: c.cell === jevLast.cell &amp;&amp; c.emoji === jevLast.emoji }&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`${c.emoji} at ${cellLabel(c.cell)}, ${pct(c.p)}`&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;go(c.cell)&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ c.emoji }} {{ pct(c.p) }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">HudTips\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.error.value&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;hint is-error mono-label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">role\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;status&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ canvas.error.value }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;hint hint--touch mono-label&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">TuiKeys\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:keys\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;KEYS_TOUCH&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Win\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">article\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">section\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;bg-paper pb-section&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;wrap about&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">PageHead\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:back\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;{ to: &#x27;\u002Flab&#x27;, label: &#x27;Lab&#x27; }&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:rule\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;false&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> #\u003Cspan class=\"hljs-attr\">meta\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>Canvas\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;board?.renderMode&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ board.renderMode === &#x27;webgpu&#x27; ? &#x27;WebGPU&#x27; : &#x27;Canvas 2D&#x27; }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">img\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">src\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;\u002Fimg\u002F1000000-1200.webp&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">alt\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">width\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;1200&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">height\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;642&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">loading\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;lazy&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;emoji-logo&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">h1\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;statement-lg&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>1 Million \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">AccentText\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">text\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Emojis.&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">accent\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Emojis.&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">h1\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;lede&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>A thousand by a thousand cells, open to everyone. Pick an emoji and drag to paint; you have sixty cells of ink, and it comes back one a second. Everyone else’s strokes arrive as they land. Jev, TypeSafe AI’s System One model, joins in: after each stroke it reads what is around it by name, weighs emoji that belong with it, and paints one, or finishes a shape it is sure you meant.\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;emoji-actions&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">ShareBar\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:url\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;shareUrl&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;1 Million Emojis&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">to\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;\u002Flab\u002Femoji\u002Fsource&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">variant\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ghost&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;md&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{ } Source\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Btn\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">PageHead\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">Win\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-model:tab\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;activityTab&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;CW\u002FACTIVITY&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">as\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;section&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Activity&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;activity&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:tabs\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;ACTIVITY_TABS&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">persist\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;emoji-activity&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:draggable\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;false&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:minimizable\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;false&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> #\u003Cspan class=\"hljs-attr\">meta\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">GlyphSparkline\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:values\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.pace.value&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:min\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;0&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:max\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;paceMax&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">live\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`Cells painted each half minute, the last ${paceMinutes} minutes`&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan> {{ paceTotal.toLocaleString(&#x27;en&#x27;) }} in {{ paceMinutes }} min\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> #\u003Cspan class=\"hljs-attr\">tab-here\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">PresenceReadout\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:figures\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;presence&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan> #\u003Cspan class=\"hljs-attr\">tab-latest\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">TuiSelect\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:model-value\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;follow ? &#x27;on&#x27; : &#x27;off&#x27;&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:options\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;FOLLOW&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Following new paint&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">marks\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">update:model-value\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;follow = $event === &#x27;on&#x27;&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">ol\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;canvas.activity.value.length&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;feed mono-label&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;Latest paint&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">li\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-for\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;a in canvas.activity.value&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:key\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`${a.cell}-${a.at}`&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;button&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;bare&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:aria-label\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;`${a.emoji} at ${cellLabel(a.cell)}, by ${whoOf(a).name}`&quot;\u003C\u002Fspan> @\u003Cspan class=\"hljs-attr\">click\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;go(a.cell)&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ a.emoji }} {{ cellLabel(a.cell) }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">button\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;feed__who&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">aria-hidden\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;true&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;feed__swatch&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">:style\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;{ color: whoOf(a).color }&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ a.jev ? &#x27;✦&#x27; : &#x27;■&#x27; }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan> {{ whoOf(a).name }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-if\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;mounted&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>{{ ago(a.at) }}\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">span\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n              \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">li\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">ol\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n            \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">v-else\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">class\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;note&quot;\u003C\u002Fspan>&gt;\u003C\u002Fspan>New paint shows here as it lands.\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">p\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">Win\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">section\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">ExperimentPager\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">current\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;\u002Flab\u002Femoji&quot;\u003C\u002Fspan> \u002F&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">div\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">template\u003C\u002Fspan>&gt;\u003C\u002Fspan>",{"path":241,"lines":242,"code":243,"html":244},"infrastructure\u002Fgraphics\u002Femoji-render.ts",181,"import { draw, frame, init, sampler, storage, surface, texture } from 'vgpu'\nimport { PALETTE, SIZE, xy } from '@cw\u002Femoji\u002Fcanvas'\nimport shader from '.\u002Femoji-canvas.wgsl?raw'\n\n\u002F\u002F WebGPU: the palette in a mipmapped atlas (so a zoomed-out canvas stays clean), one instanced quad per painted cell.\nexport type EmojiScene = {\n  x: number\n  y: number\n  size: number\n  width: number\n  height: number\n  cells: Map\u003Cnumber, { emoji: string; jev: boolean }>\n  arrived: Map\u003Cnumber, number>\n  pending: ReadonlySet\u003Cnumber>\n  brush: string\n  hover?: number\n  colors: Record\u003C'paper' | 'enamel' | 'stone' | 'amber' | 'amber-light', string>\n  \u002F** For touch screens, where the grid's hairlines are too fine to see. *\u002F\n  checker: boolean\n}\nexport type EmojiRenderer = {\n  readonly mode: 'webgpu' | 'canvas'\n  \u002F** True while cells are still flashing (ask again next frame). *\u002F\n  render(scene: EmojiScene): boolean\n  resize(width: number, height: number): void\n  destroy(): void\n}\n\nconst FLASH = 1200\nconst GLYPH = 64\nconst LEVELS = 5\nconst COLS = Math.ceil(Math.sqrt(PALETTE.length))\nconst ROWS = Math.ceil(PALETTE.length \u002F COLS)\nconst GLYPH_INDEX = new Map(PALETTE.map((e, i) => [e, i]))\nconst EMOJI_FONT = '\"Apple Color Emoji\", \"Segoe UI Emoji\", \"Noto Color Emoji\", sans-serif'\nconst MAX_CELLS = 1 \u003C\u003C 18\n\nfunction span(s: EmojiScene) {\n  const hw = s.width \u002F 2 \u002F s.size, hh = s.height \u002F 2 \u002F s.size\n  return { x0: Math.max(0, Math.floor(s.x - hw)), y0: Math.max(0, Math.floor(s.y - hh)), x1: Math.min(SIZE - 1, Math.ceil(s.x + hw)), y1: Math.min(SIZE - 1, Math.ceil(s.y + hh)) }\n}\nconst flashOf = (s: EmojiScene, cell: number, now: number) => { const at = s.arrived.get(cell); return at && now - at \u003C FLASH ? 1 - (now - at) \u002F FLASH : 0 }\n\n\u002F\u002F any CSS colour, read through a 1 × 1 canvas\nfunction rgba(color: string): [number, number, number, number] {\n  const c = document.createElement('canvas').getContext('2d')!\n  c.fillStyle = color\n  c.fillRect(0, 0, 1, 1)\n  const d = c.getImageData(0, 0, 1, 1).data\n  return [d[0]! \u002F 255, d[1]! \u002F 255, d[2]! \u002F 255, d[3]! \u002F 255]\n}\n\nfunction atlasLevel(glyph: number) {\n  const c = document.createElement('canvas')\n  c.width = COLS * glyph\n  c.height = ROWS * glyph\n  const g = c.getContext('2d')!\n  g.textAlign = 'center'\n  g.textBaseline = 'middle'\n  g.font = `${Math.round(glyph * 0.86)}px ${EMOJI_FONT}`\n  PALETTE.forEach((e, i) => g.fillText(e, (i % COLS) * glyph + glyph \u002F 2, Math.floor(i \u002F COLS) * glyph + glyph \u002F 2 + glyph * 0.04))\n  return c\n}\n\nasync function webgpu(canvas: HTMLCanvasElement): Promise\u003CEmojiRenderer> {\n  const gpu = await init()\n  const view = surface(gpu, canvas, { dpr: [1, 2] })\n  const atlas = texture(gpu, { kind: '2d', size: [COLS * GLYPH, ROWS * GLYPH], format: 'rgba8unorm', mipLevelCount: LEVELS, usage: ['texture_binding', 'copy_dst', 'render_attachment'], label: 'emoji atlas' })\n  for (let level = 0; level \u003C LEVELS; level++) {\n    const source = atlasLevel(GLYPH >> level)\n    gpu.gpu.queue.copyExternalImageToTexture({ source }, { texture: atlas.gpu, mipLevel: level }, { width: source.width, height: source.height })\n  }\n  const glyphs = sampler(gpu, { minFilter: 'linear', magFilter: 'linear', mipmapFilter: 'linear', addressModeU: 'clamp-to-edge', addressModeV: 'clamp-to-edge' })\n  const cells = storage(gpu, MAX_CELLS * 16, 'read')\n  const data = new Float32Array(MAX_CELLS * 4)\n  const ground = draw(gpu, { shader, entry: { vertex: 'ground_vs', fragment: 'ground_fs' }, label: 'emoji ground' })\n  const quads = draw(gpu, { shader, entry: { vertex: 'cell_vs', fragment: 'cell_fs' }, vertices: 6, blend: 'alpha', label: 'emoji cells', set: { cells, atlas, glyph_sampler: glyphs } })\n  let colors: Record\u003Cstring, [number, number, number, number]> | undefined\n\n  return {\n    mode: 'webgpu',\n    render(s) {\n      colors ??= Object.fromEntries(Object.entries(s.colors).map(([k, v]) => [k, rgba(v)]))\n      const now = Date.now(), { x0, y0, x1, y1 } = span(s)\n      let n = 0, flashing = false\n      for (let y = y0; y \u003C= y1 && n \u003C MAX_CELLS; y++) {\n        for (let x = x0; x \u003C= x1 && n \u003C MAX_CELLS; x++) {\n          const cell = y * SIZE + x\n          const pending = s.pending.has(cell), c = s.cells.get(cell)\n          const flash = flashOf(s, cell, now)\n          if (!pending && !c && !flash) continue\n          const glyph = GLYPH_INDEX.get(pending ? s.brush : c?.emoji ?? '')\n          if (flash) flashing = true\n          data.set([x, y, glyph ?? -1, (c?.jev && !pending ? 1 : 0) + (pending ? 2 : 0) + Math.min(0.99, flash)], n * 4)\n          n++\n        }\n      }\n      if (n) cells.write(data.subarray(0, n * 4))\n      const hover = s.hover === undefined ? [0, 0, 0, 0] : [xy(s.hover).x, xy(s.hover).y, 1, 0]\n      const params = {\n        view: [s.x, s.y, s.size, view.dpr], resolution: [s.width, s.height, COLS, ROWS], hover,\n        paper: colors.paper!, enamel: colors.enamel!, stone: colors.stone!, amber: colors.amber!, flash: colors['amber-light']!, checker: [s.checker ? 1 : 0, 0, 0, 0],\n      }\n      ground.set({ params })\n      quads.set({ params })\n      frame(gpu, (f) => f.pass(view, (pass) => { pass.draw(ground); if (n) pass.draw(quads, { instances: n }) }))\n      return flashing\n    },\n    resize() {},\n    destroy() { view.dispose(); gpu.dispose() },\n  }\n}\n\nfunction canvas2d(canvas: HTMLCanvasElement): EmojiRenderer {\n  const ctx = canvas.getContext('2d')!\n  return {\n    mode: 'canvas',\n    resize(width, height) {\n      const dpr = Math.min(window.devicePixelRatio || 1, 2)\n      canvas.width = Math.round(width * dpr)\n      canvas.height = Math.round(height * dpr)\n    },\n    render(s) {\n      const dpr = canvas.width \u002F Math.max(1, s.width), size = s.size, { x0, y0, x1, y1 } = span(s)\n      const ox = s.width \u002F 2 - s.x * size, oy = s.height \u002F 2 - s.y * size\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0)\n      ctx.fillStyle = s.colors.enamel\n      ctx.fillRect(0, 0, s.width, s.height)\n      ctx.fillStyle = s.colors.paper\n      ctx.fillRect(ox, oy, SIZE * size, SIZE * size)\n      if (s.checker) {\n        ctx.globalAlpha = 0.25\n        ctx.fillStyle = s.colors.stone\n        for (let y = Math.max(0, y0); y \u003C= Math.min(SIZE - 1, y1); y++) for (let x = Math.max(0, x0) + ((Math.max(0, x0) + y) % 2 ? 0 : 1); x \u003C= Math.min(SIZE - 1, x1); x += 2) ctx.fillRect(ox + x * size, oy + y * size, size, size)\n        ctx.globalAlpha = 1\n      }\n      if (size >= 12) {\n        ctx.strokeStyle = s.colors.stone\n        ctx.lineWidth = 1\n        ctx.beginPath()\n        for (let x = x0; x \u003C= x1 + 1; x++) { const px = Math.round(ox + x * size) + 0.5; ctx.moveTo(px, Math.max(0, oy)); ctx.lineTo(px, Math.min(s.height, oy + SIZE * size)) }\n        for (let y = y0; y \u003C= y1 + 1; y++) { const py = Math.round(oy + y * size) + 0.5; ctx.moveTo(Math.max(0, ox), py); ctx.lineTo(Math.min(s.width, ox + SIZE * size), py) }\n        ctx.stroke()\n      }\n      ctx.textAlign = 'center'\n      ctx.textBaseline = 'middle'\n      ctx.font = `${Math.max(2, Math.floor(size * 0.8))}px ${EMOJI_FONT}`\n      const now = Date.now()\n      let flashing = false\n      for (let y = y0; y \u003C= y1; y++) {\n        for (let x = x0; x \u003C= x1; x++) {\n          const cell = y * SIZE + x, px = ox + x * size, py = oy + y * size\n          const flash = flashOf(s, cell, now)\n          if (flash) { flashing = true; ctx.globalAlpha = flash; ctx.fillStyle = s.colors['amber-light']; ctx.fillRect(px, py, size, size); ctx.globalAlpha = 1 }\n          if (s.pending.has(cell)) { ctx.globalAlpha = 0.55; ctx.fillText(s.brush, px + size \u002F 2, py + size \u002F 2 + size * 0.04); ctx.globalAlpha = 1; continue }\n          const c = s.cells.get(cell)\n          if (!c) continue\n          ctx.fillText(c.emoji, px + size \u002F 2, py + size \u002F 2 + size * 0.04)\n          if (c.jev && size >= 16) { ctx.fillStyle = s.colors.amber; ctx.fillRect(px + size - 6, py + 2, 4, 4) }\n        }\n      }\n      if (s.hover !== undefined && size >= 6) {\n        const { x, y } = xy(s.hover)\n        ctx.strokeStyle = s.colors.amber\n        ctx.lineWidth = 2\n        ctx.strokeRect(ox + x * size + 1, oy + y * size + 1, size - 2, size - 2)\n      }\n      return flashing\n    },\n    destroy() {},\n  }\n}\n\n\u002F\u002F if WebGPU fails after claiming the canvas, this throws: mount a fresh canvas and ask again with `canvas2dOnly`\nexport async function mountEmojiRenderer(canvas: HTMLCanvasElement, opts: { canvas2dOnly?: boolean } = {}): Promise\u003CEmojiRenderer> {\n  if (!opts.canvas2dOnly && 'gpu' in navigator) {\n    try { return await webgpu(canvas) } catch {}\n  }\n  if (!canvas.getContext('2d')) throw new Error('The canvas is claimed by WebGPU: mount a fresh one')\n  return canvas2d(canvas)\n}","\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { draw, frame, init, sampler, storage, surface, texture } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;vgpu&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan>, xy } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;@cw\u002Femoji\u002Fcanvas&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> shader \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&#x27;.\u002Femoji-canvas.wgsl?raw&#x27;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F WebGPU: the palette in a mipmapped atlas (so a zoomed-out canvas stays clean), one instanced quad per painted cell.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">type\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">EmojiScene\u003C\u002Fspan> = {\n  \u003Cspan class=\"hljs-attr\">x\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">y\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">width\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">height\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">cells\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, { \u003Cspan class=\"hljs-attr\">emoji\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>; \u003Cspan class=\"hljs-attr\">jev\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan> }&gt;\n  \u003Cspan class=\"hljs-attr\">arrived\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>&gt;\n  \u003Cspan class=\"hljs-attr\">pending\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">ReadonlySet\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>&gt;\n  \u003Cspan class=\"hljs-attr\">brush\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">hover\u003C\u002Fspan>?: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">colors\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-string\">&#x27;paper&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;enamel&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;stone&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;amber&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;amber-light&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>&gt;\n  \u003Cspan class=\"hljs-comment\">\u002F** For touch screens, where the grid&#x27;s hairlines are too fine to see. *\u002F\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">checker\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan>\n}\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">type\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">EmojiRenderer\u003C\u002Fspan> = {\n  \u003Cspan class=\"hljs-keyword\">readonly\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">mode\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;webgpu&#x27;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&#x27;canvas&#x27;\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-comment\">\u002F** True while cells are still flashing (ask again next frame). *\u002F\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-title function_\">render\u003C\u002Fspan>(\u003Cspan class=\"hljs-attr\">scene\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">EmojiScene\u003C\u002Fspan>): \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-title function_\">resize\u003C\u002Fspan>(\u003Cspan class=\"hljs-attr\">width\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">height\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>): \u003Cspan class=\"hljs-built_in\">void\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-title function_\">destroy\u003C\u002Fspan>(): \u003Cspan class=\"hljs-built_in\">void\u003C\u002Fspan>\n}\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">FLASH\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1200\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">GLYPH\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">64\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">LEVELS\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">5\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">COLS\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">ceil\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">sqrt\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan>))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">ROWS\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">ceil\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">length\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-variable constant_\">COLS\u003C\u002Fspan>)\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">GLYPH_INDEX\u003C\u002Fspan> = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">e, i\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> [e, i]))\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">EMOJI_FONT\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;&quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Noto Color Emoji&quot;, sans-serif&#x27;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-variable constant_\">MAX_CELLS\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> &lt;&lt; \u003Cspan class=\"hljs-number\">18\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">span\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">s\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">EmojiScene\u003C\u002Fspan>\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> hw = s.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> \u002F s.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, hh = s.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> \u002F s.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">x0\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> - hw)), \u003Cspan class=\"hljs-attr\">y0\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> - hh)), \u003Cspan class=\"hljs-attr\">x1\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> - \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">ceil\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> + hw)), \u003Cspan class=\"hljs-attr\">y1\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> - \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">ceil\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> + hh)) }\n}\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">flashOf\u003C\u002Fspan> = (\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">s\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">EmojiScene\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">cell\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">now\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\u003C\u002Fspan>) =&gt; { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> at = s.\u003Cspan class=\"hljs-property\">arrived\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell); \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> at &amp;&amp; now - at &lt; \u003Cspan class=\"hljs-variable constant_\">FLASH\u003C\u002Fspan> ? \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> - (now - at) \u002F \u003Cspan class=\"hljs-variable constant_\">FLASH\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> }\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F any CSS colour, read through a 1 × 1 canvas\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">rgba\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">color\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>\u003C\u002Fspan>): [\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>] {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> c = \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">createElement\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;canvas&#x27;\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">getContext\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;2d&#x27;\u003C\u002Fspan>)!\n  c.\u003Cspan class=\"hljs-property\">fillStyle\u003C\u002Fspan> = color\n  c.\u003Cspan class=\"hljs-title function_\">fillRect\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> d = c.\u003Cspan class=\"hljs-title function_\">getImageData\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>).\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> [d[\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>]! \u003Cspan class=\"hljs-regexp\">\u002F 255, d[1]! \u002F\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">255\u003C\u002Fspan>, d[\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>]! \u003Cspan class=\"hljs-regexp\">\u002F 255, d[3]! \u002F\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">255\u003C\u002Fspan>]\n}\n\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">atlasLevel\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">glyph\u003C\u002Fspan>: \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> c = \u003Cspan class=\"hljs-variable language_\">document\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">createElement\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;canvas&#x27;\u003C\u002Fspan>)\n  c.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan> = \u003Cspan class=\"hljs-variable constant_\">COLS\u003C\u002Fspan> * glyph\n  c.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan> = \u003Cspan class=\"hljs-variable constant_\">ROWS\u003C\u002Fspan> * glyph\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> g = c.\u003Cspan class=\"hljs-title function_\">getContext\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;2d&#x27;\u003C\u002Fspan>)!\n  g.\u003Cspan class=\"hljs-property\">textAlign\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;center&#x27;\u003C\u002Fspan>\n  g.\u003Cspan class=\"hljs-property\">textBaseline\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;middle&#x27;\u003C\u002Fspan>\n  g.\u003Cspan class=\"hljs-property\">font\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.round(glyph * \u003Cspan class=\"hljs-number\">0.86\u003C\u002Fspan>)}\u003C\u002Fspan>px \u003Cspan class=\"hljs-subst\">${EMOJI_FONT}\u003C\u002Fspan>`\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-variable constant_\">PALETTE\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">forEach\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">e, i\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> g.\u003Cspan class=\"hljs-title function_\">fillText\u003C\u002Fspan>(e, (i % \u003Cspan class=\"hljs-variable constant_\">COLS\u003C\u002Fspan>) * glyph + glyph \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">floor\u003C\u002Fspan>(i \u002F \u003Cspan class=\"hljs-variable constant_\">COLS\u003C\u002Fspan>) * glyph + glyph \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + glyph * \u003Cspan class=\"hljs-number\">0.04\u003C\u002Fspan>))\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> c\n}\n\n\u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">webgpu\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">canvas\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">HTMLCanvasElement\u003C\u002Fspan>\u003C\u002Fspan>): \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-title class_\">EmojiRenderer\u003C\u002Fspan>&gt; {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> gpu = \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">init\u003C\u002Fspan>()\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> view = \u003Cspan class=\"hljs-title function_\">surface\u003C\u002Fspan>(gpu, canvas, { \u003Cspan class=\"hljs-attr\">dpr\u003C\u002Fspan>: [\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>] })\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> atlas = \u003Cspan class=\"hljs-title function_\">texture\u003C\u002Fspan>(gpu, { \u003Cspan class=\"hljs-attr\">kind\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;2d&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">size\u003C\u002Fspan>: [\u003Cspan class=\"hljs-variable constant_\">COLS\u003C\u002Fspan> * \u003Cspan class=\"hljs-variable constant_\">GLYPH\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">ROWS\u003C\u002Fspan> * \u003Cspan class=\"hljs-variable constant_\">GLYPH\u003C\u002Fspan>], \u003Cspan class=\"hljs-attr\">format\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;rgba8unorm&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">mipLevelCount\u003C\u002Fspan>: \u003Cspan class=\"hljs-variable constant_\">LEVELS\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">usage\u003C\u002Fspan>: [\u003Cspan class=\"hljs-string\">&#x27;texture_binding&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;copy_dst&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;render_attachment&#x27;\u003C\u002Fspan>], \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji atlas&#x27;\u003C\u002Fspan> })\n  \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> level = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>; level &lt; \u003Cspan class=\"hljs-variable constant_\">LEVELS\u003C\u002Fspan>; level++) {\n    \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> source = \u003Cspan class=\"hljs-title function_\">atlasLevel\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">GLYPH\u003C\u002Fspan> &gt;&gt; level)\n    gpu.\u003Cspan class=\"hljs-property\">gpu\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">queue\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">copyExternalImageToTexture\u003C\u002Fspan>({ source }, { \u003Cspan class=\"hljs-attr\">texture\u003C\u002Fspan>: atlas.\u003Cspan class=\"hljs-property\">gpu\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">mipLevel\u003C\u002Fspan>: level }, { \u003Cspan class=\"hljs-attr\">width\u003C\u002Fspan>: source.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">height\u003C\u002Fspan>: source.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan> })\n  }\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> glyphs = \u003Cspan class=\"hljs-title function_\">sampler\u003C\u002Fspan>(gpu, { \u003Cspan class=\"hljs-attr\">minFilter\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;linear&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">magFilter\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;linear&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">mipmapFilter\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;linear&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">addressModeU\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;clamp-to-edge&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">addressModeV\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;clamp-to-edge&#x27;\u003C\u002Fspan> })\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cells = \u003Cspan class=\"hljs-title function_\">storage\u003C\u002Fspan>(gpu, \u003Cspan class=\"hljs-variable constant_\">MAX_CELLS\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">16\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&#x27;read&#x27;\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> data = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Float32Array\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">MAX_CELLS\u003C\u002Fspan> * \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> ground = \u003Cspan class=\"hljs-title function_\">draw\u003C\u002Fspan>(gpu, { shader, \u003Cspan class=\"hljs-attr\">entry\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">vertex\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;ground_vs&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">fragment\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;ground_fs&#x27;\u003C\u002Fspan> }, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji ground&#x27;\u003C\u002Fspan> })\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> quads = \u003Cspan class=\"hljs-title function_\">draw\u003C\u002Fspan>(gpu, { shader, \u003Cspan class=\"hljs-attr\">entry\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">vertex\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;cell_vs&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">fragment\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;cell_fs&#x27;\u003C\u002Fspan> }, \u003Cspan class=\"hljs-attr\">vertices\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">6\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">blend\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;alpha&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">label\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;emoji cells&#x27;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">set\u003C\u002Fspan>: { cells, atlas, \u003Cspan class=\"hljs-attr\">glyph_sampler\u003C\u002Fspan>: glyphs } })\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">colors\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-built_in\">string\u003C\u002Fspan>, [\u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">number\u003C\u002Fspan>]&gt; | \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan>\n\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-attr\">mode\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;webgpu&#x27;\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-title function_\">render\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">s\u003C\u002Fspan>) {\n      colors ??= \u003Cspan class=\"hljs-title class_\">Object\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">fromEntries\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Object\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">entries\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">colors\u003C\u002Fspan>).\u003Cspan class=\"hljs-title function_\">map\u003C\u002Fspan>(\u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">[k, v]\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> [k, \u003Cspan class=\"hljs-title function_\">rgba\u003C\u002Fspan>(v)]))\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> now = \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>(), { x0, y0, x1, y1 } = \u003Cspan class=\"hljs-title function_\">span\u003C\u002Fspan>(s)\n      \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> n = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, flashing = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> y = y0; y &lt;= y1 &amp;&amp; n &lt; \u003Cspan class=\"hljs-variable constant_\">MAX_CELLS\u003C\u002Fspan>; y++) {\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> x = x0; x &lt;= x1 &amp;&amp; n &lt; \u003Cspan class=\"hljs-variable constant_\">MAX_CELLS\u003C\u002Fspan>; x++) {\n          \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cell = y * \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> + x\n          \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> pending = s.\u003Cspan class=\"hljs-property\">pending\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(cell), c = s.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell)\n          \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> flash = \u003Cspan class=\"hljs-title function_\">flashOf\u003C\u002Fspan>(s, cell, now)\n          \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!pending &amp;&amp; !c &amp;&amp; !flash) \u003Cspan class=\"hljs-keyword\">continue\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> glyph = \u003Cspan class=\"hljs-variable constant_\">GLYPH_INDEX\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(pending ? s.\u003Cspan class=\"hljs-property\">brush\u003C\u002Fspan> : c?.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan> ?? \u003Cspan class=\"hljs-string\">&#x27;&#x27;\u003C\u002Fspan>)\n          \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (flash) flashing = \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>\n          data.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>([x, y, glyph ?? -\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, (c?.\u003Cspan class=\"hljs-property\">jev\u003C\u002Fspan> &amp;&amp; !pending ? \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) + (pending ? \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) + \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0.99\u003C\u002Fspan>, flash)], n * \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>)\n          n++\n        }\n      }\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (n) cells.\u003Cspan class=\"hljs-title function_\">write\u003C\u002Fspan>(data.\u003Cspan class=\"hljs-title function_\">subarray\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, n * \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>))\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> hover = s.\u003Cspan class=\"hljs-property\">hover\u003C\u002Fspan> === \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> ? [\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>] : [\u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">hover\u003C\u002Fspan>).\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, \u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">hover\u003C\u002Fspan>).\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>]\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> params = {\n        \u003Cspan class=\"hljs-attr\">view\u003C\u002Fspan>: [s.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan>, s.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan>, s.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, view.\u003Cspan class=\"hljs-property\">dpr\u003C\u002Fspan>], \u003Cspan class=\"hljs-attr\">resolution\u003C\u002Fspan>: [s.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan>, s.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">COLS\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable constant_\">ROWS\u003C\u002Fspan>], hover,\n        \u003Cspan class=\"hljs-attr\">paper\u003C\u002Fspan>: colors.\u003Cspan class=\"hljs-property\">paper\u003C\u002Fspan>!, \u003Cspan class=\"hljs-attr\">enamel\u003C\u002Fspan>: colors.\u003Cspan class=\"hljs-property\">enamel\u003C\u002Fspan>!, \u003Cspan class=\"hljs-attr\">stone\u003C\u002Fspan>: colors.\u003Cspan class=\"hljs-property\">stone\u003C\u002Fspan>!, \u003Cspan class=\"hljs-attr\">amber\u003C\u002Fspan>: colors.\u003Cspan class=\"hljs-property\">amber\u003C\u002Fspan>!, \u003Cspan class=\"hljs-attr\">flash\u003C\u002Fspan>: colors[\u003Cspan class=\"hljs-string\">&#x27;amber-light&#x27;\u003C\u002Fspan>]!, \u003Cspan class=\"hljs-attr\">checker\u003C\u002Fspan>: [s.\u003Cspan class=\"hljs-property\">checker\u003C\u002Fspan> ? \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>],\n      }\n      ground.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>({ params })\n      quads.\u003Cspan class=\"hljs-title function_\">set\u003C\u002Fspan>({ params })\n      \u003Cspan class=\"hljs-title function_\">frame\u003C\u002Fspan>(gpu, \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">f\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> f.\u003Cspan class=\"hljs-title function_\">pass\u003C\u002Fspan>(view, \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">pass\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> { pass.\u003Cspan class=\"hljs-title function_\">draw\u003C\u002Fspan>(ground); \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (n) pass.\u003Cspan class=\"hljs-title function_\">draw\u003C\u002Fspan>(quads, { \u003Cspan class=\"hljs-attr\">instances\u003C\u002Fspan>: n }) }))\n      \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> flashing\n    },\n    \u003Cspan class=\"hljs-title function_\">resize\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {},\n    \u003Cspan class=\"hljs-title function_\">destroy\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) { view.\u003Cspan class=\"hljs-title function_\">dispose\u003C\u002Fspan>(); gpu.\u003Cspan class=\"hljs-title function_\">dispose\u003C\u002Fspan>() },\n  }\n}\n\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">canvas2d\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">canvas\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">HTMLCanvasElement\u003C\u002Fspan>\u003C\u002Fspan>): \u003Cspan class=\"hljs-title class_\">EmojiRenderer\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> ctx = canvas.\u003Cspan class=\"hljs-title function_\">getContext\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;2d&#x27;\u003C\u002Fspan>)!\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-attr\">mode\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&#x27;canvas&#x27;\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-title function_\">resize\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">width, height\u003C\u002Fspan>) {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> dpr = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">devicePixelRatio\u003C\u002Fspan> || \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>)\n      canvas.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">round\u003C\u002Fspan>(width * dpr)\n      canvas.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">round\u003C\u002Fspan>(height * dpr)\n    },\n    \u003Cspan class=\"hljs-title function_\">render\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">s\u003C\u002Fspan>) {\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> dpr = canvas.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, s.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan>), size = s.\u003Cspan class=\"hljs-property\">size\u003C\u002Fspan>, { x0, y0, x1, y1 } = \u003Cspan class=\"hljs-title function_\">span\u003C\u002Fspan>(s)\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> ox = s.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> - s.\u003Cspan class=\"hljs-property\">x\u003C\u002Fspan> * size, oy = s.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> - s.\u003Cspan class=\"hljs-property\">y\u003C\u002Fspan> * size\n      ctx.\u003Cspan class=\"hljs-title function_\">setTransform\u003C\u002Fspan>(dpr, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, dpr, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>)\n      ctx.\u003Cspan class=\"hljs-property\">fillStyle\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">colors\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">enamel\u003C\u002Fspan>\n      ctx.\u003Cspan class=\"hljs-title function_\">fillRect\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, s.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan>, s.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan>)\n      ctx.\u003Cspan class=\"hljs-property\">fillStyle\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">colors\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">paper\u003C\u002Fspan>\n      ctx.\u003Cspan class=\"hljs-title function_\">fillRect\u003C\u002Fspan>(ox, oy, \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> * size, \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> * size)\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (s.\u003Cspan class=\"hljs-property\">checker\u003C\u002Fspan>) {\n        ctx.\u003Cspan class=\"hljs-property\">globalAlpha\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">0.25\u003C\u002Fspan>\n        ctx.\u003Cspan class=\"hljs-property\">fillStyle\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">colors\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">stone\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> y = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, y0); y &lt;= \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> - \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, y1); y++) \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> x = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, x0) + ((\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, x0) + y) % \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> ? \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan> : \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>); x &lt;= \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> - \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, x1); x += \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) ctx.\u003Cspan class=\"hljs-title function_\">fillRect\u003C\u002Fspan>(ox + x * size, oy + y * size, size, size)\n        ctx.\u003Cspan class=\"hljs-property\">globalAlpha\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>\n      }\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (size &gt;= \u003Cspan class=\"hljs-number\">12\u003C\u002Fspan>) {\n        ctx.\u003Cspan class=\"hljs-property\">strokeStyle\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">colors\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">stone\u003C\u002Fspan>\n        ctx.\u003Cspan class=\"hljs-property\">lineWidth\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>\n        ctx.\u003Cspan class=\"hljs-title function_\">beginPath\u003C\u002Fspan>()\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> x = x0; x &lt;= x1 + \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>; x++) { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> px = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">round\u003C\u002Fspan>(ox + x * size) + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>; ctx.\u003Cspan class=\"hljs-title function_\">moveTo\u003C\u002Fspan>(px, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, oy)); ctx.\u003Cspan class=\"hljs-title function_\">lineTo\u003C\u002Fspan>(px, \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">height\u003C\u002Fspan>, oy + \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> * size)) }\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> y = y0; y &lt;= y1 + \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>; y++) { \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> py = \u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">round\u003C\u002Fspan>(oy + y * size) + \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>; ctx.\u003Cspan class=\"hljs-title function_\">moveTo\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, ox), py); ctx.\u003Cspan class=\"hljs-title function_\">lineTo\u003C\u002Fspan>(\u003Cspan class=\"hljs-title class_\">Math\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">min\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">width\u003C\u002Fspan>, ox + \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> * size), py) }\n        ctx.\u003Cspan class=\"hljs-title function_\">stroke\u003C\u002Fspan>()\n      }\n      ctx.\u003Cspan class=\"hljs-property\">textAlign\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;center&#x27;\u003C\u002Fspan>\n      ctx.\u003Cspan class=\"hljs-property\">textBaseline\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&#x27;middle&#x27;\u003C\u002Fspan>\n      ctx.\u003Cspan class=\"hljs-property\">font\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">`\u003Cspan class=\"hljs-subst\">${\u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.max(\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-built_in\">Math\u003C\u002Fspan>.floor(size * \u003Cspan class=\"hljs-number\">0.8\u003C\u002Fspan>))}\u003C\u002Fspan>px \u003Cspan class=\"hljs-subst\">${EMOJI_FONT}\u003C\u002Fspan>`\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> now = \u003Cspan class=\"hljs-title class_\">Date\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">now\u003C\u002Fspan>()\n      \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> flashing = \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> y = y0; y &lt;= y1; y++) {\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> x = x0; x &lt;= x1; x++) {\n          \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> cell = y * \u003Cspan class=\"hljs-variable constant_\">SIZE\u003C\u002Fspan> + x, px = ox + x * size, py = oy + y * size\n          \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> flash = \u003Cspan class=\"hljs-title function_\">flashOf\u003C\u002Fspan>(s, cell, now)\n          \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (flash) { flashing = \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>; ctx.\u003Cspan class=\"hljs-property\">globalAlpha\u003C\u002Fspan> = flash; ctx.\u003Cspan class=\"hljs-property\">fillStyle\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">colors\u003C\u002Fspan>[\u003Cspan class=\"hljs-string\">&#x27;amber-light&#x27;\u003C\u002Fspan>]; ctx.\u003Cspan class=\"hljs-title function_\">fillRect\u003C\u002Fspan>(px, py, size, size); ctx.\u003Cspan class=\"hljs-property\">globalAlpha\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan> }\n          \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (s.\u003Cspan class=\"hljs-property\">pending\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">has\u003C\u002Fspan>(cell)) { ctx.\u003Cspan class=\"hljs-property\">globalAlpha\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">0.55\u003C\u002Fspan>; ctx.\u003Cspan class=\"hljs-title function_\">fillText\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">brush\u003C\u002Fspan>, px + size \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, py + size \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + size * \u003Cspan class=\"hljs-number\">0.04\u003C\u002Fspan>); ctx.\u003Cspan class=\"hljs-property\">globalAlpha\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>; \u003Cspan class=\"hljs-keyword\">continue\u003C\u002Fspan> }\n          \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> c = s.\u003Cspan class=\"hljs-property\">cells\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(cell)\n          \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!c) \u003Cspan class=\"hljs-keyword\">continue\u003C\u002Fspan>\n          ctx.\u003Cspan class=\"hljs-title function_\">fillText\u003C\u002Fspan>(c.\u003Cspan class=\"hljs-property\">emoji\u003C\u002Fspan>, px + size \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, py + size \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> + size * \u003Cspan class=\"hljs-number\">0.04\u003C\u002Fspan>)\n          \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (c.\u003Cspan class=\"hljs-property\">jev\u003C\u002Fspan> &amp;&amp; size &gt;= \u003Cspan class=\"hljs-number\">16\u003C\u002Fspan>) { ctx.\u003Cspan class=\"hljs-property\">fillStyle\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">colors\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">amber\u003C\u002Fspan>; ctx.\u003Cspan class=\"hljs-title function_\">fillRect\u003C\u002Fspan>(px + size - \u003Cspan class=\"hljs-number\">6\u003C\u002Fspan>, py + \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>) }\n        }\n      }\n      \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (s.\u003Cspan class=\"hljs-property\">hover\u003C\u002Fspan> !== \u003Cspan class=\"hljs-literal\">undefined\u003C\u002Fspan> &amp;&amp; size &gt;= \u003Cspan class=\"hljs-number\">6\u003C\u002Fspan>) {\n        \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { x, y } = \u003Cspan class=\"hljs-title function_\">xy\u003C\u002Fspan>(s.\u003Cspan class=\"hljs-property\">hover\u003C\u002Fspan>)\n        ctx.\u003Cspan class=\"hljs-property\">strokeStyle\u003C\u002Fspan> = s.\u003Cspan class=\"hljs-property\">colors\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">amber\u003C\u002Fspan>\n        ctx.\u003Cspan class=\"hljs-property\">lineWidth\u003C\u002Fspan> = \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>\n        ctx.\u003Cspan class=\"hljs-title function_\">strokeRect\u003C\u002Fspan>(ox + x * size + \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, oy + y * size + \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, size - \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, size - \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>)\n      }\n      \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> flashing\n    },\n    \u003Cspan class=\"hljs-title function_\">destroy\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003C\u002Fspan>) {},\n  }\n}\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F if WebGPU fails after claiming the canvas, this throws: mount a fresh canvas and ask again with `canvas2dOnly`\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">export\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">async\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">mountEmojiRenderer\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">\u003Cspan class=\"hljs-attr\">canvas\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">HTMLCanvasElement\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">opts\u003C\u002Fspan>: { canvas2dOnly?: \u003Cspan class=\"hljs-built_in\">boolean\u003C\u002Fspan> } = {}\u003C\u002Fspan>): \u003Cspan class=\"hljs-title class_\">Promise\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-title class_\">EmojiRenderer\u003C\u002Fspan>&gt; {\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!opts.\u003Cspan class=\"hljs-property\">canvas2dOnly\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-string\">&#x27;gpu&#x27;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan> navigator) {\n    \u003Cspan class=\"hljs-keyword\">try\u003C\u002Fspan> { \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">await\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">webgpu\u003C\u002Fspan>(canvas) } \u003Cspan class=\"hljs-keyword\">catch\u003C\u002Fspan> {}\n  }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!canvas.\u003Cspan class=\"hljs-title function_\">getContext\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;2d&#x27;\u003C\u002Fspan>)) \u003Cspan class=\"hljs-keyword\">throw\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Error\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&#x27;The canvas is claimed by WebGPU: mount a fresh one&#x27;\u003C\u002Fspan>)\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">canvas2d\u003C\u002Fspan>(canvas)\n}",{"path":246,"lines":247,"code":248,"html":249},"infrastructure\u002Fgraphics\u002Femoji-canvas.wgsl",87,"\u002F\u002F The emoji canvas (infrastructure\u002Fgraphics\u002Femoji-render.ts). Two draws over one set of params:\n\u002F\u002F   ground: a full-screen triangle; enamel off the canvas, paper on it (alternate cells shaded on touch screens), grid\n\u002F\u002F           lines when cells are large, the hover frame\n\u002F\u002F   cells:  one quad per painted cell in view, sampling the emoji atlas (mipmapped, level chosen from the cell's size),\n\u002F\u002F           over an amber-light flash when it just arrived; Jev's cells carry a small amber corner\n\nstruct Params {\n  view: vec4f,        \u002F\u002F centre x, centre y (cells), cell size (CSS px), device pixel ratio\n  resolution: vec4f,  \u002F\u002F width, height (CSS px), atlas columns, atlas rows\n  hover: vec4f,       \u002F\u002F x, y, 1 when shown\n  paper: vec4f,\n  enamel: vec4f,\n  stone: vec4f,\n  amber: vec4f,\n  flash: vec4f,\n  checker: vec4f,     \u002F\u002F x: 1 when alternate cells are shaded\n}\n@group(0) @binding(0) var\u003Cuniform> params: Params;\n\nfn world(css: vec2f) -> vec2f {\n  return params.view.xy + (css - params.resolution.xy * 0.5) \u002F params.view.z;\n}\n\n@vertex fn ground_vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n  var p = array\u003Cvec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));\n  return vec4f(p[vi], 0, 1);\n}\n\n@fragment fn ground_fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {\n  let w = world(frag.xy \u002F params.view.w);\n  if (w.x \u003C 0.0 || w.y \u003C 0.0 || w.x >= 1000.0 || w.y >= 1000.0) { return params.enamel; }\n  let s = params.view.z;\n  let px = fract(w) * s;\n  if (params.hover.z > 0.5 && all(floor(w) == params.hover.xy) && s >= 6.0 && (px.x \u003C 2.0 || px.y \u003C 2.0 || px.x > s - 2.0 || px.y > s - 2.0)) { return params.amber; }\n  if (s >= 12.0 && (px.x \u003C 1.0 || px.y \u003C 1.0)) { return params.stone; }\n  let odd = (u32(floor(w.x)) + u32(floor(w.y))) % 2u == 1u;\n  if (params.checker.x > 0.5 && odd) { return mix(params.paper, params.stone, 0.25); }\n  return params.paper;\n}\n\n\u002F\u002F a painted cell: column, row, glyph index in the atlas, flags (1 Jev, 2 pending) plus the flash (0–1) as the fraction\n@group(0) @binding(1) var\u003Cstorage, read> cells: array\u003Cvec4f>;\n@group(0) @binding(2) var atlas: texture_2d\u003Cf32>;\n@group(0) @binding(3) var glyph_sampler: sampler;\n\nstruct Out {\n  @builtin(position) pos: vec4f,\n  @location(0) local: vec2f,\n  @location(1) @interpolate(flat) glyph: f32,\n  @location(2) @interpolate(flat) flags: f32,\n}\n\n@vertex fn cell_vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> Out {\n  var corner = array\u003Cvec2f, 6>(vec2f(0, 0), vec2f(1, 0), vec2f(0, 1), vec2f(0, 1), vec2f(1, 0), vec2f(1, 1));\n  let c = cells[ii];\n  let k = corner[vi];\n  let css = (c.xy + k - params.view.xy) * params.view.z + params.resolution.xy * 0.5;\n  var out: Out;\n  out.pos = vec4f(css.x \u002F params.resolution.x * 2.0 - 1.0, 1.0 - css.y \u002F params.resolution.y * 2.0, 0, 1);\n  out.local = k;\n  out.glyph = c.z;\n  out.flags = c.w;\n  return out;\n}\n\n@fragment fn cell_fs(in: Out) -> @location(0) vec4f {\n  let bits = u32(floor(in.flags));\n  let flash = fract(in.flags);\n  let s = params.view.z;\n  var col = vec4f(params.flash.rgb, flash);\n  \u002F\u002F the emoji fills the middle 80% of the cell\n  let g = (in.local - 0.1) \u002F 0.8;\n  if (all(g >= vec2f(0.0)) && all(g \u003C= vec2f(1.0))) {\n    let cols = params.resolution.z;\n    let cell = vec2f(in.glyph % cols, floor(in.glyph \u002F cols));\n    let uv = (cell + clamp(g, vec2f(0.01), vec2f(0.99))) \u002F params.resolution.zw;\n    let level = clamp(log2(64.0 \u002F max(1.0, s * 0.8 * params.view.w)), 0.0, 4.0);\n    var t = textureSampleLevel(atlas, glyph_sampler, uv, level);\n    if ((bits & 2u) != 0u) { t.a *= 0.55; }\n    let a = t.a + col.a * (1.0 - t.a);\n    col = vec4f((t.rgb * t.a + col.rgb * col.a * (1.0 - t.a)) \u002F max(a, 0.0001), a);\n  }\n  if ((bits & 1u) != 0u && s >= 16.0 && in.local.x * s > s - 6.0 && in.local.x * s \u003C s - 2.0 && in.local.y * s > 2.0 && in.local.y * s \u003C 6.0) {\n    col = params.amber;\n  }\n  return col;\n}","\u003Cspan class=\"hljs-comment\">\u002F\u002F The emoji canvas (infrastructure\u002Fgraphics\u002Femoji-render.ts). Two draws over one set of params:\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F   ground: a full-screen triangle; enamel off the canvas, paper on it (alternate cells shaded on touch screens), grid\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F           lines when cells are large, the hover frame\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F   cells:  one quad per painted cell in view, sampling the emoji atlas (mipmapped, level chosen from the cell&#x27;s size),\u003C\u002Fspan>\n\u003Cspan class=\"hljs-comment\">\u002F\u002F           over an amber-light flash when it just arrived; Jev&#x27;s cells carry a small amber corner\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-keyword\">struct\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Params\u003C\u002Fspan> {\n  view: vec4f,        \u003Cspan class=\"hljs-comment\">\u002F\u002F centre x, centre y (cells), cell size (CSS px), device pixel ratio\u003C\u002Fspan>\n  resolution: vec4f,  \u003Cspan class=\"hljs-comment\">\u002F\u002F width, height (CSS px), atlas columns, atlas rows\u003C\u002Fspan>\n  hover: vec4f,       \u003Cspan class=\"hljs-comment\">\u002F\u002F x, y, 1 when shown\u003C\u002Fspan>\n  paper: vec4f,\n  enamel: vec4f,\n  stone: vec4f,\n  amber: vec4f,\n  flash: vec4f,\n  checker: vec4f,     \u003Cspan class=\"hljs-comment\">\u002F\u002F x: 1 when alternate cells are shaded\u003C\u002Fspan>\n}\n@\u003Cspan class=\"hljs-title function_ invoke__\">group\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) @\u003Cspan class=\"hljs-title function_ invoke__\">binding\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) var&lt;uniform&gt; params: Params;\n\n\u003Cspan class=\"hljs-keyword\">fn\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">world\u003C\u002Fspan>(css: vec2f) \u003Cspan class=\"hljs-punctuation\">-&gt;\u003C\u002Fspan> vec2f {\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> params.view.xy + (css - params.resolution.xy * \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>) \u002F params.view.z;\n}\n\n@vertex \u003Cspan class=\"hljs-keyword\">fn\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">ground_vs\u003C\u002Fspan>(@\u003Cspan class=\"hljs-title function_ invoke__\">builtin\u003C\u002Fspan>(vertex_index) vi: \u003Cspan class=\"hljs-type\">u32\u003C\u002Fspan>) \u003Cspan class=\"hljs-punctuation\">-&gt;\u003C\u002Fspan> @\u003Cspan class=\"hljs-title function_ invoke__\">builtin\u003C\u002Fspan>(position) vec4f {\n  var p = array&lt;vec2f, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(-\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, -\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>), \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, -\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>), \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(-\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>));\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_ invoke__\">vec4f\u003C\u002Fspan>(p[vi], \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>);\n}\n\n@fragment \u003Cspan class=\"hljs-keyword\">fn\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">ground_fs\u003C\u002Fspan>(@\u003Cspan class=\"hljs-title function_ invoke__\">builtin\u003C\u002Fspan>(position) frag: vec4f) \u003Cspan class=\"hljs-punctuation\">-&gt;\u003C\u002Fspan> @\u003Cspan class=\"hljs-title function_ invoke__\">location\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) vec4f {\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">w\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_ invoke__\">world\u003C\u002Fspan>(frag.xy \u002F params.view.w);\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (w.x &lt; \u003Cspan class=\"hljs-number\">0.0\u003C\u002Fspan> || w.y &lt; \u003Cspan class=\"hljs-number\">0.0\u003C\u002Fspan> || w.x &gt;= \u003Cspan class=\"hljs-number\">1000.0\u003C\u002Fspan> || w.y &gt;= \u003Cspan class=\"hljs-number\">1000.0\u003C\u002Fspan>) { \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> params.enamel; }\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">s\u003C\u002Fspan> = params.view.z;\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">px\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_ invoke__\">fract\u003C\u002Fspan>(w) * s;\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (params.hover.z &gt; \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-title function_ invoke__\">all\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_ invoke__\">floor\u003C\u002Fspan>(w) == params.hover.xy) &amp;&amp; s &gt;= \u003Cspan class=\"hljs-number\">6.0\u003C\u002Fspan> &amp;&amp; (px.x &lt; \u003Cspan class=\"hljs-number\">2.0\u003C\u002Fspan> || px.y &lt; \u003Cspan class=\"hljs-number\">2.0\u003C\u002Fspan> || px.x &gt; s - \u003Cspan class=\"hljs-number\">2.0\u003C\u002Fspan> || px.y &gt; s - \u003Cspan class=\"hljs-number\">2.0\u003C\u002Fspan>)) { \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> params.amber; }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (s &gt;= \u003Cspan class=\"hljs-number\">12.0\u003C\u002Fspan> &amp;&amp; (px.x &lt; \u003Cspan class=\"hljs-number\">1.0\u003C\u002Fspan> || px.y &lt; \u003Cspan class=\"hljs-number\">1.0\u003C\u002Fspan>)) { \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> params.stone; }\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">odd\u003C\u002Fspan> = (\u003Cspan class=\"hljs-title function_ invoke__\">u32\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_ invoke__\">floor\u003C\u002Fspan>(w.x)) + \u003Cspan class=\"hljs-title function_ invoke__\">u32\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_ invoke__\">floor\u003C\u002Fspan>(w.y))) % \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>u == \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>u;\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (params.checker.x &gt; \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> &amp;&amp; odd) { \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_ invoke__\">mix\u003C\u002Fspan>(params.paper, params.stone, \u003Cspan class=\"hljs-number\">0.25\u003C\u002Fspan>); }\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> params.paper;\n}\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F a painted cell: column, row, glyph index in the atlas, flags (1 Jev, 2 pending) plus the flash (0–1) as the fraction\u003C\u002Fspan>\n@\u003Cspan class=\"hljs-title function_ invoke__\">group\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) @\u003Cspan class=\"hljs-title function_ invoke__\">binding\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) var&lt;storage, read&gt; cells: array&lt;vec4f&gt;;\n@\u003Cspan class=\"hljs-title function_ invoke__\">group\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) @\u003Cspan class=\"hljs-title function_ invoke__\">binding\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) var atlas: texture_2d&lt;\u003Cspan class=\"hljs-type\">f32\u003C\u002Fspan>&gt;;\n@\u003Cspan class=\"hljs-title function_ invoke__\">group\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) @\u003Cspan class=\"hljs-title function_ invoke__\">binding\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>) var glyph_sampler: sampler;\n\n\u003Cspan class=\"hljs-keyword\">struct\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Out\u003C\u002Fspan> {\n  @\u003Cspan class=\"hljs-title function_ invoke__\">builtin\u003C\u002Fspan>(position) pos: vec4f,\n  @\u003Cspan class=\"hljs-title function_ invoke__\">location\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) local: vec2f,\n  @\u003Cspan class=\"hljs-title function_ invoke__\">location\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>) @\u003Cspan class=\"hljs-title function_ invoke__\">interpolate\u003C\u002Fspan>(flat) glyph: \u003Cspan class=\"hljs-type\">f32\u003C\u002Fspan>,\n  @\u003Cspan class=\"hljs-title function_ invoke__\">location\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) @\u003Cspan class=\"hljs-title function_ invoke__\">interpolate\u003C\u002Fspan>(flat) flags: \u003Cspan class=\"hljs-type\">f32\u003C\u002Fspan>,\n}\n\n@vertex \u003Cspan class=\"hljs-keyword\">fn\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">cell_vs\u003C\u002Fspan>(@\u003Cspan class=\"hljs-title function_ invoke__\">builtin\u003C\u002Fspan>(vertex_index) vi: \u003Cspan class=\"hljs-type\">u32\u003C\u002Fspan>, @\u003Cspan class=\"hljs-title function_ invoke__\">builtin\u003C\u002Fspan>(instance_index) ii: \u003Cspan class=\"hljs-type\">u32\u003C\u002Fspan>) \u003Cspan class=\"hljs-punctuation\">-&gt;\u003C\u002Fspan> Out {\n  var corner = array&lt;vec2f, \u003Cspan class=\"hljs-number\">6\u003C\u002Fspan>&gt;(\u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>), \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>), \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>), \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>), \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>), \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>));\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">c\u003C\u002Fspan> = cells[ii];\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">k\u003C\u002Fspan> = corner[vi];\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">css\u003C\u002Fspan> = (c.xy + k - params.view.xy) * params.view.z + params.resolution.xy * \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan>;\n  var out: Out;\n  out.pos = \u003Cspan class=\"hljs-title function_ invoke__\">vec4f\u003C\u002Fspan>(css.x \u002F params.resolution.x * \u003Cspan class=\"hljs-number\">2.0\u003C\u002Fspan> - \u003Cspan class=\"hljs-number\">1.0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1.0\u003C\u002Fspan> - css.y \u002F params.resolution.y * \u003Cspan class=\"hljs-number\">2.0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>);\n  out.local = k;\n  out.glyph = c.z;\n  out.flags = c.w;\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> out;\n}\n\n@fragment \u003Cspan class=\"hljs-keyword\">fn\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">cell_fs\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>: Out) \u003Cspan class=\"hljs-punctuation\">-&gt;\u003C\u002Fspan> @\u003Cspan class=\"hljs-title function_ invoke__\">location\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) vec4f {\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">bits\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_ invoke__\">u32\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_ invoke__\">floor\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.flags));\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">flash\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_ invoke__\">fract\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.flags);\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">s\u003C\u002Fspan> = params.view.z;\n  var col = \u003Cspan class=\"hljs-title function_ invoke__\">vec4f\u003C\u002Fspan>(params.flash.rgb, flash);\n  \u003Cspan class=\"hljs-comment\">\u002F\u002F the emoji fills the middle 80% of the cell\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">g\u003C\u002Fspan> = (\u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.local - \u003Cspan class=\"hljs-number\">0.1\u003C\u002Fspan>) \u002F \u003Cspan class=\"hljs-number\">0.8\u003C\u002Fspan>;\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (\u003Cspan class=\"hljs-title function_ invoke__\">all\u003C\u002Fspan>(g &gt;= \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0.0\u003C\u002Fspan>)) &amp;&amp; \u003Cspan class=\"hljs-title function_ invoke__\">all\u003C\u002Fspan>(g &lt;= \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1.0\u003C\u002Fspan>))) {\n    \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">cols\u003C\u002Fspan> = params.resolution.z;\n    \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">cell\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.glyph % cols, \u003Cspan class=\"hljs-title function_ invoke__\">floor\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.glyph \u002F cols));\n    \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">uv\u003C\u002Fspan> = (cell + \u003Cspan class=\"hljs-title function_ invoke__\">clamp\u003C\u002Fspan>(g, \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0.01\u003C\u002Fspan>), \u003Cspan class=\"hljs-title function_ invoke__\">vec2f\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">0.99\u003C\u002Fspan>))) \u002F params.resolution.zw;\n    \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">level\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_ invoke__\">clamp\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_ invoke__\">log2\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">64.0\u003C\u002Fspan> \u002F \u003Cspan class=\"hljs-title function_ invoke__\">max\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1.0\u003C\u002Fspan>, s * \u003Cspan class=\"hljs-number\">0.8\u003C\u002Fspan> * params.view.w)), \u003Cspan class=\"hljs-number\">0.0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4.0\u003C\u002Fspan>);\n    var t = \u003Cspan class=\"hljs-title function_ invoke__\">textureSampleLevel\u003C\u002Fspan>(atlas, glyph_sampler, uv, level);\n    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> ((bits &amp; \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>u) != \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>u) { t.a *= \u003Cspan class=\"hljs-number\">0.55\u003C\u002Fspan>; }\n    \u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">a\u003C\u002Fspan> = t.a + col.a * (\u003Cspan class=\"hljs-number\">1.0\u003C\u002Fspan> - t.a);\n    col = \u003Cspan class=\"hljs-title function_ invoke__\">vec4f\u003C\u002Fspan>((t.rgb * t.a + col.rgb * col.a * (\u003Cspan class=\"hljs-number\">1.0\u003C\u002Fspan> - t.a)) \u002F \u003Cspan class=\"hljs-title function_ invoke__\">max\u003C\u002Fspan>(a, \u003Cspan class=\"hljs-number\">0.0001\u003C\u002Fspan>), a);\n  }\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> ((bits &amp; \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>u) != \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>u &amp;&amp; s &gt;= \u003Cspan class=\"hljs-number\">16.0\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.local.x * s &gt; s - \u003Cspan class=\"hljs-number\">6.0\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.local.x * s &lt; s - \u003Cspan class=\"hljs-number\">2.0\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.local.y * s &gt; \u003Cspan class=\"hljs-number\">2.0\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-keyword\">in\u003C\u002Fspan>.local.y * s &lt; \u003Cspan class=\"hljs-number\">6.0\u003C\u002Fspan>) {\n    col = params.amber;\n  }\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> col;\n}",1790667166701]