Public, free to read, reuse, and adapt — Kirk Patrick Miller's notice for this edition. Not a patent grant.
FLINT_NEXT_TREE_GLOW_2A_v0.1.txt · c03
d92d1487bd · words 796 · characters 20561–26539
Previous section · Next section
Exact section text · Full source · Source index
+ // v-tree-glow-2a-v0: a calm way back. Escape, a tap on the empty glass, a close
+ // in the galaxies menu, and the browser's Back each close what is open, one
+ // layer at a time, and stay on this page. They reuse the same close buttons.
+ function bindWayBack() {
+ if (window.__treeWayBack) return;
+ window.__treeWayBack = true;
+ var place = document.getElementById('place-veil');
+ var thread = document.getElementById('thread-veil');
+ function isOpen(v) { return !!(v && v.classList.contains('is-open')); }
+ function openGalaxies() { return document.querySelector('details.galaxies[open]'); }
+ function closeTop() {
+ var gal = openGalaxies();
+ if (gal) { gal.open = false; return true; }
+ var menu = document.getElementById('lumino-menu');
+ if (menu && !menu.hidden) { hideLuminoMenu(); return true; }
+ var picker = document.querySelector('[data-core-picker]:not([hidden])');
+ var decline = picker && picker.querySelector('.core-bind-decline');
+ if (decline) { decline.click(); return true; }
+ var tc = document.getElementById('thread-close');
+ if (isOpen(thread) && tc) { tc.click(); return true; }
+ var pc = document.getElementById('place-veil-close');
+ if (isOpen(place) && pc) { pc.click(); return true; }
+ return false;
+ }
+
+ document.addEventListener('keydown', function (e) {
+ if (e.key !== 'Escape' && e.key !== 'Esc') return;
+ if (e.defaultPrevented) return;
+ if (closeTop()) e.preventDefault();
+ });
+
+ // A tap on the glass itself (not on a word, chair or card) closes that place.
+ // Press and release must both land on the glass, and never in its first moment.
+ [place, thread].forEach(function (veil) {
+ if (!veil) return;
+ var downOnGlass = false;
+ var openedAt = 0;
+ new MutationObserver(function () {
+ if (isOpen(veil) && !openedAt) openedAt = Date.now();
+ if (!isOpen(veil)) openedAt = 0;
+ }).observe(veil, { attributes: true, attributeFilter: ['class'] });
+ veil.addEventListener('pointerdown', function (e) { downOnGlass = e.target === veil; });
+ veil.addEventListener('click', function (e) {
+ var ok = downOnGlass && e.target === veil && isOpen(veil) && openedAt && Date.now() - openedAt > 600;
+ downOnGlass = false;
+ if (!ok) return;
+ var btn = document.getElementById(veil === thread ? 'thread-close' : 'place-veil-close');
+ if (btn) btn.click();
+ });
+ });
+
+ // galaxies: a visible close inside, and a tap outside closes it (that tap does
+ // only that, so it never opens something by surprise).
+ var panels = document.querySelectorAll('details.galaxies .galaxies-panel');
+ for (var gi = 0; gi < panels.length; gi++) {
+ if (panels[gi].querySelector('[data-galaxies-close]')) continue;
+ var gx = document.createElement('button');
+ gx.type = 'button';
+ gx.className = 'galaxies-close';
+ gx.setAttribute('data-galaxies-close', '1');
+ gx.textContent = 'close';
+ gx.addEventListener('click', function (e) {
+ e.preventDefault();
+ var d = e.currentTarget.closest('details');
+ if (d) d.open = false;
+ });
+ panels[gi].appendChild(gx);
+ }
+ var swallowUntil = 0;
+ document.addEventListener('pointerdown', function (e) {
+ var gal = openGalaxies();
+ if (!gal || gal.contains(e.target)) return;
+ gal.open = false;
+ swallowUntil = Date.now() + 500;
+ }, true);
+ document.addEventListener('click', function (e) {
+ if (Date.now() > swallowUntil) return;
+ swallowUntil = 0;
+ e.preventDefault();
+ e.stopPropagation();
+ }, true);
+
+ // Back closes the open place instead of leaving the site. One quiet history
+ // step while something is open; closing it any other way gives the step back.
+ var stepped = false;
+ function anyOpen() { return isOpen(place) || isOpen(thread); }
+ function settle() {
+ setTimeout(function () {
+ if (anyOpen()) {
+ if (!stepped) {
+ try { history.pushState({ treeWayBack: 1 }, ''); stepped = true; } catch (eP) {}
+ }
+ } else if (stepped) {
+ stepped = false;
+ try { if (history.state && history.state.treeWayBack) history.back(); } catch (eB) {}
+ }
+ }, 160);
+ }
+ [place, thread].forEach(function (veil) {
+ if (veil) new MutationObserver(settle).observe(veil, { attributes: true, attributeFilter: ['class'] });
+ });
+ window.addEventListener('popstate', function () {
+ if (!stepped) return;
+ stepped = false;
+ closeTop();
+ settle();
+ });
+ }
+
function goToLumino(id) {
if (!id) return;
var galaxy = currentGalaxy();
@@ -1168,6 +1277,9 @@
findBtn.textContent = 'Find local minds';
findBtn.setAttribute('aria-label', 'Find local minds. May I look — only when you ask.');
wrap.appendChild(findBtn);
+ // v-tree-glow-2a-v0: a second tap on the legend landed on Find as it slid in
+ // under the finger and looked unasked. A tap in the first moment is not a yes.
+ var findReadyAt = Date.now() + 700;
var ring = document.createElement('div');
ring.className = 'core-chairs';
@@ -1216,12 +1328,15 @@
var n = wrap.querySelector('[data-core-note]');
if (!n) return;
n.textContent = msg;
+ // v-tree-glow-2a-v0: the newest line stays in view on a phone (CSS sticky).
+ n.classList.add('is-fresh');
}
function setNoteAbsent(kind) {
var n = wrap.querySelector('[data-core-note]');
if (!n) return;
n.textContent = '';
+ n.classList.add('is-fresh');
if (kind === 'https') {
n.appendChild(document.createTextNode(
'The mind may be there, but this secure page cannot see the quieter door. Try '
@@ -1430,6 +1545,7 @@
paintAll();