{"version":3,"sources":["tooltip.mjs"],"sourcesContent":["// ONE TOOLTIP FOR THE APP (the owner, Oct 2: \"rather than browser tooltips, implement a faster custom coded one that\n// matches the aesthetics already\"). The browser's own waits most of a second, then draws in the system's font and\n// colours, over a game drawn in Space Mono on dark panels.\n//\n// `title` stays the source of truth. Every element keeps writing its title as it always has (a slot names its item,\n// the energy gauge rewrites its reading every second), and nothing that reads one changes. Only while the pointer is\n// on an element is its title lifted off (so the browser has nothing to show) and drawn here instead; it goes back the\n// moment the pointer leaves. A title rewritten while it is shown updates the tip in place.\n//\n//   - Fast: a first tip after SHOW_MS; moving on to the next titled thing within WARM_MS shows its tip at once.\n//   - Above the element, centred, or below it when there is no room; kept on the screen.\n//   - Not on touch, which never had title tips; gone on a press, a key, a scroll or pointer lock (walking).\n//   - Keyboard focus shows it too, as the browser's did not.\n\nconst SHOW_MS = 400, WARM_MS = 400, GAP = 8, EDGE = 6;   // 400 ms before the first tip (the owner's play review, Oct 23, l4)\nlet tip = null, cur = null, timer = 0, lastHide = -1e9, watch = null, aria = false, hideCheck = 0;\n\nconst CSS = `\n  .ktip { position: fixed; left: 0; top: 0; z-index: 2147483000; pointer-events: none; max-width: min(280px, calc(100vw - 12px));\n    box-sizing: border-box; padding: 5px 9px; background: rgba(0, 0, 0, 0.92); color: #fff; border: 1px solid #444; border-radius: 4px;\n    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.55); font-family: 'Space Mono', monospace; font-size: 13px; line-height: 1.35;\n    letter-spacing: 0.2px; white-space: pre-line; overflow-wrap: anywhere; opacity: 0; transition: opacity 90ms ease; }\n  .ktip.on { opacity: 1; }\n  @media (prefers-reduced-motion: reduce) { .ktip { transition: none; } }`;\n\nfunction ensure() {\n  if (tip) return tip;\n  const st = document.createElement('style'); st.id = 'ktip-styles'; st.textContent = CSS; document.head.appendChild(st);\n  tip = document.createElement('div'); tip.className = 'ktip'; tip.setAttribute('role', 'tooltip'); tip.id = 'ktip';\n  document.body.appendChild(tip);\n  return tip;\n}\n\n// Lift an element's title onto it, so the browser shows nothing; give an unlabelled icon its name meanwhile.\nfunction lift(el) {\n  const t = el.getAttribute('title');\n  if (t != null) { el.dataset.ktip = t; el.removeAttribute('title'); }\n  aria = !el.hasAttribute('aria-label') && !el.textContent.trim();\n  if (aria) el.setAttribute('aria-label', el.dataset.ktip || '');\n  el.setAttribute('aria-describedby', 'ktip');\n}\nfunction restore(el) {\n  if (!el) return;\n  if (watch) { watch.disconnect(); watch = null; }\n  if (el.dataset.ktip != null) { if (!el.hasAttribute('title')) el.setAttribute('title', el.dataset.ktip); delete el.dataset.ktip; }\n  if (aria) el.removeAttribute('aria-label');\n  if (el.getAttribute('aria-describedby') === 'ktip') el.removeAttribute('aria-describedby');\n  aria = false;\n}\n\nfunction place() {\n  if (!cur || !tip) return;\n  const r = cur.getBoundingClientRect(), w = tip.offsetWidth, h = tip.offsetHeight, vw = window.innerWidth, vh = window.innerHeight;\n  // A BUTTON IN A ROW OF BUTTONS TIPS ABOVE THE WHOLE ROW (the play review, l4: the world card's \"Share a picture of this\n  // world\" sat over the Up a level button beside it): the tip rises over the block of buttons it belongs to, so it never\n  // covers a control the pointer is about to reach. A lone control tips above itself, as before.\n  const row = cur.parentElement, many = !!row && row.querySelectorAll(':scope > button, :scope > a').length >= 3;\n  const above = many ? Math.min(r.top, row.getBoundingClientRect().top) : r.top;\n  let top = above - GAP - h;\n  if (top < EDGE) top = Math.min(vh - EDGE - h, r.bottom + GAP);   // no room above: below\n  const left = Math.max(EDGE, Math.min(vw - EDGE - w, r.left + r.width / 2 - w / 2));\n  tip.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;\n}\nfunction show() {\n  timer = 0;\n  if (!cur || !cur.isConnected) { hide(); return; }\n  const text = cur.dataset.ktip;\n  if (!text) { hide(); return; }\n  ensure().textContent = text;\n  place();\n  tip.classList.add('on');\n  // A title written while it shows (a gauge's reading) is the tip's new text.\n  watch = new MutationObserver(() => {\n    if (!cur) return;\n    const t = cur.getAttribute('title');\n    if (t == null) return;\n    cur.dataset.ktip = t; cur.removeAttribute('title');\n    if (aria) cur.setAttribute('aria-label', t);\n    if (tip.textContent !== t) { tip.textContent = t; place(); }\n    if (!t) hide();\n  });\n  watch.observe(cur, { attributes: true, attributeFilter: ['title'] });\n  // An element repainted away under a still pointer (a slot rebuilt) takes its tip with it.\n  clearInterval(hideCheck);\n  hideCheck = setInterval(() => { if (!cur || !cur.isConnected) hide(); }, 250);\n}\nexport function hideTooltip() { hide(); pointTipHide(); }\n\n// A TIP AT THE POINTER, for things that are not elements: a world in the system view (the owner, Oct 2). Same look,\n// held a little above and to the right of the pointer, kept on the screen; `html` is trusted markup from the caller.\n// The element tips above and this one share the one node: whichever was asked for last is what shows.\nlet pointOn = false, pointKey = '';\nexport function pointTip(html, x, y, key) {\n  if (cur) hide();\n  const t = ensure();\n  if (!pointOn || key !== pointKey) { t.innerHTML = html; pointKey = key || html; }\n  const w = t.offsetWidth, h = t.offsetHeight, vw = window.innerWidth, vh = window.innerHeight;\n  let left = x + 14, top = y - h - 10;\n  if (left + w > vw - EDGE) left = x - 14 - w;\n  if (top < EDGE) top = y + 18;\n  left = Math.max(EDGE, Math.min(vw - EDGE - w, left)); top = Math.max(EDGE, Math.min(vh - EDGE - h, top));\n  t.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;\n  if (!pointOn) { pointOn = true; t.classList.add('on'); }\n}\nexport function pointTipHide() {\n  if (!pointOn) return;\n  pointOn = false; pointKey = '';\n  if (tip && !cur) tip.classList.remove('on');\n}\nfunction hide() {\n  if (timer) { clearTimeout(timer); timer = 0; }\n  clearInterval(hideCheck); hideCheck = 0;\n  if (tip && tip.classList.contains('on')) { tip.classList.remove('on'); lastHide = performance.now(); }\n  restore(cur); cur = null;\n}\nfunction arm(el) {\n  if (el === cur) return;\n  pointTipHide();\n  const warm = (tip && tip.classList.contains('on')) || performance.now() - lastHide < WARM_MS;\n  hide();\n  cur = el; lift(el);\n  if (!el.dataset.ktip) { restore(el); cur = null; return; }\n  if (warm) show(); else timer = setTimeout(show, SHOW_MS);\n}\nconst titled = (n) => (n && n.closest ? n.closest('[title], [data-ktip]') : null);\n\nexport function initTooltips() {\n  if (initTooltips.done || typeof document === 'undefined') return;\n  initTooltips.done = true;\n  document.addEventListener('pointerover', (e) => {\n    if (e.pointerType === 'touch' || document.pointerLockElement) return;\n    const el = titled(e.target);\n    if (el && el.getAttribute('title') === '') return;      // an empty title is a deliberate \"no tip\"\n    if (el) arm(el); else if (cur) hide();\n  }, true);\n  document.addEventListener('pointerout', (e) => {\n    if (!cur) return;\n    const to = e.relatedTarget;\n    if (!to || !cur.contains(to)) { if (!titled(to)) hide(); }\n  }, true);\n  document.addEventListener('focusin', (e) => {\n    const el = titled(e.target);\n    if (el && el.matches(':focus-visible')) arm(el);\n  });\n  document.addEventListener('focusout', (e) => { if (cur && e.target === cur) hide(); });\n  for (const ev of ['pointerdown', 'keydown', 'wheel']) document.addEventListener(ev, () => { if (cur) hide(); pointTipHide(); }, { capture: true, passive: true });\n  window.addEventListener('scroll', () => { if (cur) hide(); }, { capture: true, passive: true });\n  window.addEventListener('blur', () => { if (cur) hide(); });\n  document.addEventListener('pointerlockchange', () => { if (cur) hide(); pointTipHide(); });\n}\n"],"mappings":"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","names":[],"file":"tooltip.mjs"}