Files
yawn/server/web/assets/sculpt/index.html
T
Ampandheaust 347b676cf1 Add core-powered web sculpting studio with tablet controls
Add pressure-aware worker sculpting, bounded shared input, project files and history, and compact icon navigation with an XYZ orbit gizmo.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b4b4-202c-73f1-984d-9a55c88f9c83
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
2026-09-28 08:53:50 +00:00

473 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,viewport-fit=cover"
/>
<meta name="theme-color" content="#20242b" />
<meta name="description" content="A quiet space to sculpt with your pen." />
<title>Sculpt — Yawn</title>
<link
rel="stylesheet"
href="/assets/sculpt/vendor/basecoat/basecoat.min.css"
/>
<link rel="stylesheet" href="/assets/sculpt/styles.css" />
<script type="importmap">
{ "imports": { "@yawn/core": "/pkg/core.js" } }
</script>
<script type="module" src="/assets/sculpt/app.js"></script>
</head>
<body>
<main id="viewport" tabindex="0" aria-label="Sculpting canvas">
<canvas aria-label="Your sculpt"></canvas>
<div id="cursor" hidden aria-hidden="true"><i></i></div>
<div id="loading" class="canvas-message" role="status">
<h1>Preparing your clay.</h1>
<p>A moment to get ready.</p>
</div>
<div id="error" class="canvas-message" role="alert" hidden>
<h1>Your sculpt is safe.</h1>
<p id="error-detail"></p>
<button id="retry" class="btn">Try again</button>
</div>
</main>
<nav class="tool-rail" aria-label="Sculpt tools">
<div class="brushes" role="group" aria-label="Brush">
<button
class="btn icon-button"
data-variant="ghost"
data-brush="draw"
aria-pressed="true"
aria-label="Draw"
title="Draw · 1"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m14 4 6 6M5 15 16 4a2.8 2.8 0 0 1 4 4L9 19l-5 1z" />
</svg>
</button>
<button
class="btn icon-button"
data-variant="ghost"
data-brush="inflate"
aria-pressed="false"
aria-label="Inflate"
title="Inflate · 2"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="8" />
<path d="M12 7v10M7 12h10" />
</svg>
</button>
<button
class="btn icon-button"
data-variant="ghost"
data-brush="smooth"
aria-pressed="false"
aria-label="Smooth"
title="Smooth · 3"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M3 8c6-7 12 7 18 0M3 12c6-7 12 7 18 0M3 16c6-7 12 7 18 0"
/>
</svg>
</button>
<button
class="btn icon-button"
data-variant="ghost"
data-brush="flatten"
aria-pressed="false"
aria-label="Flatten"
title="Flatten · 4"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m3 16 5-7h13l-5 7zM5 20h14M12 2v4m-2-2 2 2 2-2" />
</svg>
</button>
<button
class="btn icon-button"
data-variant="ghost"
data-brush="grab"
aria-pressed="false"
aria-label="Grab"
title="Grab · 5"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M8 12V5a2 2 0 0 1 4 0v7-8a2 2 0 0 1 4 0v8-5a2 2 0 0 1 4 0v9c0 4-3 6-7 6-3 0-5-2-6-4l-3-5c-1-2 2-3 3-1l1 1"
/>
</svg>
</button>
</div>
<div class="rail-group">
<button
id="tools-toggle"
class="btn icon-button launcher"
data-variant="ghost"
aria-label="Brush settings"
title="Brush settings"
aria-expanded="false"
aria-controls="tools-panel"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 7h16M4 17h16M8 4v6M16 14v6" />
</svg>
</button>
<button
id="undo"
class="btn icon-button"
data-variant="ghost"
aria-label="Undo"
title="Undo"
disabled
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m8 4-5 5 5 5M3 9h10a7 7 0 0 1 0 14" />
</svg>
</button>
<button
id="redo"
class="btn icon-button"
data-variant="ghost"
aria-label="Redo"
title="Redo"
disabled
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m16 4 5 5-5 5M21 9H11a7 7 0 0 0 0 14" />
</svg>
</button>
</div>
<button
id="settings-toggle"
class="btn icon-button launcher"
data-variant="ghost"
aria-label="Settings and projects"
title="Settings and projects"
aria-expanded="false"
aria-controls="inspector"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 3h6l1 3 3 1 2 5-2 5-3 1-1 3H9l-1-3-3-1-2-5 2-5 3-1z" />
<circle cx="12" cy="12" r="3" />
</svg>
</button>
</nav>
<div id="view-navigation" aria-label="View navigation">
<div
id="orbit-gizmo"
data-nav="orbit"
tabindex="0"
role="group"
aria-label="Orbit view. Drag to rotate, or use arrow keys."
title="Drag to orbit · click an axis to align"
>
<svg viewBox="0 0 104 104" aria-hidden="true">
<line class="axis-line" x1="52" y1="52" />
<line class="axis-line" x1="52" y1="52" />
<line class="axis-line" x1="52" y1="52" />
</svg>
</div>
<div class="view-buttons">
<button
class="btn icon-button"
data-variant="ghost"
data-nav="pan"
aria-label="Pan view. Drag or use arrow keys."
title="Drag to pan"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M12 3v18M3 12h18m-12-6 3-3 3 3m-6 12 3 3 3-3M6 9l-3 3 3 3m12-6 3 3-3 3"
/>
</svg>
</button>
<button
class="btn icon-button"
data-variant="ghost"
data-nav="zoom"
aria-label="Zoom view. Drag up or down, or use arrow keys."
title="Drag to zoom"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<circle cx="10" cy="10" r="6" />
<path d="m15 15 6 6M10 7v6M7 10h6" />
</svg>
</button>
<button
id="frame"
class="btn icon-button"
data-variant="ghost"
aria-label="Center view"
title="Center view · F"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M8 3H3v5m13-5h5v5M3 16v5h5m8 0h5v-5" />
<circle cx="12" cy="12" r="4" />
</svg>
</button>
</div>
</div>
<section
id="tools-panel"
class="panel tools-panel"
aria-label="Brush settings"
hidden
>
<div class="panel-heading">
<h2 id="brush-title">Draw</h2>
<button
class="btn"
data-variant="ghost"
data-size="icon-sm"
data-close
aria-label="Close brushes"
>
×
</button>
</div>
<div class="control-section">
<label for="radius"
>Size <output id="radius-value" for="radius"></output></label
><input
class="input"
id="radius"
type="range"
min="0.04"
max="0.6"
step="0.01"
value="0.22"
/>
<label for="strength"
>Strength <output id="strength-value" for="strength"></output></label
><input
class="input"
id="strength"
type="range"
min="0"
max="1"
step="0.01"
value="0.45"
/>
<label class="toggle-row" for="invert"
><span>Subtract</span
><input class="input" role="switch" id="invert" type="checkbox"
/></label>
</div>
</section>
<span id="modified" class="modified-label" hidden>Unsaved</span>
<aside
id="inspector"
class="panel settings-panel"
aria-label="Settings and projects"
hidden
>
<div class="panel-heading">
<h2>Settings</h2>
<button
class="btn"
data-variant="ghost"
data-size="icon-sm"
data-close
aria-label="Close settings"
>
×
</button>
</div>
<p id="document-name" class="project-name">Untitled sphere</p>
<div class="project-actions">
<button id="save" class="btn">Download project</button
><button class="btn" data-variant="outline" id="open">Open</button
><button class="btn" data-variant="outline" id="new">New</button
><button class="btn" data-variant="outline" id="export">Export</button>
</div>
<label class="toggle-row palm-row" for="palm-rejection"
><span>Palm rejection<small>Ignore touch on the canvas</small></span
><input
class="input"
role="switch"
id="palm-rejection"
type="checkbox"
checked
/></label>
<details id="pen-settings">
<summary>Pen response</summary>
<div class="disclosure-content">
<label for="gain"
>Sensitivity <output id="gain-value" for="gain"></output></label
><input
class="input"
id="gain"
type="range"
min="0.25"
max="2"
step="0.05"
value="1"
/>
<label for="gamma"
>Response <output id="gamma-value" for="gamma"></output></label
><input
class="input"
id="gamma"
type="range"
min="0.25"
max="3"
step="0.05"
value="1"
/>
<div class="pressure-meter" aria-label="Pen pressure">
<div id="pressure-fill"></div>
</div>
<span id="pen-state" class="hint">Try your pen on the canvas</span>
<svg
id="curve"
viewBox="0 0 200 54"
role="img"
aria-label="Pressure response"
>
<path class="curve-grid" d="M0 53H200M0 27H200M100 0V54" />
<path id="curve-line" d="M0 53L200 1" />
</svg>
<label class="toggle-row" for="pressureRadius"
><span>Pressure changes size</span
><input
class="input"
role="switch"
id="pressureRadius"
type="checkbox"
/></label>
<button id="reset-pressure" class="btn wide" data-variant="outline">
Reset pen settings
</button>
</div>
</details>
<details>
<summary>Sculpt &amp; display</summary>
<div class="disclosure-content">
<label class="toggle-row" for="symmetry"
><span>Mirror left &amp; right</span
><input
class="input"
role="switch"
id="symmetry"
type="checkbox"
checked
/></label>
<label for="quality">Display</label
><select class="select" id="quality">
<option value="economy">Battery saver</option>
<option value="balanced" selected>Balanced</option>
<option value="detail">Sharper</option>
</select>
<div class="swatches" role="group" aria-label="Clay color">
<button
class="btn"
data-variant="ghost"
data-color="0.5,0.3,0.2"
style="--swatch: #bb8d70"
aria-label="Terracotta clay"
aria-pressed="true"
></button>
<button
class="btn"
data-variant="ghost"
data-color="0.42,0.48,0.48"
style="--swatch: #aab9b7"
aria-label="Stone clay"
aria-pressed="false"
></button>
<button
class="btn"
data-variant="ghost"
data-color="0.21,0.32,0.43"
style="--swatch: #718fa9"
aria-label="Slate clay"
aria-pressed="false"
></button>
</div>
</div>
</details>
<div class="settings-footer">
<button id="fullscreen" class="btn" data-variant="ghost" hidden>
Full screen</button
><button id="help" class="btn" data-variant="ghost">Help</button>
</div>
</aside>
<div
id="status"
class="status-toast"
role="status"
aria-live="polite"
></div>
<input
id="file"
type="file"
accept=".yawn-sculpt,application/json"
hidden
/>
<dialog id="new-dialog" class="dialog" aria-labelledby="new-title">
<form method="dialog">
<header>
<h2 id="new-title">Fresh clay</h2>
<p id="new-warning">Choose how much detail you want.</p>
</header>
<section>
<label for="density">Detail</label
><select class="select" id="density">
<option value="4">Simple</option>
<option value="5" selected>Balanced</option>
<option value="6">Fine</option>
</select>
<p class="hint">Simple feels lighter. Fine allows smaller details.</p>
</section>
<footer>
<button class="btn" data-variant="outline" value="cancel">
Cancel</button
><button value="create" class="btn">Create sphere</button>
</footer>
</form>
</dialog>
<dialog id="help-dialog" class="dialog" aria-labelledby="help-title">
<form method="dialog">
<header>
<h2 id="help-title">Make something.</h2>
<p>Press lightly for a soft touch, harder for a deeper stroke.</p>
</header>
<section>
<dl>
<dt>Find your tools</dt>
<dd>
Choose a brush in the left toolbar. The sliders button opens brush
settings; the gear opens projects and settings.
</dd>
<dt>Move around</dt>
<dd>
Drag the colored axis gizmo to orbit. Click an axis for a
straight-on view. Drag the pan or magnifier icons to move or zoom.
Arrow keys work when these controls are focused. Escape cancels a
drag. The scene uses Y as its up axis.
</dd>
<dt>Rest your hand</dt>
<dd>
Palm rejection is on. Turn it off in Settings to draw with a
finger or move with two fingers.
</dd>
<dt>Keep your work</dt>
<dd>
Download your project before leaving. Open it here to keep
sculpting later.
</dd>
<dt>With a mouse</dt>
<dd>Right drag to rotate, middle drag to pan, scroll to zoom.</dd>
</dl>
</section>
<footer><button value="close" class="btn">Got it</button></footer>
</form>
</dialog>
</body>
</html>