Refresh app shell and site assets
21
README.md
|
|
@ -1,15 +1,14 @@
|
||||||
# Just a bunch of blobs
|
# Fleeting Garden
|
||||||
|
|
||||||
[](https://github.com/schmelczer/webgpu/actions/workflows/deploy.yml)
|
Fleeting Garden is a single-player WebGPU drawing garden. Pick a vibe palette,
|
||||||
|
draw persistent coloured paths, spawn agents from those strokes, erase locally,
|
||||||
|
and export the scene as an internal render buffer snapshot.
|
||||||
|
|
||||||
## todo
|
Check out the [agent logic](./src/pipelines/agents/agent.wgsl).
|
||||||
|
|
||||||
- add info page description
|
## Testing
|
||||||
- add share link
|
|
||||||
- settings page
|
|
||||||
add reset link
|
|
||||||
- shareable settings
|
|
||||||
- graceful error messages when no support
|
|
||||||
- fix up generation id automatically
|
|
||||||
|
|
||||||
Check out the [agent's logic](./src/pipelines/agents/agent.wgsl).
|
- `npm test` runs the Vitest unit suite.
|
||||||
|
- `npm run test:e2e` runs the Playwright Chromium smoke test. The Playwright
|
||||||
|
config builds the production bundle before serving it.
|
||||||
|
- `npx playwright install chromium` installs the local browser binary when needed.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M3 21v-4a4 4 0 1 1 4 4h-4" />
|
|
||||||
<path d="M21 3a16 16 0 0 0 -12.8 10.2" />
|
|
||||||
<path d="M21 3a16 16 0 0 1 -10.2 12.8" />
|
|
||||||
<path d="M10.6 9a9 9 0 0 1 4.4 4.4" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 332 B |
10
assets/icons/download.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M12 3v11m0 0 4-4m-4 4-4-4M5 17v3h14v-3"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 248 B |
|
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="#FFFFFF" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<circle cx="12" cy="12" r="9" />
|
<circle cx="12" cy="12" r="9" />
|
||||||
<line x1="12" y1="8" x2="12.01" y2="8" />
|
<line x1="12" y1="8" x2="12.01" y2="8" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 349 B |
|
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="#FFFFFF" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M4 8v-2a2 2 0 0 1 2 -2h2" />
|
<path d="M4 8v-2a2 2 0 0 1 2 -2h2" />
|
||||||
<path d="M4 16v2a2 2 0 0 0 2 2h2" />
|
<path d="M4 16v2a2 2 0 0 0 2 2h2" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 382 B |
|
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="#FFFFFF" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M15 19v-2a2 2 0 0 1 2 -2h2" />
|
<path d="M15 19v-2a2 2 0 0 1 2 -2h2" />
|
||||||
<path d="M15 5v2a2 2 0 0 0 2 2h2" />
|
<path d="M15 5v2a2 2 0 0 0 2 2h2" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 382 B |
|
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="#FFFFFF" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" />
|
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" />
|
||||||
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
|
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 331 B |
|
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="#FFFFFF" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
<path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
<path d="M4 6l8 0" />
|
<path d="M4 6l8 0" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 536 B After Width: | Height: | Size: 541 B |
3
assets/icons/sound.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M3 9.25v5.5h4.1L13 20V4L7.1 9.25H3Zm13.3-2.8-1.4 1.4A5.1 5.1 0 0 1 16.5 12a5.1 5.1 0 0 1-1.6 4.15l1.4 1.4A7.1 7.1 0 0 0 18.5 12a7.1 7.1 0 0 0-2.2-5.55Zm2.85-2.85-1.42 1.42A9.55 9.55 0 0 1 20.5 12a9.55 9.55 0 0 1-2.77 6.98l1.42 1.42A11.55 11.55 0 0 0 22.5 12a11.55 11.55 0 0 0-3.35-8.4Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
253
index.html
|
|
@ -6,76 +6,243 @@
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width,initial-scale=1,viewport-fit=cover"
|
content="width=device-width,initial-scale=1,viewport-fit=cover"
|
||||||
/>
|
/>
|
||||||
<meta name="theme-color" content="#b7455e" />
|
<meta name="theme-color" content="#10151f" />
|
||||||
|
<meta name="robots" content="index,follow" />
|
||||||
|
<meta name="author" content="Andras Schmelczer" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="A WebGPU agent simulation: a million blobs leave trails, infect each other across generations, and react to your brush."
|
content="Tend it while you can. The garden returns to weather either way. A WebGPU drawing toy in your browser."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<meta property="og:title" content="Just a bunch of blobs" />
|
<link rel="canonical" href="https://schmelczer.dev/fleeting/" />
|
||||||
|
|
||||||
|
<meta property="og:title" content="Fleeting Garden" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:site_name" content="Fleeting Garden" />
|
||||||
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="A WebGPU agent simulation: a million blobs leave trails, infect each other across generations, and react to your brush."
|
content="Tend it while you can. The garden returns to weather either way. A WebGPU drawing toy in your browser."
|
||||||
/>
|
/>
|
||||||
<meta property="og:url" content="https://schmelczer.dev" />
|
<meta property="og:url" content="https://schmelczer.dev/fleeting/" />
|
||||||
<meta property="og:image" content="https://schmelczer.dev/og-image.jpg" />
|
<meta property="og:image" content="https://schmelczer.dev/fleeting/og-image.jpg" />
|
||||||
<meta property="og:image:width" content="1920" />
|
<meta property="og:image:type" content="image/jpeg" />
|
||||||
<meta property="og:image:height" content="1920" />
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:image:alt" content="Fleeting Garden social preview image." />
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<meta name="twitter:title" content="Fleeting Garden" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" />
|
<meta
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
name="twitter:description"
|
||||||
|
content="Tend it while you can. The garden returns to weather either way. A WebGPU drawing toy in your browser."
|
||||||
|
/>
|
||||||
|
<meta name="twitter:image" content="https://schmelczer.dev/fleeting/og-image.jpg" />
|
||||||
|
<meta name="twitter:image:alt" content="Fleeting Garden social preview image." />
|
||||||
|
|
||||||
<title>Just a bunch of blobs</title>
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebApplication",
|
||||||
|
"name": "Fleeting Garden",
|
||||||
|
"url": "https://schmelczer.dev/fleeting/",
|
||||||
|
"description": "Tend it while you can. The garden returns to weather either way. A WebGPU drawing toy in your browser.",
|
||||||
|
"image": "https://schmelczer.dev/fleeting/og-image.jpg",
|
||||||
|
"applicationCategory": "DesignApplication",
|
||||||
|
"operatingSystem": "Any",
|
||||||
|
"browserRequirements": "Requires a browser with WebGPU support.",
|
||||||
|
"author": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "Andras Schmelczer"
|
||||||
|
},
|
||||||
|
"sameAs": "https://github.com/schmelczer/webgpu"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon-180x180.png" />
|
||||||
|
<link rel="manifest" href="manifest.webmanifest" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Fleeting Garden" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
|
||||||
|
<title>Fleeting Garden</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="is-loading">
|
||||||
<main class="canvas-container">
|
<main class="canvas-container">
|
||||||
<canvas></canvas>
|
<canvas
|
||||||
|
role="img"
|
||||||
|
aria-label="Interactive generative garden canvas"
|
||||||
|
aria-describedby="canvas-description"
|
||||||
|
>
|
||||||
|
Your browser cannot display the interactive WebGPU garden canvas. Use a browser
|
||||||
|
with WebGPU support to draw coloured paths and watch the garden grow.
|
||||||
|
</canvas>
|
||||||
|
<p id="canvas-description" class="visually-hidden">
|
||||||
|
Fleeting Garden is a pointer-driven WebGPU drawing canvas. Drag or touch the scene
|
||||||
|
to paint coloured paths, then use the toolbar to change colours, erase, export,
|
||||||
|
adjust the config overlay, restart, or open more information.
|
||||||
|
</p>
|
||||||
|
<div class="garden-grain" aria-hidden="true"></div>
|
||||||
|
<div class="eraser-preview" aria-hidden="true"></div>
|
||||||
|
<div class="garden-prompt" aria-live="polite"></div>
|
||||||
|
|
||||||
|
<div class="loading-indicator" role="status">
|
||||||
|
<div class="splash" data-visible="true">
|
||||||
|
<h1 class="splash-title">Fleeting Garden</h1>
|
||||||
|
<p class="splash-description">
|
||||||
|
Tend it while you can. The garden returns to weather either way.
|
||||||
|
</p>
|
||||||
|
<button class="start-button" type="button" disabled>Start</button>
|
||||||
|
</div>
|
||||||
|
<div class="loading-bar" data-visible="false" aria-hidden="true" inert>
|
||||||
|
<div class="loading-status">Starting up…</div>
|
||||||
|
<div
|
||||||
|
class="loading-progress"
|
||||||
|
role="progressbar"
|
||||||
|
aria-label="Loading Fleeting Garden"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
aria-valuenow="0"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="errors-container">
|
<section class="errors-container">
|
||||||
<noscript>JavaScript is required for this website.</noscript>
|
<noscript>JavaScript is required for this website.</noscript>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<aside>
|
<aside class="control-dock">
|
||||||
<nav class="buttons">
|
<section
|
||||||
<button class="info" aria-label="About"></button>
|
id="info-panel"
|
||||||
<button class="maximize-full-screen" aria-label="Enter fullscreen"></button>
|
class="hidden info-page"
|
||||||
<button class="minimize-full-screen" aria-label="Exit fullscreen"></button>
|
role="region"
|
||||||
<button class="settings" aria-label="Settings"></button>
|
aria-label="About panel"
|
||||||
<button class="restart" aria-label="Restart simulation"></button>
|
aria-hidden="true"
|
||||||
</nav>
|
tabindex="-1"
|
||||||
|
inert
|
||||||
<main class="pages hidden info-page">
|
>
|
||||||
<section>
|
<section>
|
||||||
<h1>Just a bunch of blobs</h1>
|
<h1>Fleeting Garden</h1>
|
||||||
<p>
|
<p>
|
||||||
A million autonomous agents wander a 2D field. Each one lays down a faint
|
A garden is what we tend; the wild is what we get the moment we look away.
|
||||||
trail and follows trails it senses ahead. Two generations are competing for
|
Both happen here at once. Your strokes plant colour, small agents follow them,
|
||||||
territory: the older one fades, the newer one spreads.
|
branch off, and slowly rewrite the patch you laid down into something you
|
||||||
|
didn't quite plan.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Drag your finger or mouse anywhere on the canvas to paint a wall. Walls slow
|
Three swatches plant the line. The eraser carves a clearing. The mirror folds
|
||||||
the new generation down and let the old one breathe a little longer. Open
|
one gesture into many, like footpaths around a hidden well.
|
||||||
<em>Settings</em> to retune sensors, decay rates and aggression.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Runs entirely on your GPU via WebGPU compute shaders — no servers, no
|
Switch vibes to change the season; your shapes stay, the light moves. Add or
|
||||||
tracking, no analytics. Source on
|
quiet the piano. Restart when you want a fresh field. Take a snapshot if you
|
||||||
<a href="https://github.com/schmelczer/webgpu" target="_blank" rel="noopener"
|
want to keep one particular instant of weather.
|
||||||
>GitHub</a
|
</p>
|
||||||
|
<p>
|
||||||
|
Built with WebGPU, running locally in your browser. More of my work at
|
||||||
|
<a href="https://schmelczer.dev" target="_blank" rel="noopener"
|
||||||
|
>schmelczer.dev</a
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</section>
|
||||||
|
|
||||||
<main class="pages hidden settings-page">
|
<div class="toolbar-row" role="toolbar" aria-label="Garden toolbar">
|
||||||
<section>
|
<button
|
||||||
<div class="settings-content"></div>
|
class="previous-vibe vibe-button"
|
||||||
<button id="apply-defaults" class="large-button">Apply defaults</button>
|
aria-label="Previous vibe"
|
||||||
</section>
|
title="Previous vibe"
|
||||||
</main>
|
>
|
||||||
|
‹
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="toolbar-shell">
|
||||||
|
<section class="garden-controls" aria-label="Garden controls">
|
||||||
|
<div class="swatches" role="group" aria-label="Drawing colours">
|
||||||
|
<button
|
||||||
|
class="color-swatch"
|
||||||
|
aria-label="Draw colour 1"
|
||||||
|
title="Draw colour 1"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
class="color-swatch"
|
||||||
|
aria-label="Draw colour 2"
|
||||||
|
title="Draw colour 2"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
class="color-swatch"
|
||||||
|
aria-label="Draw colour 3"
|
||||||
|
title="Draw colour 3"
|
||||||
|
></button>
|
||||||
|
<label class="eraser-size-control" title="Erase and resize">
|
||||||
|
<input class="eraser-size-slider" type="range" aria-label="Eraser size" />
|
||||||
|
</label>
|
||||||
|
<label class="mirror-segment-control" title="Mirror off">
|
||||||
|
<input
|
||||||
|
class="mirror-segment-slider"
|
||||||
|
type="range"
|
||||||
|
aria-label="Mirror segments"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="buttons" aria-label="App controls">
|
||||||
|
<button
|
||||||
|
class="info"
|
||||||
|
data-control="info"
|
||||||
|
aria-label="About"
|
||||||
|
aria-controls="info-panel"
|
||||||
|
aria-expanded="false"
|
||||||
|
title="About"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
class="full-screen-toggle"
|
||||||
|
data-control="full-screen"
|
||||||
|
aria-label="Enter fullscreen"
|
||||||
|
title="Enter fullscreen"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
class="settings"
|
||||||
|
data-control="settings"
|
||||||
|
aria-label="Show config overlay"
|
||||||
|
aria-expanded="false"
|
||||||
|
title="Show config overlay"
|
||||||
|
></button>
|
||||||
|
<div class="audio-control">
|
||||||
|
<button
|
||||||
|
class="sound"
|
||||||
|
data-control="sound"
|
||||||
|
aria-label="Mute audio"
|
||||||
|
aria-pressed="false"
|
||||||
|
title="Mute audio"
|
||||||
|
></button>
|
||||||
|
<label class="volume-control" title="Master volume">
|
||||||
|
<input class="volume-slider" type="range" aria-label="Master volume" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="export-4k"
|
||||||
|
data-control="export"
|
||||||
|
aria-label="Download internal buffer snapshot"
|
||||||
|
title="Download internal buffer snapshot"
|
||||||
|
></button>
|
||||||
|
<span class="export-status" aria-live="polite"></span>
|
||||||
|
<button
|
||||||
|
class="restart"
|
||||||
|
data-control="restart"
|
||||||
|
aria-label="Restart simulation"
|
||||||
|
title="Restart simulation"
|
||||||
|
></button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<button class="next-vibe vibe-button" aria-label="Next vibe" title="Next vibe">
|
||||||
|
›
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<script type="module" src="/src/index.ts"></script>
|
<script type="module" src="/src/index.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
|
|
||||||
<title>Not found</title>
|
|
||||||
<meta name="theme-color" content="#b7455e" />
|
|
||||||
<meta name="viewport" content="initial-scale=1.0" />
|
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #b7455e;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
a {
|
|
||||||
font-family: 'Roboto', 'Helvetica Neue', sans-serif;
|
|
||||||
font-weight: 100;
|
|
||||||
font-size: 3rem;
|
|
||||||
color: white;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<h1>Page not found.</h1>
|
|
||||||
<a href="/">Go back</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 908 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 892 B |
|
|
@ -1,6 +1,31 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
<rect width="64" height="64" rx="14" fill="#b7455e" />
|
<defs>
|
||||||
<circle cx="22" cy="26" r="9" fill="#fff" opacity="0.95" />
|
<clipPath id="icon-clip">
|
||||||
<circle cx="42" cy="32" r="11" fill="#fff" opacity="0.85" />
|
<rect width="64" height="64" rx="14" />
|
||||||
<circle cx="28" cy="44" r="7" fill="#fff" opacity="0.75" />
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<g clip-path="url(#icon-clip)">
|
||||||
|
<rect width="64" height="64" fill="#10151f" />
|
||||||
|
<path d="M0 64a32 32 0 0 1 64 0Z" fill="#40d6c8" />
|
||||||
|
<path
|
||||||
|
d="M32 34c1.2-7.2 4.8-12.3 10-16"
|
||||||
|
fill="none"
|
||||||
|
stroke="#10151f"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="8"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M32 34c1.2-7.2 4.8-12.3 10-16"
|
||||||
|
fill="none"
|
||||||
|
stroke="#ff5da2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="4"
|
||||||
|
/>
|
||||||
|
<ellipse cx="42" cy="11.5" rx="4.2" ry="6.4" fill="#ff5da2" />
|
||||||
|
<ellipse cx="48.5" cy="18" rx="6.4" ry="4.2" fill="#ff5da2" />
|
||||||
|
<ellipse cx="42" cy="24.5" rx="4.2" ry="6.4" fill="#ff5da2" />
|
||||||
|
<ellipse cx="35.5" cy="18" rx="6.4" ry="4.2" fill="#ff5da2" />
|
||||||
|
<circle cx="42" cy="18" r="3.2" fill="#10151f" />
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 950 B |
|
|
@ -1,38 +1,35 @@
|
||||||
{
|
{
|
||||||
"name": "Just a bunch of blobs",
|
"name": "Fleeting Garden",
|
||||||
"short_name": "Blobs",
|
"short_name": "Garden",
|
||||||
"description": "A WebGPU agent simulation: a million blobs leave trails, infect each other across generations, and react to your brush.",
|
"description": "Tend it while you can. The garden returns to weather either way. A WebGPU drawing toy in your browser.",
|
||||||
"start_url": "/",
|
"start_url": "./",
|
||||||
"scope": "/",
|
"scope": "./",
|
||||||
"display": "fullscreen",
|
"display": "fullscreen",
|
||||||
"display_override": ["fullscreen", "standalone", "minimal-ui"],
|
"background_color": "#10151f",
|
||||||
"orientation": "any",
|
"theme_color": "#10151f",
|
||||||
"background_color": "#b7455e",
|
|
||||||
"theme_color": "#b7455e",
|
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/favicon.svg",
|
"src": "favicon.svg",
|
||||||
"sizes": "any",
|
"sizes": "any",
|
||||||
"type": "image/svg+xml",
|
"type": "image/svg+xml"
|
||||||
"purpose": "any"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/pwa-64x64.png",
|
"src": "pwa-64x64.png",
|
||||||
"sizes": "64x64",
|
"sizes": "64x64",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/pwa-192x192.png",
|
"src": "pwa-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/pwa-512x512.png",
|
"src": "pwa-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/maskable-icon-512x512.png",
|
"src": "maskable-icon-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "maskable"
|
"purpose": "maskable"
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
public/og-image.jpg
Normal file
|
After Width: | Height: | Size: 301 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 690 B |
|
|
@ -1,2 +1,4 @@
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://schmelczer.dev/fleeting/sitemap.xml
|
||||||
|
|
|
||||||
6
public/sitemap.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>https://schmelczer.dev/fleeting/</loc>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
68
src/analytics.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import {
|
||||||
|
init as plausibleInit,
|
||||||
|
track as plausibleTrack,
|
||||||
|
type PlausibleEventOptions,
|
||||||
|
} from '@plausible-analytics/tracker';
|
||||||
|
|
||||||
|
import { appConfig } from './config';
|
||||||
|
import type { VibeId } from './vibes';
|
||||||
|
|
||||||
|
let isInitialized = false;
|
||||||
|
|
||||||
|
const track = (eventName: string, options: PlausibleEventOptions = {}) => {
|
||||||
|
try {
|
||||||
|
plausibleTrack(eventName, options);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Could not track analytics event "${eventName}".`, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initAnalytics = () => {
|
||||||
|
if (isInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
plausibleInit({
|
||||||
|
domain: appConfig.analytics.domain,
|
||||||
|
endpoint: appConfig.analytics.endpoint,
|
||||||
|
autoCapturePageviews: appConfig.analytics.autoCapturePageviews,
|
||||||
|
logging: appConfig.analytics.logging,
|
||||||
|
});
|
||||||
|
isInitialized = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Could not initialize analytics.', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const trackVibeChange = ({
|
||||||
|
vibeId,
|
||||||
|
vibeName,
|
||||||
|
source,
|
||||||
|
}: {
|
||||||
|
vibeId: VibeId;
|
||||||
|
vibeName: string;
|
||||||
|
source: string;
|
||||||
|
}) => {
|
||||||
|
track('Vibe Change', {
|
||||||
|
props: {
|
||||||
|
vibeId,
|
||||||
|
vibeName,
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const trackStart = () => {
|
||||||
|
track('Start');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const trackExport = ({ vibeId }: { vibeId: VibeId }) => {
|
||||||
|
track('Export', {
|
||||||
|
props: {
|
||||||
|
format: 'png',
|
||||||
|
resolution: 'internal-buffer',
|
||||||
|
vibeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||