FreeLattice · Celeste reading home

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

Archive pages whose extracted text matched this section: 1449, 1450. Unmatched pages stay on the source record with the checked page method. They are not guessed into a section.

No topic tag was earned for this page under the recorded evidence rule.

+  // 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();