<ol class="ui-scroll-grid">
<li>
<ol>
<li id="r1-c1">You can enter Nowhere from Anywhere.</li>
<li id="r1-c2">I came from the North.</li>
<li id="r1-c3">I am now here.<br>In Nowhere.</li>
<li id="r1-c4">The map, a sheet of paper, with a single sentence.</li>
<li id="r1-c5">“The only way out is in”</li>
</ol>
</li>
<li>
<ol>
<li id="r2-c1">My canister if half full.<br>Or half empty?</li>
<li id="r2-c2">The path can lead to Anywhere.<br>Or Everywhere.</li>
<li id="r2-c3">Just keep walking.<br>Aimlessly.<br>Or determined.</li>
<li id="r2-c4">Did I just spot Yin and Yang?</li>
<li id="r2-c5">Nothing is permanent.</li>
</ol>
</li>
<li>
<ol>
<li id="r3-c1">You can enter Nowhere from Everywhere.</li>
<li id="r3-c2">I came from the West.</li>
<li id="r3-c3">I am now in the Middle of Nowhere.</li>
<li id="r3-c4">I came from the East.</li>
<li id="r3-c5">You can enter Nowhere from Everywhere.</li>
</ol>
</li>
<li>
<ol>
<li id="r4-c1">Nothing is permanent.</li>
<li id="r4-c2">Did I just spot Yang and Yin?</li>
<li id="r4-c3">Just keep walking.<br>Determined.<br>Or aimlessly.</li>
<li id="r4-c4">The path can lead to Everywhere.<br>Or Anywhere.</li>
<li id="r4-c5">My canister if half empty.<br>Or half full?</li>
</ol>
</li>
<li>
<ol>
<li id="r5-c1">“The only way out is in”</li>
<li id="r5-c2">The map, a sheet of paper, with a single sentence.</li>
<li id="r5-c3">I am now here.<br>In Nowhere.</li>
<li id="r5-c4">I came from the South</li>
<li id="r5-c5">You can enter Nowhere from Anywhere.</li>
</ol>
</li>
</ol>
<nav class="ui-scroll-grid-nav">
<a href="#r1-c1" title="Row 1, Cell 1"></a>
<a href="#r1-c2" title="Row 1, Cell 2"></a>
<a href="#r1-c3" title="Row 1, Cell 3"></a>
<a href="#r1-c4" title="Row 1, Cell 4"></a>
<a href="#r1-c5" title="Row 1, Cell 5"></a>
<a href="#r2-c1" title="Row 2, Cell 1"></a>
<a href="#r2-c2" title="Row 2, Cell 2"></a>
<a href="#r2-c3" title="Row 2, Cell 3"></a>
<a href="#r2-c4" title="Row 2, Cell 4"></a>
<a href="#r2-c5" title="Row 2, Cell 5"></a>
<a href="#r3-c1" title="Row 3, Cell 1"></a>
<a href="#r3-c2" title="Row 3, Cell 2"></a>
<a href="#r3-c3" title="Row 3, Cell 3"></a>
<a href="#r3-c4" title="Row 3, Cell 4"></a>
<a href="#r3-c5" title="Row 3, Cell 5"></a>
<a href="#r4-c1" title="Row 4, Cell 1"></a>
<a href="#r4-c2" title="Row 4, Cell 2"></a>
<a href="#r4-c3" title="Row 4, Cell 3"></a>
<a href="#r4-c4" title="Row 4, Cell 4"></a>
<a href="#r4-c5" title="Row 4, Cell 5"></a>
<a href="#r5-c1" title="Row 5, Cell 1"></a>
<a href="#r5-c2" title="Row 5, Cell 2"></a>
<a href="#r5-c3" title="Row 5, Cell 3"></a>
<a href="#r5-c4" title="Row 5, Cell 4"></a>
<a href="#r5-c5" title="Row 5, Cell 5"></a>
</nav>
body {
font-family: 'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif;
font-weight: normal;
height: 100dvh;
margin: 0;
scrollbar-color: #0003 #0001;
scrollbar-width: thin;
width: 100vw;
}
.ui-scroll-grid {
container-type: inline-size;
height: 100dvh;
list-style-type: none;
margin: 0;
overflow: clip auto;
padding: 0;
scroll-behavior: smooth;
scroll-snap-type: y mandatory;
width: 100vw;
ol {
display: flex;
list-style-type: none;
overflow: auto clip;
padding: 0;
scroll-behavior: smooth;
scroll-snap-type: x mandatory;
}
li {
background: var(--bg);
box-sizing: border-box;
color: #FFFD;
display: grid;
flex: 0 0 calc(100vw - 1rem);
font-size: 7.5cqi;
height: 100dvh;
padding-inline: 1rem;
place-content: center;
scroll-snap-align: start;
text-align: center;
text-wrap: balance;
}
& > li {
&:nth-of-type(1) { --bg: #123; }
&:nth-of-type(2) { --bg: #187; }
&:nth-of-type(3) { --bg: #C90; }
&:nth-of-type(4) { --bg: #D32; }
&:nth-of-type(5) { --bg: #594; }
}
}
.ui-scroll-grid-nav {
bottom: 2rem;
display: grid;
gap: .25rem;
grid-template-columns: repeat(5, 1fr);
position: fixed;
right: 2rem;
a {
aspect-ratio: 1;
background-color: #FFFD;
border-radius: 50%;
display: block;
width: .5rem;
&.active {
background-color: #0EF;
}
}
}
function handleNavigation(links, lists, activeClass = 'active') {
if (!links?.length || !lists?.length) return;
let linkClicked = false;
let currentRow = 0;
let currentCol = 0;
const syncScroll = target => {
const parent = target.parentNode;
lists.forEach(ol => ol !== parent && (ol.scrollLeft = parent.scrollLeft));
};
const navigateToCell = (row, col) => {
const targetId = `r${row + 1}-c${col + 1}`;
const target = document.getElementById(targetId);
const link = [...links].find(link => link.hash === `#${targetId}`);
if (target && link) {
linkClicked = true;
target.scrollIntoView({ behavior: 'smooth' });
links.forEach(l => l.classList.remove(activeClass));
link.classList.add(activeClass);
requestAnimationFrame(() => {
setTimeout(() => {
syncScroll(target);
linkClicked = false;
}, 1000);
});
}
};
const handleKeydown = (e) => {
switch (e.key) {
case 'ArrowLeft':
currentCol = Math.max(0, currentCol - 1);
break;
case 'ArrowRight':
currentCol = Math.min(4, currentCol + 1);
break;
case 'ArrowUp':
currentRow = Math.max(0, currentRow - 1);
break;
case 'ArrowDown':
currentRow = Math.min(4, currentRow + 1);
break;
default:
return;
}
e.preventDefault();
navigateToCell(currentRow, currentCol);
};
const IO = new IntersectionObserver(entries =>
entries.forEach(({ isIntersecting, intersectionRatio, target }) => {
if (isIntersecting && intersectionRatio >= 0.5) {
links.forEach(link => link.classList.remove(activeClass));
const link = [...links].find(link => link.hash === `#${target.id}`);
link?.classList.add(activeClass);
// Update current position when scrolling
const [_, row, col] = target.id.match(/r(\d+)-c(\d+)/);
currentRow = parseInt(row) - 1;
currentCol = parseInt(col) - 1;
if (!linkClicked) syncScroll(target);
}
}), {
threshold: [0, 0.5, 1.0]
}
);
links.forEach(link => {
const target = document.getElementById(link.hash.slice(1));
if (!target) return;
IO.observe(target);
link.addEventListener('click', e => {
e.preventDefault();
const [_, row, col] = target.id.match(/r(\d+)-c(\d+)/);
currentRow = parseInt(row) - 1;
currentCol = parseInt(col) - 1;
navigateToCell(currentRow, currentCol);
});
});
// Add keyboard event listener
document.addEventListener('keydown', handleKeydown);
}
/* Initialize navigation with links and lists from DOM */
handleNavigation(
document.querySelectorAll('.ui-scroll-grid-nav a'),
document.querySelectorAll('.ui-scroll-grid ol')
);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.