<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>High-watermark Persistent Rack (16 documents)</title>
<style>
:root{
--doc-w:850px; /* document width */
--doc-h:1100px; /* document height */
--gap-x:50px; /* horizontal gap between docs */
--gap-y:100px; /* vertical gap between docs */
--rack-left:20px;
--rack-top:20px;
}
html,body{height:100%;margin:0;font-family:Inter,Arial,Helvetica,sans-serif}
/* Viewport where user scrolls/pans (this is what we scroll programmatically) */
.viewport{width:100vw;height:100vh;overflow:auto;touch-action:pan-x pan-y;background:#eef2f6}
/* Very large workspace that simulates the 32" x 64" desktop (kept pixel-based for consistency) */
.workspace{position:relative;width:3200px;height:6400px;margin:40px auto;background:linear-gradient(180deg,#fff,#f7fbff);box-shadow:0 12px 40px rgba(10,20,40,0.08)}/* Rack (top-left) */ .rack{position:absolute;left:var(--rack-left);top:var(--rack-top);display:grid;grid-template-columns:repeat(4,100px);grid-auto-rows:60px;gap:20px;z-index:120} .rack button{width:100px;height:60px;border-radius:8px;border:1px solid #888;background:linear-gradient(#fff,#e9effa);font-weight:700;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer} .rack .coords{font-size:11px;font-weight:500;margin-top:4px}
/* Editor panel (appears below rack) */ #editorPanel{position:absolute;left:var(--rack-left);display:none;background:#fff;border:1px solid #cfd6df;padding:10px;border-radius:6px;box-shadow:0 6px 18px rgba(10,20,40,0.08);max-height:55vh;overflow:auto;z-index:150} #editorPanel textarea{display:block;width:320px;height:30px;margin-bottom:8px;font-size:13px;padding:6px}
/* Controls */ .controls{position:absolute;left:480px;top:28px;z-index:200} .controls button{margin-right:10px;padding:8px 12px;border-radius:6px;border:1px solid #888;background:#fff;cursor:pointer}
/* Document panel */ .doc{position:absolute;width:var(--doc-w);height:var(--doc-h);background:#fff;border:1px solid #c7ced8;border-radius:8px;overflow:hidden;z-index:60;box-shadow:0 6px 20px rgba(8,20,40,0.06)} .doc iframe{position:relative;z-index:1;width:100%;height:100%;border:0}
/* Floating return button: high z-index so it's always clickable above iframe content */ .return-btn{position:absolute;top:12px;left:12px;z-index:9999;padding:8px 10px;border-radius:6px;border:1px solid #666;background:#fff;font-weight:700;cursor:pointer}
/* small note */ .note{position:fixed;right:12px;bottom:12px;background:#fff;padding:8px 12px;border-radius:8px;border:1px solid #ddd;box-shadow:0 8px 26px rgba(0,0,0,0.08);z-index:200} </style>
</head>
<body><div class="viewport" id="viewport">
<div class="workspace" id="workspace">
<div id="rack" class="rack" aria-label="button rack"></div><div class="controls">
<button id="editBtn">Edit</button>
<button id="saveBtn">Save</button>
<button id="clearBtn">Clear All</button>
</div>
<div id="editorPanel" aria-hidden="true"></div>
<div id="docsContainer"></div>
</div>
</div><div class="note">Triple-tap anywhere (mobile) or triple-click (desktop) to return home above the rack.</div><script>
(function(){
const FRAME_COUNT = 16;
const DOC_W = 850;
const DOC_H = 1100;
const GAP_X = 50;
const GAP_Y = 100;
const viewport = document.getElementById('viewport');
const workspace = document.getElementById('workspace');
const rack = document.getElementById('rack');
const docsContainer = document.getElementById('docsContainer');
const editorPanel = document.getElementById('editorPanel');
const editBtn = document.getElementById('editBtn');
const saveBtn = document.getElementById('saveBtn');
const clearBtn = document.getElementById('clearBtn');
let editMode = false;
let currentView = 'rack'; // 'rack' or 'docX'
let autosaveTimeout = null;
function getCoords(i){
const col = (i-1) % 4;
const row = Math.floor((i-1)/4);
const x = col * (DOC_W + GAP_X);
const y = row * (DOC_H + GAP_Y);
return {x,y, xEnd: x + DOC_W, yEnd: y + DOC_H};
}
// Create rack buttons, docs and editor textareas
for(let i=1;i<=FRAME_COUNT;i++){
const coords = getCoords(i);
// rack button
const b = document.createElement('button');
b.innerHTML = `${i}<span class='coords'>X:${coords.x}-${coords.xEnd}, Y:${coords.y}-${coords.yEnd}</span>`;
b.addEventListener('click', ()=> goToDoc(i));
rack.appendChild(b);
// document container
const doc = document.createElement('div');
doc.className = 'doc';
doc.id = 'doc' + i;
doc.style.left = coords.x + 'px';
doc.style.top = coords.y + 'px';
// return button fixed within the doc (floating above iframe)
const ret = document.createElement('button');
ret.className = 'return-btn';
ret.innerHTML = '.'+i+` <span style='font-size:11px;display:block;margin-top:4px;'>X:${coords.x}-${coords.xEnd}, Y:${coords.y}-${coords.yEnd}</span>`;
ret.addEventListener('click', ()=> { goHome(); });
doc.appendChild(ret);
// iframe
const iframe = document.createElement('iframe');
iframe.id = 'iframe' + i;
iframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin allow-popups');
doc.appendChild(iframe);
docsContainer.appendChild(doc);
// editor textarea
const ta = document.createElement('textarea');
ta.id = 'ta' + i;
ta.placeholder = `Document ${i} SRC`;
editorPanel.appendChild(ta);
}
// Position editorPanel exactly 30px below the rack
function positionEditorPanel(){
// rack is absolutely positioned inside workspace
const top = rack.offsetTop + rack.offsetHeight + 30; // 30px gap
const left = rack.offsetLeft;
editorPanel.style.top = top + 'px';
editorPanel.style.left = left + 'px';
}
// Run once after layout
positionEditorPanel();
// Scroll helpers: center a doc in the viewport
function centerElementInViewport(el){
const elRect = el.getBoundingClientRect();
const vpRect = viewport.getBoundingClientRect();
// Compute element's coordinates relative to workspace (offsetLeft/Top)
const offsetLeft = el.offsetLeft;
const offsetTop = el.offsetTop;
const targetLeft = Math.max(0, offsetLeft + el.offsetWidth/2 - viewport.clientWidth/2);
const targetTop = Math.max(0, offsetTop + el.offsetHeight/2 - viewport.clientHeight/2);
viewport.scrollTo({left: targetLeft, top: targetTop, behavior: 'auto'});
}
// Home scroll (center rack horizontally, position near top)
function goHome(){
const rackLeft = rack.offsetLeft;
const rackWidth = rack.offsetWidth;
const targetLeft = Math.max(0, rackLeft - (viewport.clientWidth/2 - rackWidth/2));
const targetTop = Math.max(0, rack.offsetTop - 20);
viewport.scrollTo({left: targetLeft, top: targetTop, behavior: 'auto'});
currentView = 'rack';
scheduleAutoSave();
}
// Navigate to doc
function goToDoc(i){
const el = document.getElementById('doc'+i);
if(!el) return;
centerElementInViewport(el);
currentView = 'doc'+i;
scheduleAutoSave();
}
// Toggle edit panel
editBtn.addEventListener('click', ()=>{
editMode = !editMode;
if(editMode){
// populate
for(let i=1;i<=FRAME_COUNT;i++){
const iframe = document.getElementById('iframe'+i);
const ta = document.getElementById('ta'+i);
ta.value = iframe.src || '';
}
positionEditorPanel();
editorPanel.style.display = 'block';
editorPanel.setAttribute('aria-hidden','false');
editBtn.textContent = 'Apply';
} else {
// apply
for(let i=1;i<=FRAME_COUNT;i++){
const iframe = document.getElementById('iframe'+i);
const ta = document.getElementById('ta'+i);
if(iframe && ta){ iframe.src = ta.value.trim(); }
}
editorPanel.style.display = 'none';
editorPanel.setAttribute('aria-hidden','true');
editBtn.textContent = 'Edit';
scheduleAutoSave();
}
});
// Save / Clear
function saveConfig(){
const cfg = { currentView };
for(let i=1;i<=FRAME_COUNT;i++){
const iframe = document.getElementById('iframe'+i);
cfg['iframe'+i] = iframe ? iframe.src : '';
}
localStorage.setItem('highwater_cfg_v1', JSON.stringify(cfg));
}
function loadConfig(){
const saved = JSON.parse(localStorage.getItem('highwater_cfg_v1') || '{}');
if(Object.keys(saved).length===0) return;
for(let i=1;i<=FRAME_COUNT;i++){
const src = saved['iframe'+i];
if(src){ const iframe = document.getElementById('iframe'+i); if(iframe) iframe.src = src; }
// also populate editor textarea so user sees current values when opening editor
const ta = document.getElementById('ta'+i); if(ta) ta.value = saved['iframe'+i] || '';
}
if(saved.currentView){
if(saved.currentView === 'rack') goHome();
else{
const el = document.getElementById(saved.currentView);
if(el) centerElementInViewport(el);
currentView = saved.currentView;
}
}
}
saveBtn.addEventListener('click', ()=>{ saveConfig(); alert('Saved configuration.'); });
clearBtn.addEventListener('click', ()=>{
localStorage.removeItem('highwater_cfg_v1');
for(let i=1;i<=FRAME_COUNT;i++){ const iframe = document.getElementById('iframe'+i); if(iframe) iframe.src = ''; const ta = document.getElementById('ta'+i); if(ta) ta.value = ''; }
goHome();
alert('Cleared configuration.');
});
// Auto-save scheduling (so last state is preserved even if user is interrupted)
function scheduleAutoSave(){
if(autosaveTimeout) clearTimeout(autosaveTimeout);
autosaveTimeout = setTimeout(()=>{ saveConfig(); autosaveTimeout = null; }, 400);
}
// Triple-tap / triple-click detection on viewport
(function(){
let taps = 0; let timer = null;
viewport.addEventListener('touchend', ()=>{
taps++;
if(taps===1){ timer = setTimeout(()=>{ taps = 0; }, 700); }
else if(taps===3){ clearTimeout(timer); taps = 0; goHome(); }
}, {passive:true});
// Desktop triple-click fallback
let clicks = 0; let clickTimer = null;
viewport.addEventListener('click', ()=>{
clicks++;
if(clicks===1){ clickTimer = setTimeout(()=>{ clicks = 0; }, 700); }
else if(clicks===3){ clearTimeout(clickTimer); clicks = 0; goHome(); }
});
})();
// Update editor panel position when window resizes (so it stays under rack)
window.addEventListener('resize', positionEditorPanel);
// Load saved config on start (and center view)
window.addEventListener('load', ()=>{ positionEditorPanel(); loadConfig(); });
// Expose a small API for debugging from console
window.__RackApp = { goToDoc, goHome, saveConfig, loadConfig };
})();
</script></body>
</html>