blog/static/pixacao/index.html
2026-05-30 10:30:31 +02:00

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>&nbsp;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>