298 lines
9.2 KiB
HTML
298 lines
9.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Pixação</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
html, body {
|
|
height: 100%;
|
|
overflow: hidden;
|
|
background: #ffffff;
|
|
}
|
|
|
|
#pager {
|
|
will-change: transform;
|
|
}
|
|
|
|
.screen {
|
|
height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
#screen-greeting,
|
|
#screen-date {
|
|
font-family: monospace;
|
|
color: #1a1612;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
#screen-greeting {
|
|
font-size: clamp(1rem, 3vw, 1.6rem);
|
|
margin: 0 3em 0 3em;
|
|
text-align: center;
|
|
}
|
|
|
|
#screen-date {
|
|
font-size: clamp(0.8rem, 2.4vw, 1.3rem);
|
|
font-face: monospace;
|
|
font-weight: 600;
|
|
}
|
|
|
|
#screen-greeting a {
|
|
color: inherit;
|
|
text-decoration: underline;
|
|
text-underline-offset: 0.2em;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#diagram {
|
|
max-height: 80vh;
|
|
max-width: 80vw;
|
|
width: auto;
|
|
height: auto;
|
|
display: block;
|
|
}
|
|
|
|
#print-text {
|
|
font-family: sans-serif;
|
|
font-size: 0.82rem;
|
|
color: #999;
|
|
max-width: 56ch;
|
|
line-height: 1.65;
|
|
text-align: justify;
|
|
margin: 0 3em 0 3em;
|
|
}
|
|
|
|
@media print {
|
|
/* margin:0 hints to Chromium/Firefox to suppress their date/title/URL headers.
|
|
Paper size is left to the browser/user so this works on A4, Letter, etc. */
|
|
@page {
|
|
margin: 0;
|
|
}
|
|
|
|
html, body {
|
|
height: auto !important;
|
|
overflow: visible !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
/* position:fixed;inset:0 pins to the paper box in all browsers.
|
|
vw/vh would resolve to the screen viewport in Chromium and shrink into a corner. */
|
|
#pager {
|
|
transform: none !important;
|
|
transition: none !important;
|
|
position: fixed;
|
|
inset: 0;
|
|
padding: 14mm;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: space-evenly;
|
|
}
|
|
|
|
/* Hide all screens, then re-show the three we want */
|
|
.screen {
|
|
display: none !important;
|
|
height: auto;
|
|
min-height: 0;
|
|
}
|
|
|
|
#screen-date,
|
|
#screen-diagram,
|
|
#screen-text {
|
|
display: flex !important;
|
|
width: 100%;
|
|
justify-content: center;
|
|
align-items: center;
|
|
break-inside: avoid;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
#screen-date {
|
|
font-size: 18pt;
|
|
}
|
|
|
|
/* Without this, the SVG hugs the left edge of the block-level container. */
|
|
#diagram-container {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
}
|
|
|
|
#diagram {
|
|
width: 50% !important;
|
|
height: auto !important;
|
|
max-width: 100% !important;
|
|
max-height: none !important;
|
|
}
|
|
|
|
/* Browsers (esp. Chromium) darken light text to save ink unless asked
|
|
not to. print-color-adjust: exact preserves the gray. */
|
|
#print-text {
|
|
font-size: 14pt;
|
|
max-width: 50em;
|
|
line-height: 1.55;
|
|
-webkit-print-color-adjust: exact;
|
|
print-color-adjust: exact;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="pager">
|
|
|
|
<div class="screen" id="screen-date"></div>
|
|
|
|
<div class="screen" id="screen-greeting">
|
|
<span>
|
|
<a href="#" onclick="event.preventDefault(); window.print();">Print this page</a> to bestow protection from unforeseen circumstances upon your computer.
|
|
</span>
|
|
</div>
|
|
|
|
<div class="screen" id="screen-diagram">
|
|
<div id="diagram-container"></div>
|
|
</div>
|
|
|
|
<div class="screen" id="screen-text">
|
|
<p id="print-text">
|
|
THE BLESSING IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND DHARMA.
|
|
IN NO EVENT SHALL THE AUTHORS OR CUSTODIANS BE LIABLE FOR
|
|
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
PRAYER, OFFERING OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
WITH THE BLESSING OR THE USE OR OTHER DEALINGS IN THE BLESSING.
|
|
</p>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
// ── Pager ──────────────────────────────────────────────────────────────────
|
|
const pager = document.getElementById('pager');
|
|
const PAGES = pager.querySelectorAll('.screen').length;
|
|
window.currentPage = Math.min(parseInt(sessionStorage.getItem('page') || '0', 10), PAGES - 1);
|
|
let locked = false;
|
|
|
|
pager.style.transition = 'none';
|
|
pager.style.transform = `translateY(${-window.currentPage * 100}vh)`;
|
|
|
|
function goTo(n) {
|
|
n = Math.max(0, Math.min(PAGES - 1, n));
|
|
if (n === window.currentPage || locked) return;
|
|
locked = true;
|
|
window.currentPage = n;
|
|
sessionStorage.setItem('page', n);
|
|
pager.style.transition = 'transform 0.45s ease-in-out';
|
|
pager.style.transform = `translateY(${-window.currentPage * 100}vh)`;
|
|
setTimeout(() => { locked = false; }, 500);
|
|
}
|
|
|
|
window.addEventListener('wheel', e => {
|
|
e.preventDefault();
|
|
if (e.deltaY > 0) goTo(window.currentPage + 1);
|
|
else if (e.deltaY < 0) goTo(window.currentPage - 1);
|
|
}, { passive: false });
|
|
|
|
let touchStartY = null;
|
|
window.addEventListener('touchstart', e => {
|
|
touchStartY = e.touches[0].clientY;
|
|
}, { passive: true });
|
|
window.addEventListener('touchend', e => {
|
|
if (touchStartY === null) return;
|
|
const dy = touchStartY - e.changedTouches[0].clientY;
|
|
if (Math.abs(dy) > 40) {
|
|
if (dy > 0) goTo(window.currentPage + 1);
|
|
else goTo(window.currentPage - 1);
|
|
}
|
|
touchStartY = null;
|
|
}, { passive: true });
|
|
|
|
window.addEventListener('keydown', e => {
|
|
switch (e.key) {
|
|
case 'ArrowDown': case 'ArrowRight': case 'Enter': case 'PageDown': case ' ':
|
|
e.preventDefault(); goTo(window.currentPage + 1); break;
|
|
case 'ArrowUp': case 'ArrowLeft': case 'PageUp':
|
|
e.preventDefault(); goTo(window.currentPage - 1); break;
|
|
}
|
|
});
|
|
|
|
// ── SVG substitution ───────────────────────────────────────────────────────
|
|
async function main() {
|
|
const [diagramText, pixacaoText] = await Promise.all([
|
|
fetch('template diagram.svg').then(r => r.text()),
|
|
fetch('template pixacao onepage.svg').then(r => r.text()),
|
|
]);
|
|
|
|
const parser = new DOMParser();
|
|
const diagramDoc = parser.parseFromString(diagramText, 'image/svg+xml');
|
|
const pixacaoDoc = parser.parseFromString(pixacaoText, 'image/svg+xml');
|
|
|
|
// Build symbol map: digit → group (already carries its own centering transform)
|
|
const symbols = {};
|
|
pixacaoDoc.querySelectorAll('[id^="sym-"]').forEach(g => {
|
|
symbols[g.id.slice(4)] = g;
|
|
});
|
|
|
|
// Slot reading order: top-to-bottom within each column, left-to-right across columns
|
|
const slotOrder = [
|
|
's1', 's2', 's3', 's4', 's5', 's6',
|
|
's101', 's102', 's103', 's104', 's105', 's106',
|
|
's201', 's202', 's203', 's204', 's205', 's206',
|
|
's301', 's302', 's303', 's304', 's305', 's306',
|
|
's401', 's402', 's403', 's404', 's405', 's406',
|
|
];
|
|
|
|
// Fetch from NIST Randomness Beacon; use first 30 chars of outputValue
|
|
const { pulse } = await fetch('https://beacon.nist.gov/beacon/2.0/pulse/last')
|
|
.then(r => r.json());
|
|
const hexString = pulse.outputValue.slice(0, slotOrder.length).toLowerCase();
|
|
|
|
// ISO UTC timestamp, no milliseconds
|
|
document.getElementById('screen-date').textContent =
|
|
pulse.timeStamp.replace(/\.\d{3}Z$/, 'Z');
|
|
|
|
// Substitute: each slot is a <rect> whose x/y give the cell origin;
|
|
// sym groups carry their own centering transform, so just stack them.
|
|
slotOrder.forEach((slotId, i) => {
|
|
const rect = diagramDoc.getElementById(slotId);
|
|
if (!rect) return;
|
|
const sym = symbols[hexString[i]];
|
|
if (!sym) return;
|
|
|
|
const g = diagramDoc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
g.setAttribute('transform', `translate(${rect.getAttribute('x')},${rect.getAttribute('y')})`);
|
|
|
|
const clone = diagramDoc.importNode(sym, true);
|
|
clone.removeAttribute('id');
|
|
clone.querySelectorAll('[id]').forEach(el => el.removeAttribute('id'));
|
|
g.appendChild(clone);
|
|
|
|
rect.parentNode.replaceChild(g, rect);
|
|
});
|
|
|
|
const svgEl = diagramDoc.documentElement;
|
|
svgEl.setAttribute('width', '400');
|
|
svgEl.setAttribute('height', '850');
|
|
svgEl.setAttribute('id', 'diagram');
|
|
|
|
document.getElementById('diagram-container').appendChild(
|
|
document.importNode(svgEl, true)
|
|
);
|
|
}
|
|
|
|
main().catch(err => {
|
|
document.getElementById('screen-date').textContent = 'Error: ' + err.message;
|
|
console.error(err);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|