Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3891a027f3 | ||
|
|
e1f541d366 | ||
|
|
0f9c468b1e | ||
|
|
ede79f5c57 |
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
# Browser tab
|
# Browser tab
|
||||||
|
|
||||||
One web page, full screen in the tab: no address bar, bookmarks or scrollbars — just the page, in the
|
One web page, full screen in the tab: no address bar, bookmarks, scrollbars or focus highlights — just the page, in the
|
||||||
same area the Graphics tab uses. It loads when the app starts and keeps running while other tabs are
|
same area the Graphics tab uses. It loads when the app starts and keeps running while other tabs are
|
||||||
showing, so it is ready the moment it is opened. Built for It Is Written
|
showing, so it is ready the moment it is opened. Built for It Is Written
|
||||||
(`https://it-is-written-waters-church.vercel.app/`), but any web page works.
|
(`https://it-is-written-waters-church.vercel.app/`), but any web page works.
|
||||||
@@ -65,7 +65,9 @@ the search and could put different results on the program feed.
|
|||||||
- **`browser-guest-preload.cjs`** runs inside the page (invisible to the site) and reports what the app
|
- **`browser-guest-preload.cjs`** runs inside the page (invisible to the site) and reports what the app
|
||||||
cannot see from outside: a tapped text field, a tapped reference (found with the app's own
|
cannot see from outside: a tapped text field, a tapped reference (found with the app's own
|
||||||
`scripture-scan.js` — the same rules ProPresenter Sync uses), scroll, activity, five fingers, and — only
|
`scripture-scan.js` — the same rules ProPresenter Sync uses), scroll, activity, five fingers, and — only
|
||||||
while a follower is linked — a script-free copy of the page's DOM.
|
while a follower is linked — a script-free copy of the page's DOM. It also takes the look (outline,
|
||||||
|
border, glow) out of the site's focus styles: every key on the app's keyboard takes focus from the page
|
||||||
|
for a moment, so a focus highlight would flash with each letter typed.
|
||||||
- **`browser-tab.js`** owns the tab: the pages, the keyboard (via `createPrayerKeyboard` with a "sink"),
|
- **`browser-tab.js`** owns the tab: the pages, the keyboard (via `createPrayerKeyboard` with a "sink"),
|
||||||
scripture taps, scrolling for the phone / OSC / admin, and the follower's copy. On the follower it
|
scripture taps, scrolling for the phone / OSC / admin, and the follower's copy. On the follower it
|
||||||
shows that copy in sandboxed frames (no scripts); in a plain browser (admin offstage copy, `npm start`)
|
shows that copy in sandboxed frames (no scripts); in a plain browser (admin offstage copy, `npm start`)
|
||||||
|
|||||||
@@ -167,6 +167,71 @@ document.addEventListener('focusout', (e) => {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ no focus highlight
|
||||||
|
/*
|
||||||
|
* Nothing on the page shows that it has focus. The app's keyboard is outside the page, so every key press
|
||||||
|
* takes focus from the page for a moment and gives it straight back: a site's focus style would flash
|
||||||
|
* with each letter — and on stage it is clutter anyway. Outline rings (the browser's own and any site's)
|
||||||
|
* go with one rule in browser-tab.js's CLEAN_CSS; this takes the rest — a border that brightens, a glow —
|
||||||
|
* out of the site's own :focus / :focus-within / :focus-visible rules, and leaves everything else those
|
||||||
|
* rules do: a menu that opens on focus still opens. Sites add rules as they go (CSS-in-JS, late
|
||||||
|
* stylesheets), so this runs again just before anything can take focus. Another origin's stylesheets
|
||||||
|
* cannot be read and keep theirs; the outline rule still covers their rings.
|
||||||
|
*/
|
||||||
|
const FOCUS_LOOK = /^(?:outline|box-shadow|border)(?:-|$)/;
|
||||||
|
const seenRules = new WeakSet();
|
||||||
|
|
||||||
|
// Split at commas that are not inside brackets or quotes: "a:hover, b:is(c, d)" -> 2; "x:1; y:f(a;b)" -> 2.
|
||||||
|
function splitTop(text, sep) {
|
||||||
|
const out = [];
|
||||||
|
let depth = 0, quote = '', start = 0;
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
const c = text[i];
|
||||||
|
if (quote) { if (c === '\\') i++; else if (c === quote) quote = ''; continue; }
|
||||||
|
if (c === '"' || c === "'") quote = c;
|
||||||
|
else if (c === '(' || c === '[') depth++;
|
||||||
|
else if (c === ')' || c === ']') depth--;
|
||||||
|
else if (c === sep && depth === 0) { out.push(text.slice(start, i).trim()); start = i + 1; }
|
||||||
|
}
|
||||||
|
out.push(text.slice(start).trim());
|
||||||
|
return out.filter(Boolean);
|
||||||
|
}
|
||||||
|
// Styled BECAUSE it has focus. ":not(:focus)" styles a field at rest, so it does not count.
|
||||||
|
function focusDriven(sel) {
|
||||||
|
return /:focus(?:-within|-visible)?(?![\w-])/.test(sel.replace(/:not\((?:[^()]|\([^()]*\))*\)/g, ''));
|
||||||
|
}
|
||||||
|
function isLook(decl) { return FOCUS_LOOK.test(decl.slice(0, decl.indexOf(':')).trim().toLowerCase()); }
|
||||||
|
|
||||||
|
function stripFocusLook(list, owner) {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const r = list[i];
|
||||||
|
if (r.cssRules && r.cssRules.length) stripFocusLook(r.cssRules, r); // @media, @supports, @layer, nesting
|
||||||
|
if (!r.selectorText || !r.style || seenRules.has(r)) continue;
|
||||||
|
seenRules.add(r);
|
||||||
|
const sels = splitTop(r.selectorText, ',');
|
||||||
|
const focus = sels.filter(focusDriven);
|
||||||
|
if (!focus.length) continue;
|
||||||
|
// Declarations as written (cssText), so a shorthand built on var() survives intact.
|
||||||
|
const decls = splitTop(r.style.cssText, ';');
|
||||||
|
const keep = decls.filter((d) => !isLook(d));
|
||||||
|
if (keep.length === decls.length) continue;
|
||||||
|
if (focus.length === sels.length) { r.style.cssText = keep.join('; '); continue; }
|
||||||
|
// Shared with selectors that are not about focus ("a:hover, a:focus"): those keep the whole rule, and
|
||||||
|
// the focus ones move to a copy without the look.
|
||||||
|
r.selectorText = sels.filter((s) => !focusDriven(s)).join(', ');
|
||||||
|
if (!keep.length) continue;
|
||||||
|
try { owner.insertRule(focus.join(', ') + ' { ' + keep.join('; ') + ' }', i + 1); i++; seenRules.add(list[i]); } catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideFocusLook() {
|
||||||
|
const sheets = [...document.styleSheets, ...(document.adoptedStyleSheets || [])];
|
||||||
|
for (const sh of sheets) { try { stripFocusLook(sh.cssRules, sh); } catch (_) {} } // another origin's: unreadable
|
||||||
|
}
|
||||||
|
window.addEventListener('DOMContentLoaded', hideFocusLook);
|
||||||
|
window.addEventListener('load', hideFocusLook);
|
||||||
|
window.addEventListener('pointerdown', hideFocusLook, true);
|
||||||
|
document.addEventListener('focusin', hideFocusLook, true);
|
||||||
|
|
||||||
// ------------------------------------------------------------------ activity + five fingers
|
// ------------------------------------------------------------------ activity + five fingers
|
||||||
let lastActivity = 0;
|
let lastActivity = 0;
|
||||||
function activity() {
|
function activity() {
|
||||||
|
|||||||
+7
-4
@@ -41,15 +41,18 @@
|
|||||||
var RETRY_MS = 10000;
|
var RETRY_MS = 10000;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The page itself is the only thing on screen: no scrollbars, no blue tap flash, no text selection from
|
* The page itself is the only thing on screen: no scrollbars, no blue tap flash, no focus ring, no text
|
||||||
* a resting finger, and no rubber-band pull that would drag the whole page (or, sideways, swipe back a
|
* selection from a resting finger, and no rubber-band pull that would drag the whole page (or, sideways,
|
||||||
* page). Inputs keep their selection so typing still behaves. Applied to the live page with insertCSS
|
* swipe back a page). Inputs keep their selection so typing still behaves. Applied to the live page with
|
||||||
* and to the booth's copy as a <style>, so both screens look the same.
|
* insertCSS and to the booth's copy as a <style>, so both screens look the same. (The ring matters
|
||||||
|
* twice: every key on the app's keyboard takes focus from the page for a moment, so a ring would flash
|
||||||
|
* with each letter. The site's other focus styles are taken out by browser-guest-preload.cjs.)
|
||||||
*/
|
*/
|
||||||
var CLEAN_CSS =
|
var CLEAN_CSS =
|
||||||
'html,body{scrollbar-width:none!important;overscroll-behavior:none!important;}' +
|
'html,body{scrollbar-width:none!important;overscroll-behavior:none!important;}' +
|
||||||
'::-webkit-scrollbar{display:none!important;width:0!important;height:0!important;}' +
|
'::-webkit-scrollbar{display:none!important;width:0!important;height:0!important;}' +
|
||||||
'*{-webkit-tap-highlight-color:transparent!important;}' +
|
'*{-webkit-tap-highlight-color:transparent!important;}' +
|
||||||
|
':focus,:focus-visible{outline:none!important;}' +
|
||||||
'body *{-webkit-user-select:none!important;user-select:none!important;-webkit-touch-callout:none!important;}' +
|
'body *{-webkit-user-select:none!important;user-select:none!important;-webkit-touch-callout:none!important;}' +
|
||||||
'input,textarea,[contenteditable],[contenteditable] *{-webkit-user-select:text!important;user-select:text!important;}';
|
'input,textarea,[contenteditable],[contenteditable] *{-webkit-user-select:text!important;user-select:text!important;}';
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bible-app",
|
"name": "bible-app",
|
||||||
"version": "1.4.39",
|
"version": "1.4.41",
|
||||||
"description": "Minimalist Bible App for Portrait-Oriented Touchscreen TV",
|
"description": "Minimalist Bible App for Portrait-Oriented Touchscreen TV",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "main.cjs",
|
"main": "main.cjs",
|
||||||
|
|||||||
+3
-1
@@ -1547,7 +1547,7 @@ body.drawing * {
|
|||||||
}
|
}
|
||||||
/* Removed explicit close button; using hold-to-close gesture on tab */
|
/* Removed explicit close button; using hold-to-close gesture on tab */
|
||||||
|
|
||||||
.bible-icon, .media-icon, .whiteboard-icon, .media-tab-icon, .graphics-tab-icon, .maps-tab-icon, .prayer-tab-icon, .alpha-icon, .notes-icon, .ask-tab-icon {
|
.bible-icon, .media-icon, .whiteboard-icon, .media-tab-icon, .graphics-tab-icon, .maps-tab-icon, .browser-tab-icon, .prayer-tab-icon, .alpha-icon, .notes-icon, .ask-tab-icon {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
@@ -1564,6 +1564,8 @@ body.drawing * {
|
|||||||
.tab.active .graphics-tab-icon,
|
.tab.active .graphics-tab-icon,
|
||||||
.tab:hover .maps-tab-icon,
|
.tab:hover .maps-tab-icon,
|
||||||
.tab.active .maps-tab-icon,
|
.tab.active .maps-tab-icon,
|
||||||
|
.tab:hover .browser-tab-icon,
|
||||||
|
.tab.active .browser-tab-icon,
|
||||||
.tab:hover .prayer-tab-icon,
|
.tab:hover .prayer-tab-icon,
|
||||||
.tab.active .prayer-tab-icon,
|
.tab.active .prayer-tab-icon,
|
||||||
.tab:hover .alpha-icon,
|
.tab:hover .alpha-icon,
|
||||||
|
|||||||
Reference in New Issue
Block a user