Compare commits

...

6 Commits

Author SHA1 Message Date
Oleg Proskurin 04c7310749 feat: component editor 2026-04-30 15:14:02 +07:00
Oleg Proskurin 2319f2549b feat: components 2026-04-30 14:54:15 +07:00
Oleg Proskurin 7689adf095 assets: update 2026-04-30 10:01:31 +07:00
Oleg Proskurin aedb85720c assets: sonic assets 2026-04-28 20:00:29 +07:00
Oleg Proskurin e4f2a0edfa assets: more sonics 2026-04-28 19:07:22 +07:00
Oleg Proskurin 3d0f777f7d theme: add sonics theme 2026-04-27 20:55:24 +07:00
178 changed files with 2243 additions and 0 deletions

View File

@ -0,0 +1 @@
{"sessionId":"dec1e8b5-dc16-43d2-b20f-0fad3fe0354e","pid":148363,"procStart":"635354","acquiredAt":1777523259923}

View File

@ -0,0 +1,92 @@
# Sonic Universe Theme
Sonic the Hedgehog universe theme. Cinematic, photorealistic visual style — movie-poster quality renders with dramatic lighting, detailed textures, and dynamic compositions.
## Visual Style
- **Cinematic photorealistic** — like Sonic movie posters / Unreal Engine 5 renders
- Dramatic lighting, volumetric effects, detailed fur/quill textures
- Characters should feel alive and dimensional, not flat or cartoon-like
- White or transparent backgrounds for standalone assets
- Concept sketches stored in `sketches/` subfolders during exploration phase
## Character Roster
### Heroes
| Character | File | Description | Status |
|-----------|------|-------------|--------|
| Sonic | `sonic.png` | Blue hedgehog, super speed, iconic quills | Sketch |
| Shadow | `shadow.png` | Black/red hedgehog, chaos energy, rival | Sketch |
| Tails | `tails.png` | Two-tailed fox, flight, tech genius | Sketch |
| Knuckles | `knuckles.png` | Red echidna, super strength, guardian | Sketch |
| Amy Rose | `amy.png` | Pink hedgehog, Piko Piko Hammer | Sketch |
| Silver | `silver.png` | Silver-white hedgehog, psychokinesis, futuristic | Sketch |
| Cream | `cream.png` | Cream rabbit with Chao companion Cheese | Sketch |
### Villains / Anti-heroes
| Character | File | Description | Status |
|-----------|------|-------------|--------|
| Dr. Eggman | `eggman.png` | Main villain, genius inventor, red military jacket | Sketch |
| Metal Sonic | `metal-sonic.png` | Robot hedgehog, blue chrome, red eyes | Sketch |
| Rouge | `rouge.png` | White bat spy, jewel hunter, bat wings | Sketch |
| Chaos | `chaos.png` | Translucent water creature, green glowing brain | Sketch |
| Zavok | `zavok.png` | Red demon, leader of Deadly Six, muscular | Sketch |
| Infinite | `infinite.png` | Black jackal, silver mask, phantom ruby | Sketch |
| Egg Robo | `egg-robo.png` | Egg-shaped robot soldier, red/silver metal | Sketch |
## Items
| Item | Description | Status |
|------|-------------|--------|
| Chaos Emeralds | 7 colored crystals of immense power | Planned |
| Gold Rings | Collectible rings, life energy | Planned |
| More TBD | — | — |
## Asset Inventory
| Category | Path | Count | Description |
|----------|------|-------|-------------|
| Protagonist sketches | `sketches/protagonists/{name}/` | 7×5 | Base sketch, character sheet, lvl1-3 per hero |
| Antagonist sketches | `sketches/antagonists/{name}/` | 7×5 | Base sketch, character sheet, lvl1-3 per villain |
### Per-character file structure
Each character folder contains:
- `{name}.png` — base presentation sketch (standing pose, white bg)
- `{name}-sheet.png` — character sheet (views + expressions, 16:9)
- `{name}-lvl1.png` — Level 1: child (age 5-6), cute accessories, shy smile
- `{name}-lvl2.png` — Level 2: teen (age 12-14), gadgets, stylish, show-off
- `{name}-lvl3.png` — Level 3: young adult (18-20), cyberpunk gear, serious hero/villain
**Exceptions:**
- Eggman: lvl1 = early Robotnik (government suit), lvl2 = unhinged mad scientist, lvl3 = Gerald Robotnik (elderly grandfather)
- Metal Sonic: lvl1 = crude prototype, lvl2 = upgraded combat model, lvl3 = ultimate war machine
- Egg Robo: lvl1 = basic tin robot, lvl2 = combat model, lvl3 = elite destroyer mech
**Total: 70 files** (14 characters × 5 files each)
## Usage in Templates
All paths relative to project root:
- HTML templates: `/assets/themes/sonic/{category}/{file}`
- Generator scripts (from docs/): `../../assets/themes/sonic/{category}/{file}`
- Generator scripts (from project root): `assets/themes/sonic/{category}/{file}`
## Color Palette
TBD — will be defined after initial sketches are reviewed and art direction is confirmed.
## Reference Policy
- `heroes/` — use existing hero images as `--ref` for consistency within character
- `items/` — use existing items as `--ref` within same item type
- Cross-character refs: use any hero sketch to maintain style consistency
## Adding New Assets
1. Generate via `/gen-image` with cinematic photorealistic style keywords
2. Save sketches/concepts to `{category}/sketches/`
3. Final production assets go directly to `{category}/`
4. Use `--ref` from same category for visual consistency

View File

@ -0,0 +1,164 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Component Editor — sonic-diamond</title>
<style>
:root {
--bg: #f5f5f7;
--panel: #ffffff;
--line: #e5e5ea;
--text: #1c1c1e;
--muted: #8e8e93;
--accent: #0a84ff;
--invalid: #fafafa;
--checker: #c8c8c8;
}
* { box-sizing: border-box; }
body { margin: 0; font: 13px/1.4 -apple-system, system-ui, sans-serif;
background: var(--bg); color: var(--text); }
header { position: sticky; top: 0; z-index: 10;
background: var(--panel); border-bottom: 1px solid var(--line);
padding: 12px 20px; display: flex; align-items: center; gap: 16px; }
header h1 { margin: 0; font-size: 15px; font-weight: 600; }
header .meta { color: var(--muted); font-size: 12px; }
header button { font: inherit; padding: 6px 14px;
background: var(--accent); color: white;
border: none; border-radius: 6px; cursor: pointer; }
header button:hover { opacity: 0.9; }
header button.ghost { background: transparent; color: var(--text);
border: 1px solid var(--line); }
main { padding: 20px; }
/* ---- Matrix view ---- */
section { margin-bottom: 32px; }
section > h2 { margin: 0 0 12px; font-size: 13px; font-weight: 600;
color: var(--muted); text-transform: uppercase;
letter-spacing: 0.5px; }
.grid { display: grid; gap: 12px; background: var(--panel);
padding: 16px; border-radius: 10px; border: 1px solid var(--line); }
.cell { display: flex; flex-direction: column; align-items: center;
gap: 6px; padding: 12px; border: 1px solid var(--line);
border-radius: 8px; background: white; min-height: 110px;
justify-content: center; cursor: pointer; transition: transform 0.1s, border-color 0.1s; }
.cell:hover { border-color: var(--accent); transform: translateY(-1px); }
.cell .preview { position: relative; width: 24mm; height: 24mm;
display: flex; align-items: center; justify-content: center; }
.cell .label { font-size: 10px; color: var(--muted);
text-align: center; line-height: 1.3; }
.cell .key { font-family: ui-monospace, Menlo, monospace;
font-size: 9px; color: var(--text); }
/* ---- Single view ---- */
.single-toolbar { display: flex; align-items: center; gap: 16px;
padding: 12px 16px; background: var(--panel);
border: 1px solid var(--line); border-radius: 10px;
margin-bottom: 16px; }
.single-toolbar code { background: var(--bg); padding: 2px 6px;
border-radius: 4px; font-size: 12px; }
.single-body { display: grid; grid-template-columns: 280px 1fr; gap: 16px; }
.single-controls { background: var(--panel); border: 1px solid var(--line);
border-radius: 10px; padding: 16px;
display: flex; flex-direction: column; gap: 14px;
align-self: start; }
.ctrl-row { display: flex; flex-direction: column; gap: 4px; }
.ctrl-row > label { font-size: 11px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.5px; }
.ctrl-row select { font: inherit; padding: 6px 8px;
border: 1px solid var(--line); border-radius: 6px;
background: white; }
.ctrl-row.checkbox { flex-direction: row; align-items: center;
gap: 8px; padding-top: 4px; }
.ctrl-row.checkbox label { font-size: 13px; color: var(--text);
text-transform: none; letter-spacing: 0; }
.ctrl-row.checkbox input[disabled] + label { color: var(--muted); }
.ctrl-info { margin-top: 12px; padding-top: 12px;
border-top: 1px solid var(--line);
display: flex; flex-direction: column; gap: 8px;
font-size: 11px; color: var(--muted); }
.ctrl-info code { font-family: ui-monospace, Menlo, monospace;
font-size: 10px; color: var(--text);
word-break: break-all; }
.single-stage { background: var(--panel); border: 1px solid var(--line);
border-radius: 10px; padding: 24px;
display: flex; align-items: center; justify-content: center; }
.single-frame { position: relative;
width: 60mm; height: 60mm;
background-color: white;
background-image:
linear-gradient(45deg, var(--checker) 25%, transparent 25%),
linear-gradient(-45deg, var(--checker) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--checker) 75%),
linear-gradient(-45deg, transparent 75%, var(--checker) 75%);
background-size: 4mm 4mm;
background-position: 0 0, 0 2mm, 2mm -2mm, -2mm 0;
border: 1px solid var(--line); border-radius: 4px; }
/* ---- Component centering inside any preview frame ---- */
.cell .preview sonic-diamond,
.single-frame sonic-diamond {
position: absolute !important;
left: 50% !important;
top: 50% !important;
transform: translate(-50%, -50%) !important;
}
#toast { position: fixed; bottom: 20px; left: 50%;
transform: translateX(-50%); padding: 10px 20px;
background: var(--text); color: white; border-radius: 8px;
opacity: 0; pointer-events: none; transition: opacity 0.2s;
font-size: 12px; }
#toast.show { opacity: 1; }
</style>
<script type="module" src="./diamond.mjs"></script>
</head>
<body>
<header>
<h1>sonic-diamond</h1>
<span class="meta" id="meta"></span>
<span style="flex: 1"></span>
<button id="btn-copy">Copy snapshot</button>
</header>
<main>
<div id="matrix-view"></div>
<div id="single-view" hidden>
<div class="single-toolbar">
<button class="ghost" id="btn-back">← Matrix</button>
<span>Variant: <code id="info-variant"></code></span>
</div>
<div class="single-body">
<aside class="single-controls">
<div class="ctrl-row">
<label for="ctrl-shape">Shape</label>
<select id="ctrl-shape"></select>
</div>
<div class="ctrl-row">
<label for="ctrl-color">Color</label>
<select id="ctrl-color"></select>
</div>
<div class="ctrl-row checkbox">
<input type="checkbox" id="ctrl-chipped">
<label for="ctrl-chipped" id="ctrl-chipped-label">Chipped</label>
</div>
<div class="ctrl-info">
<div>baseSize: <code id="info-basesize"></code></div>
<div>origin: <code id="info-origin"></code></div>
<div>image: <code id="info-img"></code></div>
</div>
</aside>
<main class="single-stage">
<div class="single-frame" id="single-frame"></div>
</main>
</div>
</div>
</main>
<div id="toast">Snapshot copied</div>
<script type="module" src="./diamond.editor.mjs"></script>
</body>
</html>

View File

@ -0,0 +1,223 @@
/**
* Component-editor for sonic-diamond.
*
* Modes:
* - Matrix (default): grid of all valid preset combinations. Click Single.
* - Single: one component instance with dropdown/checkbox controls and
* a checker-pattern preview frame. Diamond has no per-variant tuning,
* so this mode here only demonstrates the prop-switching flow.
*
* Mode is driven by the URL hash:
* '' Matrix
* 'shape=...&color=...&chipped=...' Single
*
* "Copy snapshot" works in either mode and produces JS source ready to
* paste between `@editor:anchors-*` and `@editor:variants-*` markers.
*/
await customElements.whenDefined('sonic-diamond');
const Comp = customElements.get('sonic-diamond');
const { presetProps, chippedShapes } = Comp;
document.getElementById('meta').textContent =
`${Object.keys(Comp.variants).length} variants · baseSize ${Comp.baseSize.w}×${Comp.baseSize.h}mm · origin (${Comp.origin.x}, ${Comp.origin.y})`;
initMatrix();
initSingle();
window.addEventListener('hashchange', route);
route();
document.getElementById('btn-copy').addEventListener('click', async () => {
const text = serializeSnapshot(Comp.anchorsDefault, Comp.variants);
await navigator.clipboard.writeText(text);
showToast('Snapshot copied');
});
// ---- Routing ----
function route() {
const params = new URLSearchParams(location.hash.slice(1));
const matrixView = document.getElementById('matrix-view');
const singleView = document.getElementById('single-view');
if (params.has('shape')) {
matrixView.hidden = true;
singleView.hidden = false;
applySingle(params);
} else {
matrixView.hidden = false;
singleView.hidden = true;
}
}
function setHashParams(updates) {
const params = new URLSearchParams(location.hash.slice(1));
for (const [k, v] of Object.entries(updates)) params.set(k, String(v));
location.hash = params.toString();
}
// ---- Matrix mode ----
function initMatrix() {
const root = document.getElementById('matrix-view');
renderGrid(root, 'Full', false);
renderGrid(root, 'Chipped', true);
}
function renderGrid(root, title, chipped) {
const shapes = chipped
? presetProps.shape.filter(s => chippedShapes.has(s))
: presetProps.shape;
const colors = presetProps.color;
const section = document.createElement('section');
const h2 = document.createElement('h2');
h2.textContent = `${title}${shapes.length} × ${colors.length}`;
section.appendChild(h2);
const grid = document.createElement('div');
grid.className = 'grid';
grid.style.gridTemplateColumns = `repeat(${colors.length}, 1fr)`;
for (const shape of shapes) {
for (const color of colors) {
grid.appendChild(makeCell(shape, color, chipped));
}
}
section.appendChild(grid);
root.appendChild(section);
}
function makeCell(shape, color, chipped) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.title = 'Open in single mode';
cell.addEventListener('click', () => {
location.hash = `shape=${shape}&color=${color}&chipped=${chipped}`;
});
const preview = document.createElement('div');
preview.className = 'preview';
const inst = document.createElement('sonic-diamond');
inst.setAttribute('shape', shape);
inst.setAttribute('color', color);
inst.setAttribute('chipped', String(chipped));
preview.appendChild(inst);
const label = document.createElement('div');
label.className = 'label';
label.innerHTML = `<div>${shape} · ${color}</div>`;
const key = document.createElement('div');
key.className = 'key';
key.textContent = inst._variantKey();
cell.appendChild(preview);
cell.appendChild(label);
cell.appendChild(key);
return cell;
}
// ---- Single mode ----
let singleInstance = null;
function initSingle() {
fillSelect('ctrl-shape', presetProps.shape);
fillSelect('ctrl-color', presetProps.color);
document.getElementById('ctrl-shape').addEventListener('change', e =>
setHashParams({ shape: e.target.value })
);
document.getElementById('ctrl-color').addEventListener('change', e =>
setHashParams({ color: e.target.value })
);
document.getElementById('ctrl-chipped').addEventListener('change', e =>
setHashParams({ chipped: e.target.checked })
);
document.getElementById('btn-back').addEventListener('click', () => {
location.hash = '';
});
}
function applySingle(params) {
if (!singleInstance) {
singleInstance = document.createElement('sonic-diamond');
document.getElementById('single-frame').appendChild(singleInstance);
}
const shape = params.get('shape') ?? presetProps.shape[0];
const color = params.get('color') ?? presetProps.color[0];
const chipped = params.get('chipped') === 'true';
singleInstance.setAttribute('shape', shape);
singleInstance.setAttribute('color', color);
singleInstance.setAttribute('chipped', String(chipped));
document.getElementById('ctrl-shape').value = shape;
document.getElementById('ctrl-color').value = color;
const cb = document.getElementById('ctrl-chipped');
const cbLabel = document.getElementById('ctrl-chipped-label');
const supportsChipped = chippedShapes.has(shape);
cb.checked = chipped && supportsChipped;
cb.disabled = !supportsChipped;
cbLabel.textContent = supportsChipped ? 'Chipped' : 'Chipped (n/a for this shape)';
const key = singleInstance._variantKey();
document.getElementById('info-variant').textContent = key;
document.getElementById('info-basesize').textContent = `${Comp.baseSize.w}×${Comp.baseSize.h}mm`;
document.getElementById('info-origin').textContent = `(${Comp.origin.x}, ${Comp.origin.y})`;
document.getElementById('info-img').textContent = Comp.variants[key]?.img ?? '—';
}
function fillSelect(id, options) {
const sel = document.getElementById(id);
sel.innerHTML = '';
for (const opt of options) {
const o = document.createElement('option');
o.value = opt;
o.textContent = opt;
sel.appendChild(o);
}
}
// ---- Snapshot serialization ----
function serializeSnapshot(anchorsDefault, variants) {
const lines = [];
lines.push(' // @editor:anchors-start');
lines.push(` static anchorsDefault = ${formatAnchors(anchorsDefault, ' ')};`);
lines.push(' // @editor:anchors-end');
lines.push('');
lines.push(' // @editor:variants-start');
lines.push(' static variants = {');
for (const [key, v] of Object.entries(variants)) {
const a = formatAnchorsInline(v.anchors || {});
lines.push(` ${key}: { img: ${JSON.stringify(v.img)}, dx: ${v.dx}, dy: ${v.dy}, scale: ${v.scale}, anchors: ${a} },`);
}
lines.push(' };');
lines.push(' // @editor:variants-end');
return lines.join('\n');
}
function formatAnchors(anchors, indent) {
const keys = Object.keys(anchors);
if (keys.length === 0) return '{}';
const inner = keys.map(k => `${indent} ${k}: { x: ${anchors[k].x}, y: ${anchors[k].y} }`).join(',\n');
return `{\n${inner},\n${indent}}`;
}
function formatAnchorsInline(anchors) {
const keys = Object.keys(anchors);
if (keys.length === 0) return '{}';
const parts = keys.map(k => `${k}: { x: ${anchors[k].x}, y: ${anchors[k].y} }`);
return `{ ${parts.join(', ')} }`;
}
function showToast(msg) {
const toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 1500);
}

View File

@ -0,0 +1,85 @@
import { FunPenComponent } from '/src/components/base.mjs';
class SonicDiamond extends FunPenComponent {
static origin = { x: 5, y: 5 };
static baseSize = { w: 10, h: 10 };
static presetProps = {
shape: ['classic', 'elongated', 'flat', 'round', 'raw'],
color: ['blue', 'cyan', 'green', 'magenta', 'pink', 'red'],
chipped: ['false', 'true'],
};
// Chipped variants exist only for these shapes. Other combinations
// silently fall back to the non-chipped variant.
static chippedShapes = new Set(['classic', 'round']);
// @editor:anchors-start
static anchorsDefault = {};
// @editor:anchors-end
// @editor:variants-start
static variants = {
classic_blue: { img: '/assets/themes/sonic/items/diamonds/01-classic-blue.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_cyan: { img: '/assets/themes/sonic/items/diamonds/01-classic-cyan.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_green: { img: '/assets/themes/sonic/items/diamonds/01-classic-green.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_magenta: { img: '/assets/themes/sonic/items/diamonds/01-classic-magenta.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_pink: { img: '/assets/themes/sonic/items/diamonds/01-classic-pink.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_red: { img: '/assets/themes/sonic/items/diamonds/01-classic-red.png', dx: 0, dy: 0, scale: 1, anchors: {} },
elongated_blue: { img: '/assets/themes/sonic/items/diamonds/02-elongated-blue.png', dx: 0, dy: 0, scale: 1, anchors: {} },
elongated_cyan: { img: '/assets/themes/sonic/items/diamonds/02-elongated-cyan.png', dx: 0, dy: 0, scale: 1, anchors: {} },
elongated_green: { img: '/assets/themes/sonic/items/diamonds/02-elongated-green.png', dx: 0, dy: 0, scale: 1, anchors: {} },
elongated_magenta: { img: '/assets/themes/sonic/items/diamonds/02-elongated-magenta.png', dx: 0, dy: 0, scale: 1, anchors: {} },
elongated_pink: { img: '/assets/themes/sonic/items/diamonds/02-elongated-pink.png', dx: 0, dy: 0, scale: 1, anchors: {} },
elongated_red: { img: '/assets/themes/sonic/items/diamonds/02-elongated-red.png', dx: 0, dy: 0, scale: 1, anchors: {} },
flat_blue: { img: '/assets/themes/sonic/items/diamonds/03-flat-blue.png', dx: 0, dy: 0, scale: 1, anchors: {} },
flat_cyan: { img: '/assets/themes/sonic/items/diamonds/03-flat-cyan.png', dx: 0, dy: 0, scale: 1, anchors: {} },
flat_green: { img: '/assets/themes/sonic/items/diamonds/03-flat-green.png', dx: 0, dy: 0, scale: 1, anchors: {} },
flat_magenta: { img: '/assets/themes/sonic/items/diamonds/03-flat-magenta.png', dx: 0, dy: 0, scale: 1, anchors: {} },
flat_pink: { img: '/assets/themes/sonic/items/diamonds/03-flat-pink.png', dx: 0, dy: 0, scale: 1, anchors: {} },
flat_red: { img: '/assets/themes/sonic/items/diamonds/03-flat-red.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_blue: { img: '/assets/themes/sonic/items/diamonds/04-round-blue.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_cyan: { img: '/assets/themes/sonic/items/diamonds/04-round-cyan.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_green: { img: '/assets/themes/sonic/items/diamonds/04-round-green.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_magenta: { img: '/assets/themes/sonic/items/diamonds/04-round-magenta.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_pink: { img: '/assets/themes/sonic/items/diamonds/04-round-pink.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_red: { img: '/assets/themes/sonic/items/diamonds/04-round-red.png', dx: 0, dy: 0, scale: 1, anchors: {} },
raw_blue: { img: '/assets/themes/sonic/items/diamonds/05-raw-blue.png', dx: 0, dy: 0, scale: 1, anchors: {} },
raw_cyan: { img: '/assets/themes/sonic/items/diamonds/05-raw-cyan.png', dx: 0, dy: 0, scale: 1, anchors: {} },
raw_green: { img: '/assets/themes/sonic/items/diamonds/05-raw-green.png', dx: 0, dy: 0, scale: 1, anchors: {} },
raw_magenta: { img: '/assets/themes/sonic/items/diamonds/05-raw-magenta.png', dx: 0, dy: 0, scale: 1, anchors: {} },
raw_pink: { img: '/assets/themes/sonic/items/diamonds/05-raw-pink.png', dx: 0, dy: 0, scale: 1, anchors: {} },
raw_red: { img: '/assets/themes/sonic/items/diamonds/05-raw-red.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_blue_chipped: { img: '/assets/themes/sonic/items/diamonds/06-classic-blue-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_cyan_chipped: { img: '/assets/themes/sonic/items/diamonds/06-classic-cyan-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_green_chipped: { img: '/assets/themes/sonic/items/diamonds/06-classic-green-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_magenta_chipped: { img: '/assets/themes/sonic/items/diamonds/06-classic-magenta-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_pink_chipped: { img: '/assets/themes/sonic/items/diamonds/06-classic-pink-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
classic_red_chipped: { img: '/assets/themes/sonic/items/diamonds/06-classic-red-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_blue_chipped: { img: '/assets/themes/sonic/items/diamonds/07-round-blue-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_cyan_chipped: { img: '/assets/themes/sonic/items/diamonds/07-round-cyan-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_green_chipped: { img: '/assets/themes/sonic/items/diamonds/07-round-green-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_magenta_chipped: { img: '/assets/themes/sonic/items/diamonds/07-round-magenta-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_pink_chipped: { img: '/assets/themes/sonic/items/diamonds/07-round-pink-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
round_red_chipped: { img: '/assets/themes/sonic/items/diamonds/07-round-red-chipped.png', dx: 0, dy: 0, scale: 1, anchors: {} },
};
// @editor:variants-end
_variantKey() {
const shape = this.getAttribute('shape') ?? this.constructor.presetProps.shape[0];
const color = this.getAttribute('color') ?? this.constructor.presetProps.color[0];
const chipped = this.getAttribute('chipped') === 'true';
const supportsChipped = this.constructor.chippedShapes.has(shape);
return supportsChipped && chipped
? `${shape}_${color}_chipped`
: `${shape}_${color}`;
}
}
customElements.define('sonic-diamond', SonicDiamond);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -0,0 +1,15 @@
/**
* Sonic theme component loader.
* Scans the document for `sonic-*` custom elements and lazy-imports
* only those component modules. See src/components/CLAUDE.md.
*/
const PREFIX = 'sonic-';
const tags = new Set();
document.querySelectorAll('*').forEach(el => {
const t = el.tagName.toLowerCase();
if (t.startsWith(PREFIX)) tags.add(t);
});
await Promise.all([...tags].map(t => {
const name = t.slice(PREFIX.length);
return import(`./components/${name}/${name}.mjs`);
}));

Some files were not shown because too many files have changed in this diff Show More