Files
yawn/server/web/assets/sculpt/index.html
T
Ampandheaust d279ec4123 Add clay pieces, masking, and topology editing to sculpt
Add pastel presets, piece selection and colors, mask brushes, subdivision and conservative decimation with bounded worker operations and mixed undo history.

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

616 lines
20 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="empty-clay" class="canvas-message" hidden>
<h1>A fresh canvas.</h1>
<p>Add a piece of clay to begin.</p>
<button id="add-empty" class="btn">Add clay</button>
</div>
<div id="clay-progress" class="canvas-message" role="status" hidden>
<h1 id="operation-label">Updating clay…</h1>
<p>Your other pieces stay unchanged.</p>
<button id="cancel-operation" class="btn">Cancel</button>
</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">
<button
id="pieces-toggle"
class="btn icon-button launcher"
aria-label="Clay pieces"
title="Clay pieces"
aria-expanded="false"
aria-controls="pieces-panel"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m12 3 9 5-9 5-9-5zm-9 9 9 5 9-5M3 16l9 5 9-5" />
</svg>
</button>
<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>
<button
class="btn icon-button"
data-brush="mask"
aria-pressed="false"
aria-label="Mask"
title="Mask · 6"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 5h16v8c0 5-8 8-8 8s-8-3-8-8zM12 5v16" />
<path d="M6 8h4M6 11h4M7 14h3" />
</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 id="invert-label">Subtract</span
><input class="input" role="switch" id="invert" type="checkbox"
/></label>
</div>
</section>
<section
id="pieces-panel"
class="panel pieces-panel"
aria-label="Clay pieces"
hidden
>
<div class="panel-heading">
<h2>Clay</h2>
<button class="btn" data-close aria-label="Close clay pieces">×</button>
</div>
<div id="piece-list" aria-label="Select a piece"></div>
<div class="piece-material">
<label for="piece-color">Color</label
><input id="piece-color" type="color" aria-label="Piece color" />
<button
id="delete-piece"
class="btn"
aria-label="Delete selected piece"
title="Delete selected piece"
>
Delete
</button>
</div>
<div class="preset-grid" aria-label="Add clay preset">
<button
class="btn"
data-preset="sphere"
aria-label="Add sphere"
title="Add sphere"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<ellipse cx="12" cy="12" rx="4" ry="9" />
<path d="M3 12h18" />
</svg>
</button>
<button
class="btn"
data-preset="cube"
aria-label="Add cube"
title="Add cube"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m12 2 9 5v10l-9 5-9-5V7zm0 10 9-5M12 12 3 7m9 5v10" />
</svg>
</button>
<button
class="btn"
data-preset="cylinder"
aria-label="Add cylinder"
title="Add cylinder"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<ellipse cx="12" cy="5" rx="8" ry="3" />
<path d="M4 5v14c0 4 16 4 16 0V5" />
</svg>
</button>
<button
class="btn"
data-preset="cone"
aria-label="Add cone"
title="Add cone"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m4 18 8-16 8 16" />
<ellipse cx="12" cy="18" rx="8" ry="3" />
</svg>
</button>
<button
class="btn"
data-preset="torus"
aria-label="Add torus"
title="Add torus"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<ellipse cx="12" cy="12" rx="10" ry="7" />
<ellipse cx="12" cy="10" rx="4" ry="2" />
</svg>
</button>
</div>
<details class="disclosure">
<summary>Detail &amp; mask</summary>
<div class="piece-actions">
<button id="subdivide" class="btn">Subdivide</button
><button id="decimate" class="btn">Decimate</button>
<button id="clear-mask" class="btn">Clear mask</button
><button id="invert-mask" class="btn">Invert mask</button>
</div>
<p class="hint">
Subdivide adds resolution. Decimate reduces detail and preserves
masked areas.
</p>
</details>
</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>Build with clay</dt>
<dd>
Open Clay at the top of the toolbar. Add a preset, select its row,
then change its color or delete it. Each new piece gets a pastel
color.
</dd>
<dt>Protect and refine</dt>
<dd>
Mask paints protected areas; use Erase mask in brush settings to
remove it. Clay → Detail &amp; mask contains Clear, Invert,
Subdivide and Decimate. Subdivide adds resolution without
smoothing. Decimate preserves masked areas and may reduce less
than half.
</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>