Something went wrong. Try again.
My website. cameron.stream
Something went wrong. Try again.
16 kB · 201 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202const SPECIES = [ { id: "scraper", code: "SC", name: "Scraper", note: "Creeps slowly over wet floors, grazing the film as it goes, and leaves clean trails through it. Hides in shells, hollows, and walled homes, and sleeps in a pod up in a structure." }, { id: "tab", code: "TB", name: "Tab", note: "Schools in open water, turning as one, and makes its home circling a structure, darting in among its frames when startled. Follows the plankton when hungry. Ranges out over the flooded shore at high water and swims home to its pool as the tide turns." }, { id: "pylon", code: "PY", name: "Pylon", note: "Stands still, filters the water, and catches tabs. Buds settle at the foot of structures." }, { id: "collector", code: "CL", name: "Collector", note: "Salvages husks and hoards brass in a corner, or up on a structure's deck." }, { id: "mason", code: "MS", name: "Mason", note: "Fills. Lifts spoil and stones and builds walled homes, filling in the hollow beneath first." }, { id: "breaker", code: "BR", name: "Breaker", note: "Hunts scrapers. Breaks walls down to reach them, and tears at a structure's footing to bring a pod down." }, { id: "borer", code: "BO", name: "Borer", note: "Digs. Sinks a burrow, then tunnels outward toward other water and banks the spoil beside it." }, { id: "artificer", code: "AR", name: "Artificer", note: "Builds from brass and salvage: structures in the water, one small part at a time, and sluice gates and beacons. Each has its own way of building, which its children inherit. Mends parts that breakers and storms work loose." },];
// Two swells across twice the ring's width, so sliding it along by half repeats seamlessly.const WAVE = "M0 10C16.5 6 33.5 6 50 10S83.5 14 100 10S133.5 6 150 10S183.5 14 200 10";
// The keys, as the settings panel lists them.const KEYS = [ ["W A S D", "Glide the view. Hold Shift to go faster."], ["Q E", "Turn the view."], ["R F", "Zoom in and out. Plus and minus work too."], ["Z X", "Choose what a tap drops."], ["Space", "Drop it where you point, or follow the machine there in Look mode. Enter works too."], ["Arrows", "Move the marker. With Shift, pan."], ["[ ]", "Step between machines."], ["1–9", "Go to a pool."], ["Esc", "Let go of the machine you follow."], ["?", "Open or close this panel."],];
const TOOLS = [ { id: "look", label: "Look" }, { id: "brass", label: "Brass" }, { id: "shell", label: "Shell" }, { id: "pebble", label: "Pebble" }, { id: "feed", label: "Feed" },];
export function TidePoolContent() { return ( <main class="tide-pool" data-tide-pool data-veiled="loading" data-map="off"> <header class="tide-heading"> <a class="tide-exit" href="/other">← Other</a> <h1>Tide pool</h1> <p>One pool on a tidal shelf. Everything else is dug.</p> </header> <p class="tide-mark" aria-hidden="true">Tide pool</p>
<figure class="tide-figure"> <div class="tide-stage" id="tide-stage"> <canvas id="tide-canvas" hidden tabindex={0} role="application" aria-roledescription="interactive scene" aria-label="Tide pool. Drag to pan, scroll or pinch to zoom, right-drag or twist to turn, double-tap to zoom in. Tap a machine to follow it, or tap water to drop the selected object. Press question mark for keys." aria-describedby="tide-instructions">A dark tidal shelf that the machines living on it dig, fill, and wall as the tide rises and falls.</canvas> {/* While the pool loads, and if the view is ever lost: the ring fills like a pool, then the view opens out of it. */} <div class="tide-veil" id="tide-veil"> <div class="tide-veil-ring" aria-hidden="true"> <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="49" stroke-width="2"/><circle cx="50" cy="50" r="36.85" stroke-width="7.7"/></svg> <div class="tide-veil-water"><div class="tide-veil-level"><div class="tide-veil-bob"><div class="tide-veil-swell"> <svg viewBox="0 0 200 100" preserveAspectRatio="none"> <path class="tide-veil-sea" d={`${WAVE}V100H0Z`}/> <path class="tide-veil-crest" d={WAVE}/> </svg> </div></div></div></div> </div> <p class="tide-veil-note" id="tide-veil-note" aria-hidden="true">Filling the pool</p> <noscript> <style dangerouslySetInnerHTML={{ __html: "#tide-veil-note { display: none; } .tide-veil-bob, .tide-veil-swell { animation: none; }" }}/> <p class="tide-veil-note">The pool needs JavaScript to fill</p> </noscript> </div> </div> </figure>
<aside class="tide-hud" aria-label="Pool readout"> <dl class="tide-clock"> <div><dt>Time</dt><dd id="tide-time">07:40</dd></div> <div><dt>Tide</dt><dd id="tide-level">Mid</dd></div> <div><dt>Pool</dt><dd id="tide-basin">III</dd></div> </dl> <canvas class="tide-map" id="tide-map" aria-hidden="true" title="The whole shelf. Tap or drag to go there."/> <details class="tide-census"> <summary id="tide-census-title">Census</summary> <canvas id="tide-chart" width="240" height="56" aria-hidden="true"/> <ul> {SPECIES.map(s => ( <li> <button type="button" data-species={s.id} disabled title={s.note}> <span class="tide-code">{s.code}</span> <span class="tide-name">{s.name}</span> <span class="tide-count" data-count>–</span> </button> </li> ))} </ul> </details> </aside>
<section class="tide-inspect" id="tide-inspect" hidden aria-labelledby="tide-inspect-title"> <header> <p class="tide-kicker" id="tide-inspect-kind">Specimen</p> <h2 id="tide-inspect-title">—</h2> <button type="button" id="tide-inspect-close" aria-label="Close">×</button> </header> <dl class="tide-facts"> <div><dt>Generation</dt><dd id="tide-gen">—</dd></div> <div><dt>Age</dt><dd id="tide-age">—</dd></div> <div><dt>Pool</dt><dd id="tide-where">—</dd></div> <div><dt>Lineage</dt><dd id="tide-lineage">—</dd></div> </dl> <div class="tide-meter" aria-hidden="true"><span id="tide-energy-bar"/></div> <p class="tide-energy" id="tide-energy">Energy —</p> <p class="tide-goal" id="tide-goal">—</p> <p class="tide-carrying" id="tide-carrying"/> <ol class="tide-log" id="tide-log" aria-label="Recent activity"/> <div class="tide-inspect-actions"> <button type="button" id="tide-follow" aria-pressed="true">Following</button> <button type="button" id="tide-release">Release</button> </div> </section>
<div class="tide-controls" aria-label="Tide pool controls"> <p class="tide-hint" id="tide-instructions">Tap the water to drop brass.<span> Drag to look around. Tap a machine to follow it.</span></p> <div class="tide-tools" role="group" aria-label="What a tap does"> {TOOLS.map(t => ( <button type="button" data-tool={t.id} aria-pressed={t.id === "brass" ? "true" : "false"} disabled> <span class={`tide-token tide-${t.id}`} aria-hidden="true"/>{t.label} </button> ))} </div> </div> <div class="tide-nav" aria-label="Navigation"> <div class="tide-machines" role="group" aria-label="Machines"> <button type="button" id="tide-machine-prev" disabled aria-label="Previous machine" title="Previous machine ([)">‹</button> <span aria-hidden="true">Machines</span> <button type="button" id="tide-machine-next" disabled aria-label="Next machine" title="Next machine (])">›</button> </div> <div class="tide-basins" role="group" aria-label="Go to pool"> {/* Filled in by the page with the pools that exist right now. */} </div> <button type="button" class="tide-compass" id="tide-compass" disabled aria-label="Turn the view back" title="Turn the view back (Q and E turn it)"> <svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="8.5"/><path d="M10 3.5 12.4 10H7.6Z"/><path class="tide-compass-tail" d="M7.6 10h4.8L10 16.5Z"/></svg> </button> <div class="tide-zoom" role="group" aria-label="Zoom"> <button type="button" id="tide-zoom-out" disabled aria-label="Zoom out">−</button> <button type="button" id="tide-zoom-in" disabled aria-label="Zoom in">+</button> </div> </div> <div class="tide-utility" aria-label="Playback"> <button type="button" id="tide-pause" aria-pressed="false" disabled>Pause</button> <button type="button" id="tide-sound" aria-pressed="false" disabled>Sound off</button> <button type="button" id="tide-reset" disabled>Begin again</button> <button type="button" id="tide-settings-toggle" aria-expanded="false" aria-controls="tide-settings" disabled>Settings</button> </div> <section class="tide-settings" id="tide-settings" hidden aria-labelledby="tide-settings-title"> <header> <h2 class="tide-kicker" id="tide-settings-title">Settings</h2> <button type="button" id="tide-settings-close" aria-label="Close settings">×</button> </header> <label class="tide-option"> <input type="checkbox" data-option="wander"/> <span><b>Wander when idle</b>Left alone, the view drifts to whatever is happening.</span> </label> <label class="tide-option"> <input type="checkbox" data-option="edge"/> <span><b>Edge scrolling</b>A mouse resting at the edge of the view moves it that way.</span> </label> <label class="tide-option"> <input type="checkbox" data-option="map"/> <span><b>Map</b>The whole shelf in the corner. Tap or drag on it to go there.</span> </label> <div class="tide-keys"> <h3 class="tide-kicker">Keys</h3> <dl> {KEYS.map(([k, v]) => <div><dt>{k}</dt><dd>{v}</dd></div>)} </dl> <p>With a mouse, right-drag or Option-drag to turn. Double-click to zoom in on a spot.</p> </div> </section> <ol class="tide-ticker" id="tide-ticker" aria-hidden="true"/> {/* While the view wanders on its own, a line about what it has found. */} <p class="tide-caption" id="tide-caption" aria-hidden="true"/> <p class="tide-status" id="tide-status" role="status">Preparing the pool.</p> <details class="tide-notes"> <summary>Inside the pool</summary> <p>Everything here is one surface: a rocky shore that the tide floods and drains every four minutes. The water is simulated: as the tide rises the sea comes in over the rock, up the gullies first, and pours over the lip of the pool; as it falls it drains away again. A swell rolls in off the sea all the while, and its waves break into foam on the rocks, bigger in a storm. The surface is simulated too, a few centimetres at a time: every drop, swimmer and gust of wind raises ripples that spread, interfere, and bounce off the rock, and the sunlight they bend gathers into the bright, shifting net on the floor of the pool, while the floor itself, seen through them, sways as they pass. Loose sand lies in the hollows and gullies and wherever the borers heap their spoil; fast water lifts it and slow water lets it fall, so the gullies scour, spoil heaps wash out, and a storm rearranges the shore. The tide's reach swells and shrinks with the moon, which waxes and wanes over forty minutes: springs come at full and new moon, so some low waters lay the shore bare, and the rock stays dark and glistening where the water has just left it. Full-moon nights are silver; on moonless nights the plankton, the stars in still water, and the lit windows show best. Wherever the ground dips and cannot drain as the tide falls, water stays behind, and that is a pool. The shelf you arrive on has been going for a few tides: the borers have dug, an artificer has put up a structure in the first pool, and the others arrive in their time. Borers dig burrows and tunnels, masons fill hollows and raise walls, and breakers knock walls down, so the pools grow, join, drain, and form on their own. Pools are named as they appear. Artificers build structures in the water out of small parts, one at a time, each chosen from what is already around it: frames that bear load, solid plates, decks that reach out over the water, pods to live in, gears and tide wheels the tide turns, pipes, and lamps. Nothing is planned beyond the next part, so no two structures come out alike. A structure's footing is solid: the water goes around it, and the ground under it holds. Scrapers sleep in the pods, collectors keep hoards on the decks, and the tabs circle below and dart in among the frames when something startles them. Breakers work parts loose at the footing and artificers make them good again; when a footing gives way, everything it held falls. A tide wheel on a structure turns only while the water runs through it and stores its charge; the structure's lamps and windows burn at night only with a charged wheel nearby. Artificers also build working machines: a sluice gate on a pool's outlet shuts as the tide falls and holds the pool full, and a beacon, powered by a wheel, burns at night, feeds the plankton around it, and draws the tabs. Now and then a storm comes in off the sea: the water rises and runs rough, the waves batter whatever stands at the waterline, and the fish hide among the frames. Day turns to night every seven minutes, and the film on each floor grows only in wet light. At night the plankton glows wherever something moves through it.</p> <ul class="tide-species"> {SPECIES.map(s => <li><b>{s.code} {s.name}.</b> {s.note}</li>)} </ul> <p>Large machines need a brass core to cast a new body. When they stop, they leave a husk and give their core back. Brass, shells, and pebbles also wash in from the sea at high tide. Collectors hoard it, and others steal it.</p> <p>Tap a machine to follow it. Choose a material, then tap water to drop it. Feed makes the film and plankton bloom where it lands. After dark every machine's lamp is lit, so the life on the shelf shows as small moving lights, and a ball of light floats wherever you point, over rock or water, lighting what is around it and casting shadows; the fish come to it, and the plankton sparks behind it as you move it over the water. Drag to look around, pinch or scroll to zoom, and twist two fingers or right-drag to turn the view; the compass turns it back. Double-tap to zoom in on a spot. Leave it alone and the controls fade away and the view drifts on its own toward whatever is happening, unless you turn that off in Settings; any touch brings them back. Every visit grows its own shelf; its number is in the address, so you can send someone yours, and Begin again grows another. A purely synthetic, procedural artwork. Sound is optional and starts only when you turn it on. Your settings stay in this browser; nothing else you do here is saved, and nothing is sent.</p> <p>Keyboard: the keys work anywhere on the page. Hold W, A, S, or D to glide the view, Q or E to turn it, and R or F to zoom, with Shift to go faster. Z and X choose what to drop, and Space or Enter drops it where you point. Arrow keys move the marker; Shift and arrows pan. Plus and minus zoom. Brackets step between machines. Escape releases. Keys 1 to 9 jump to a pool. Question mark lists the keys. Reduced-motion settings start the world paused; you can choose to play.</p> <button type="button" id="tide-describe" disabled>Describe this moment</button> <p id="tide-description" role="status"></p> <p>Rendered with Three.js. <a href="/public/tide-pool-three-license.txt">Software license</a>.</p> </details> <script type="module" src="/public/tide-pool.js?v=87"></script> </main> );}
export async function TidePool() { return <TidePoolContent />;}