Compare commits
6 Commits
13f4d61fe0
...
04c7310749
| Author | SHA1 | Date |
|---|---|---|
|
|
04c7310749 | |
|
|
2319f2549b | |
|
|
7689adf095 | |
|
|
aedb85720c | |
|
|
e4f2a0edfa | |
|
|
3d0f777f7d |
|
|
@ -0,0 +1 @@
|
|||
{"sessionId":"dec1e8b5-dc16-43d2-b20f-0fad3fe0354e","pid":148363,"procStart":"635354","acquiredAt":1777523259923}
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 710 KiB |
|
After Width: | Height: | Size: 819 KiB |
|
After Width: | Height: | Size: 817 KiB |
|
After Width: | Height: | Size: 880 KiB |
|
After Width: | Height: | Size: 988 KiB |
|
After Width: | Height: | Size: 1006 KiB |
|
After Width: | Height: | Size: 860 KiB |
|
After Width: | Height: | Size: 755 KiB |
|
After Width: | Height: | Size: 784 KiB |
|
After Width: | Height: | Size: 962 KiB |
|
After Width: | Height: | Size: 737 KiB |
|
After Width: | Height: | Size: 718 KiB |
|
After Width: | Height: | Size: 1009 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 862 KiB |
|
After Width: | Height: | Size: 820 KiB |
|
After Width: | Height: | Size: 868 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1011 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 972 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 997 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 995 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 998 KiB |
|
After Width: | Height: | Size: 895 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 987 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1015 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1022 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1005 KiB |
|
After Width: | Height: | Size: 907 KiB |
|
After Width: | Height: | Size: 890 KiB |
|
After Width: | Height: | Size: 911 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 979 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
|
@ -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`);
|
||||
}));
|
||||