slim
This commit is contained in:
parent
43b659b64b
commit
06ab438d7b
13 changed files with 1167 additions and 3747 deletions
|
|
@ -1,36 +0,0 @@
|
|||
# Default Art Audit
|
||||
|
||||
## 実施内容
|
||||
|
||||
`app.js` の `buildDefaultGalleryPack()` を確認し、初期同梱作品の品質を監査した。低品質または初期作品として弱いものは、削除対象リストで除外する方式にした。既存ローカル状態に残っている Island Team 製の該当作品も、次回ロード時に Collection / island placement / dynamic summon から除外される。
|
||||
|
||||
## 削除した初期作品
|
||||
|
||||
| 作品名 | 理由 |
|
||||
|---|---|
|
||||
| Shell Rock | 16×16に対して実質的な形状が小さく、島上で石・貝の識別点が弱い。 |
|
||||
| Azure Minnow | 魚としての頭・尾・ヒレが弱く、単なる水色の塊に近い。 |
|
||||
| Firefly Swirl | 8×8で透明部が多く、作品というより粒子エフェクトに近い。 |
|
||||
| Leaf Sparrow | 鳥のシルエットが薄く、頭・翼・尾の分離が不足している。 |
|
||||
| Silver Trout | 魚としては読めるが、既存の Cloud Koi / Sunset Koi より低密度で初期作品として弱い。 |
|
||||
| Butterfly Fish | 小型魚としての情報量が少なく、島上で主題が読み取りにくい。 |
|
||||
|
||||
## 残したが改変候補に入る作品
|
||||
|
||||
| 作品名 | コメント |
|
||||
|---|---|
|
||||
| River Stones | 8×8としては成立しているが、石の影と水際の情報を足すと良い。 |
|
||||
| Paper Crane | モチーフは明確だが、分断セルが多い。羽根の接続を調整できる。 |
|
||||
| Linen Cottage | 読みやすいが、屋根と壁の面が単純。影と窓を増やせる。 |
|
||||
| Human 系小型作品 | 役割差はあるが、体型・顔・道具の差分が弱いものがある。 |
|
||||
|
||||
## コード上の変更点
|
||||
|
||||
- `REMOVED_LOW_QUALITY_SEED_ASSET_NAMES` を追加。
|
||||
- `buildDefaultGalleryPack()` の戻り値に品質フィルタを適用。
|
||||
- `mergeDefaultGallery()` で既存ローカル状態の Island Team 製削除対象も除外。
|
||||
- Python 側テストは `14 passed`。
|
||||
|
||||
## 次の作業
|
||||
|
||||
ユーザー確認後、新しいデフォルト作品を追加する。追加時は `PIXEL_ART_QUALITY.md` の基準に従い、既存の弱いカテゴリを補う。
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# Default Art Expansion / Depth Lighting Tuning
|
||||
|
||||
## Depth tuning
|
||||
|
||||
Adjusted the depth-local light response so that:
|
||||
|
||||
- local light influence stays colorized instead of washing into a neutral gray band,
|
||||
- the light source itself and nearby sprite/depth corners catch the strongest highlight,
|
||||
- brightness depends less on raw source-to-pixel distance than before,
|
||||
- convex edges still read clearly under nearby inspection/night lights.
|
||||
|
||||
Implementation areas: `depthLocalLightResponse()`, `getDepthEdgeLightBoost()`, and local-light color mixing inside `shadeAssetPixelColor()` in `app.js`.
|
||||
|
||||
## UI bubble correction
|
||||
|
||||
- Reduced the selection bubble top offset so the bubble appears closer to the selected sprite.
|
||||
|
||||
## New large default works
|
||||
|
||||
### Static / building
|
||||
- Smile Portrait — 24x32
|
||||
- Rosetta Stela — 24x32
|
||||
- Pharaoh Mask — 24x32
|
||||
- Terracotta Sentinel — 20x32
|
||||
|
||||
### Static / nature
|
||||
- Great Wave Panel — 32x24
|
||||
- Sunflower Still Life — 24x32
|
||||
- Stone Circle — 32x24
|
||||
|
||||
## Notes
|
||||
|
||||
- `assets.json` and `default_assets_sheet.png` were refreshed.
|
||||
- The seeded island placement list was expanded so these works appear by default.
|
||||
- Save schema was bumped to `25`.
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# ドット絵品質基準 / Pixel Art Quality Standard
|
||||
|
||||
このプロトタイプで「良質なドット絵」と見なす基準。共有サービスの初期作品、審査、改変、削除判断で使う。
|
||||
|
||||
## 1. 最低条件
|
||||
|
||||
- **透明セルを作品の一部として設計している**:余白はただの空きではなく、シルエットを読ませるために残す。
|
||||
- **1秒以内に主題が判別できる**:縮小表示、島上表示、夜間表示でも「家」「鳥」「魚」「橋」などの役割が読める。
|
||||
- **接地・重心が明確**:静物は地面や水面に乗って見える。動物・人は足元または接地点が曖昧でない。
|
||||
- **パレットに収まる**:任意色の多用ではなく、既存パレットコードに近い色で統一する。
|
||||
- **クリック対象として破綻しない**:極端に薄い線、孤立点だけの作品、透明部分が多すぎる作品は避ける。
|
||||
|
||||
## 2. 具体的な評価軸
|
||||
|
||||
| 評価軸 | 良い状態 | 低品質と判断する状態 |
|
||||
|---|---|---|
|
||||
| シルエット | 背景なしでも輪郭だけで主題が読める | 点・棒・塊にしか見えない |
|
||||
| 明暗 | 上面・側面・影の方向がある | 全面が同じ明度で平坦 |
|
||||
| 色数 | 小物は3〜6色、中型は5〜10色程度で役割が分かれる | 1〜2色だけ、または無秩序に多色 |
|
||||
| ピクセル密度 | 余白、密部、抜きのバランスがある | ほぼ空白、またはベタ塗りの矩形 |
|
||||
| エッジ | 角、段差、斜線に意図がある | ガタつきが偶然に見える |
|
||||
| 内部ディテール | 目、窓、葉、波、道具など識別点がある | 主題を補強する点がない |
|
||||
| 島との親和性 | 地形、昼夜、他作品のサイズ感と合う | 尺度が浮く、接地しない、光源が不自然 |
|
||||
| メタ情報 | building の door、light、depth、particle が主題に沿う | メタ情報がない、または見た目と矛盾する |
|
||||
|
||||
## 3. サイズ別の合格目安
|
||||
|
||||
### 8×8
|
||||
|
||||
- 主題は1つに絞る。
|
||||
- 色は3〜5色までを目安にする。
|
||||
- 透明を含めても、主題の有効ピクセルは概ね16セル以上ほしい。
|
||||
- 「点の集合」ではなく、頭・胴・尾、花・葉・茎などの構造を最低2部位に分ける。
|
||||
|
||||
### 12×12〜16×16
|
||||
|
||||
- デフォルト作品の中心サイズ。
|
||||
- 輪郭色、主色、中間色、ハイライト、影の5層を基本にする。
|
||||
- 動物・人は顔または進行方向が読めること。
|
||||
- 建物・自然物は接地線、屋根/幹/水面などの基礎形を入れる。
|
||||
|
||||
### 20×20以上
|
||||
|
||||
- 大型作品はディテール量だけでなく、遠目の大形状を優先する。
|
||||
- 3〜5個の大きな面に分け、その中に小ディテールを置く。
|
||||
- 光・depth・particle を使う場合、効果が主題を補強していること。
|
||||
|
||||
## 4. 削除・改変の判断
|
||||
|
||||
### 削除候補
|
||||
|
||||
- 何を描いたか判別できない。
|
||||
- 既存の高品質作品と役割が重複し、見劣りする。
|
||||
- ほぼ点、線、単色塊で、改変より作り直しが早い。
|
||||
- 島上でクリックや配置の視認性を落とす。
|
||||
|
||||
### 改変候補
|
||||
|
||||
- 主題は読めるが、明暗・輪郭・接地が弱い。
|
||||
- 色の階調が足りない。
|
||||
- サイズに対してディテールが不足している。
|
||||
- 良いアイデアはあるが、他作品群と密度が合っていない。
|
||||
|
||||
## 5. デフォルト作品としての追加基準
|
||||
|
||||
- 初回表示でサービスの品質水準を示せること。
|
||||
- ユーザーがリミックスしたくなる明確なモチーフを持つこと。
|
||||
- 小型、中型、大型、静物、動物、人、船、水辺、建物が偏りすぎないこと。
|
||||
- 低品質作品を大量に残して作品数を稼がないこと。
|
||||
26
README.md
26
README.md
|
|
@ -20,7 +20,7 @@ Open `index.html` in a modern browser. No build step or third-party dependency i
|
|||
- Save assets to the local collection.
|
||||
- Place static assets and summon dynamic assets onto the island.
|
||||
- Dynamic assets default to right-facing on spawn; left-facing is rendered by mirroring during movement only.
|
||||
- Local voting, hiding, remix/copy edit, and import/export tools are included.
|
||||
- Local voting, hiding, remix/copy edit, and collection management are included.
|
||||
|
||||
## Phase 1 changes
|
||||
|
||||
|
|
@ -33,7 +33,6 @@ Open `index.html` in a modern browser. No build step or third-party dependency i
|
|||
|
||||
- Local save uses a compact Phase 2 envelope in `localStorage`.
|
||||
- Pixel planes are bbox-cropped and RLE-compressed when this is smaller than raw cropped data.
|
||||
- Compact asset bundles can be exported separately from world snapshots.
|
||||
- World snapshots contain placements, dynamic summons, and an asset manifest rather than full asset blobs.
|
||||
- Local object/asset mutations append small sync events to `eventLog`.
|
||||
- IndexedDB asset/snapshot cache helpers are included for future server-backed missing-asset fetches.
|
||||
|
|
@ -46,7 +45,6 @@ Open `index.html` in a modern browser. No build step or third-party dependency i
|
|||
- Dynamic runtime updates are stepped at 15 Hz and capped per frame to avoid spiral-of-death on slow devices.
|
||||
- Rendering pauses while the browser tab is hidden and resumes on visibility change.
|
||||
- Tile/object lookups use a small spatial index for inspect, erase, and nearby-target searches.
|
||||
- Data tab reports visible terrain chunks for quick performance inspection.
|
||||
|
||||
## Phase 4 changes
|
||||
|
||||
|
|
@ -55,20 +53,11 @@ Open `index.html` in a modern browser. No build step or third-party dependency i
|
|||
- Added Select tool. Drag to select a rectangular area, then drag the selection or use arrow keys/buttons to move it.
|
||||
- Nudge buttons move the active selection; without a selection they shift the whole drawing.
|
||||
- Added horizontal/vertical flip, palette-colored outline generation, and clear selection.
|
||||
- Added editor PNG export/import. PNG import quantizes the image to the current island palette and current canvas size.
|
||||
- Added keyboard shortcuts: B/E/F/I/L/R/S for tools, Ctrl/Cmd+Z/Y for history, arrows for moving selected pixels, Escape to clear selection.
|
||||
|
||||
## Export formats
|
||||
|
||||
- **Export full JSON**: human-readable full local state.
|
||||
- **Export compact**: compact local state suitable for saving/transferring.
|
||||
- **Export snapshot**: placement state plus asset manifest; requires asset bundles or cache to render fully.
|
||||
- **Export asset bundle**: compact asset blob list for missing-asset transfer.
|
||||
|
||||
|
||||
## Phase 4 follow-up adjustments
|
||||
|
||||
- PNG import now chooses the editor canvas size from the image size. Square 8/16/32/64 PNGs import at their native size; other sizes are fit into the nearest supported square canvas.
|
||||
- Depth is now three-state: high, normal, and low. High brightens sprite pixels; low darkens them.
|
||||
- Right-clicking the world canvas clears the current map selection instead of moving or placing the selected asset.
|
||||
- Dynamic sprites now pause intermittently instead of walking continuously.
|
||||
|
|
@ -80,16 +69,7 @@ Open `index.html` in a modern browser. No build step or third-party dependency i
|
|||
|
||||
- Editor schema is now 7.
|
||||
- Fixed the Advanced Draw depth controls so `Depth / High / Low / Clear` wrap inside the drawer instead of overflowing to the right.
|
||||
- Added Phase 5 guardrails in the Data tab:
|
||||
- local asset/object volume counters,
|
||||
- duplicate-content count,
|
||||
- orphan placement checks,
|
||||
- terrain compatibility checks,
|
||||
- hidden/report counters.
|
||||
- Added `Validate world` export for pre-server integrity checks.
|
||||
- Added local object reporting from the selection bubble. Reported objects are hidden locally and stored in a local moderation report log.
|
||||
- Added `Export moderation report` for review/debug data.
|
||||
- Added `Clear reports` to clear local report logs without un-hiding already hidden objects.
|
||||
- Added soft local guardrail limits: 160 assets and 220 world objects. Existing objects can still be moved; new saves/placements are blocked when the local cap is reached.
|
||||
|
||||
## Phase 5 follow-up changes
|
||||
|
|
@ -129,7 +109,6 @@ Open `index.html` in a modern browser. No build step or third-party dependency i
|
|||
- Walking particles are less frequent and use more visibly varied colors.
|
||||
- Remix always creates a new derivative asset; Edit updates the user's own original asset.
|
||||
- Advanced Draw now includes configurable particle emitter cells for any asset.
|
||||
- PNG export/import moved to the Data tab.
|
||||
- Nudge arrow buttons under the canvas were removed; keyboard arrow nudging remains.
|
||||
- UI text is larger overall, while palette color-code labels keep their small size.
|
||||
|
||||
|
|
@ -138,7 +117,7 @@ Open `index.html` in a modern browser. No build step or third-party dependency i
|
|||
This build adds count-based island rotation.
|
||||
|
||||
- Default island display cap: 250 objects.
|
||||
- The local display cap is adjustable from Data > Visual settings.
|
||||
- The local display cap is adjustable from Settings.
|
||||
- Objects are not deleted when the cap is exceeded; the effective oldest objects are omitted from the island display.
|
||||
- Upvotes delay rotation-out by slot adjustment. Downvotes advance rotation-out.
|
||||
- Local publish / republish quota: 5 objects per author per rolling hour.
|
||||
|
|
@ -306,7 +285,6 @@ Use this to compare minified JSON, gzip, zlib, and Brotli when available. Produc
|
|||
- Reworked depth local-light behavior so the source itself and nearby outer/depth corners catch the strongest highlights, while distance has a weaker effect on brightness than before.
|
||||
- Reduced the selection bubble offset so the bubble sits noticeably closer to the top of the selected artwork.
|
||||
- Added several new large default works themed around masterpieces, heritage, and artifacts: **Smile Portrait**, **Great Wave Panel**, **Sunflower Still Life**, **Rosetta Stela**, **Pharaoh Mask**, **Stone Circle**, **Terracotta Sentinel**.
|
||||
- Refreshed `assets.json` and `default_assets_sheet.png` after the additions.
|
||||
|
||||
## Custom update 4: cursor-light smoothing + gray-bloom hotfix
|
||||
|
||||
|
|
|
|||
2815
assets.json
2815
assets.json
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 128 KiB |
|
|
@ -1,24 +0,0 @@
|
|||
const fs = require('fs');
|
||||
const vm = require('vm');
|
||||
const path = require('path');
|
||||
let code = fs.readFileSync(path.join(__dirname, 'app.js'), 'utf8');
|
||||
code = code.replace(/\n\s*bootstrap\(\);\s*\n\}\)\(\);\s*$/, `\n window.__PIXEL_DEBUG__ = { state, buildDefaultGalleryPack, PALETTE, PALETTE_BY_CODE, assetWidth, assetHeight };\n})();`);
|
||||
function makeCtx(){
|
||||
const noop=()=>{};
|
||||
return new Proxy({imageSmoothingEnabled:false, canvas:{width:1,height:1}}, {get(t,p){
|
||||
if(p in t) return t[p];
|
||||
if(p==='measureText') return () => ({width:0});
|
||||
return noop;
|
||||
}, set(t,p,v){ t[p]=v; return true; }});
|
||||
}
|
||||
function makeEl(id){ return new Proxy({ id, width:640, height:480, style:{}, classList:{add(){},remove(){},toggle(){},contains(){return false;}}, dataset:{}, value:'', checked:false, textContent:'', innerHTML:'', append(){}, appendChild(){}, addEventListener(){}, removeEventListener(){}, setAttribute(){}, removeAttribute(){}, focus(){}, blur(){}, getBoundingClientRect(){return {left:0,top:0,width:640,height:480};}, getContext(){return makeCtx();}, querySelector(){return makeEl('q');}, querySelectorAll(){return [];}}, {get(t,p){ if(p in t) return t[p]; if(p==='children') return []; return undefined; }, set(t,p,v){ t[p]=v; return true; }}); }
|
||||
const localStorage = { getItem(){return null;}, setItem(){}, removeItem(){} };
|
||||
const sandbox = { console, window: {}, document: { getElementById: makeEl, createElement: makeEl, querySelector(){return makeEl('q')}, querySelectorAll(){return []}, addEventListener(){}, body: makeEl('body')}, localStorage, performance: { now: () => 0 }, Math, Date, Map, Set, Array, Object, JSON, Number, String, Boolean, RegExp, Error, Uint8Array, Int8Array, Uint16Array, URL: {createObjectURL(){return ''}, revokeObjectURL(){}}, Blob: function(){}, Image: function(){}, FileReader: function(){}, requestAnimationFrame(){}, cancelAnimationFrame(){}, setTimeout, clearTimeout, structuredClone: global.structuredClone };
|
||||
sandbox.window = Object.assign(sandbox.window, { PixelIslandModules:{}, devicePixelRatio:1, addEventListener(){}, removeEventListener(){}, matchMedia(){return {matches:false, addEventListener(){}, removeEventListener(){}}}, requestAnimationFrame(){}, cancelAnimationFrame(){}, document: sandbox.document, localStorage: sandbox.localStorage });
|
||||
vm.createContext(sandbox);
|
||||
vm.runInContext(code, sandbox, {filename:'app.js'});
|
||||
const debug = sandbox.window.__PIXEL_DEBUG__;
|
||||
const assets = debug.state.assets.map(a => ({id:a.id,name:a.name,category:a.category,subtype:a.subtype,width:a.width,height:a.height,pixels:a.pixels,faces:a.faces,meta:a.meta,contentHash:a.contentHash}));
|
||||
fs.writeFileSync(path.join(__dirname,'assets.json'), JSON.stringify(assets,null,2));
|
||||
console.log('assets', assets.length);
|
||||
console.log(assets.map(a=>`${a.name} ${a.width}x${a.height} ${a.category}/${a.subtype}`).join('\n'));
|
||||
135
index.html
135
index.html
|
|
@ -34,7 +34,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<button id="createAccount" class="miniButton" type="button">Generate account</button>
|
||||
<small id="accountNote">Save + Place creates a local account for island publishing.</small>
|
||||
<small id="accountNote">Publish creates a local account for island publishing.</small>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -59,7 +59,6 @@
|
|||
<button class="tab active" data-tab="draw">Create</button>
|
||||
<button class="tab" data-tab="library">Collection</button>
|
||||
<button class="tab" data-tab="settings">Settings</button>
|
||||
<button class="tab devOnly" data-tab="data">Data</button>
|
||||
</nav>
|
||||
|
||||
<div class="drawerBody">
|
||||
|
|
@ -75,6 +74,7 @@
|
|||
<option value="human">Human</option>
|
||||
<option value="animal">Animal</option>
|
||||
<option value="bird">Bird</option>
|
||||
<option value="fish">Fish</option>
|
||||
<option value="nature">Nature</option>
|
||||
<option value="building">Building</option>
|
||||
<option value="ship">Ship</option>
|
||||
|
|
@ -91,7 +91,12 @@
|
|||
<div class="paintLayout">
|
||||
<div class="paintMainColumn">
|
||||
<canvas id="paintCanvas" width="512" height="512" aria-label="Pixel editor"></canvas>
|
||||
<div class="toolRow editorToolRow basicEditorTools">
|
||||
<div class="palettePanel">
|
||||
<div id="paletteGrid" class="paletteGrid" aria-label="Color palette"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drawControlPanel">
|
||||
<div class="toolRow editorToolRow basicEditorTools" aria-label="Basic draw tools">
|
||||
<button id="toolBrush" class="tool toolBrush active" title="B">Draw</button>
|
||||
<button id="toolErase" class="tool toolErase" title="E">Erase</button>
|
||||
<button id="toolFill" class="tool toolFill" title="F">Fill</button>
|
||||
|
|
@ -102,56 +107,53 @@
|
|||
<button id="undoPaint" class="tool toolUndo" type="button" disabled>↶ Undo</button>
|
||||
<button id="redoPaint" class="tool toolRedo" type="button" disabled>↷ Redo</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="palettePanel">
|
||||
<div id="paletteGrid" class="paletteGrid" aria-label="Color palette"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="advancedPanel">
|
||||
<div class="advancedPanelHeader">Advanced settings</div>
|
||||
<div id="advancedToolGroup" class="advancedToolGroup" hidden>
|
||||
<div class="toolRow advancedDrawRow">
|
||||
<button id="toolLine" class="tool toolLine advancedTool" title="L">Line</button>
|
||||
<button id="toolRect" class="tool toolRect advancedTool" title="R">Rect</button>
|
||||
<button id="toolSelect" class="tool toolSelect advancedTool" title="S">Select</button>
|
||||
<button id="toolDoor" class="tool toolDoor advancedTool buildingOnly">Door</button>
|
||||
<button id="clearPaint" class="tool danger advancedTool">Clear</button>
|
||||
</div>
|
||||
<div class="toolRow advancedActionRow">
|
||||
<button id="outlinePaint" class="tool toolOutline advancedTool" type="button">Outline</button>
|
||||
<button id="flipHorizontal" class="tool toolFlipH advancedTool" type="button">Flip H</button>
|
||||
<button id="flipVertical" class="tool toolFlipV advancedTool" type="button">Flip V</button>
|
||||
<button id="clearSelection" class="tool toolClearSel advancedTool" type="button" disabled>Clear Sel</button>
|
||||
<div class="advancedPanel">
|
||||
<div class="advancedPanelHeader">Advanced draw</div>
|
||||
<div id="advancedToolGroup" class="advancedToolGroup" hidden>
|
||||
<div class="toolRow advancedDrawRow">
|
||||
<button id="toolLine" class="tool toolLine advancedTool" title="L">Line</button>
|
||||
<button id="toolRect" class="tool toolRect advancedTool" title="R">Rect</button>
|
||||
<button id="toolSelect" class="tool toolSelect advancedTool" title="S">Select</button>
|
||||
<button id="toolDoor" class="tool toolDoor advancedTool buildingOnly">Door</button>
|
||||
<button id="clearPaint" class="tool danger advancedTool">Clear</button>
|
||||
</div>
|
||||
<div class="toolRow advancedActionRow">
|
||||
<button id="outlinePaint" class="tool toolOutline advancedTool" type="button">Outline</button>
|
||||
<button id="flipHorizontal" class="tool toolFlipH advancedTool" type="button">Flip H</button>
|
||||
<button id="flipVertical" class="tool toolFlipV advancedTool" type="button">Flip V</button>
|
||||
<button id="clearSelection" class="tool toolClearSel advancedTool" type="button" disabled>Clear Sel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="advancedRow extraAdvancedRow">
|
||||
<button id="toolLight" class="tool toolLight advancedOnly" hidden>Light</button>
|
||||
<button id="toolParticle" class="tool toolParticle advancedOnly" hidden>Particle</button>
|
||||
<button id="toolDepth" class="tool toolDepth advancedOnly" hidden>Depth</button>
|
||||
<button id="depthHigh" class="tool advancedOnly" type="button" hidden>High</button>
|
||||
<button id="depthLow" class="tool advancedOnly" type="button" hidden>Low</button>
|
||||
<div id="particleDirectionWrap" class="particleControl particleControlGroup advancedOnly" hidden>
|
||||
<label>Direction
|
||||
<select id="particleDirection">
|
||||
<option value="up" selected>↑ Up</option>
|
||||
<option value="down">↓ Down</option>
|
||||
<option value="left">← Left</option>
|
||||
<option value="right">→ Right</option>
|
||||
</select>
|
||||
</label>
|
||||
<small id="particleRangeStatus">Particle: paint cells like Light/Depth. Shift/right-click clears a cell.</small>
|
||||
</div>
|
||||
</div>
|
||||
<span id="advancedHint" class="hint" hidden></span>
|
||||
<input id="paintColor" type="color" value="#6bd06b" hidden />
|
||||
<div id="editHint" class="hint" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="advancedRow extraAdvancedRow">
|
||||
<button id="toolLight" class="tool toolLight advancedOnly" hidden>Light</button>
|
||||
<button id="toolParticle" class="tool toolParticle advancedOnly" hidden>Particle</button>
|
||||
<button id="toolDepth" class="tool toolDepth advancedOnly" hidden>Depth</button>
|
||||
<button id="depthHigh" class="tool advancedOnly" type="button" hidden>High</button>
|
||||
<button id="depthLow" class="tool advancedOnly" type="button" hidden>Low</button>
|
||||
<div id="particleDirectionWrap" class="particleControl particleControlGroup advancedOnly" hidden>
|
||||
<label>Direction
|
||||
<select id="particleDirection">
|
||||
<option value="up" selected>↑ Up</option>
|
||||
<option value="down">↓ Down</option>
|
||||
<option value="left">← Left</option>
|
||||
<option value="right">→ Right</option>
|
||||
</select>
|
||||
</label>
|
||||
<small id="particleRangeStatus">Particle: paint cells like Light/Depth. Shift/right-click clears a cell.</small>
|
||||
</div>
|
||||
</div>
|
||||
<span id="advancedHint" class="hint" hidden>Advanced reveals line, rectangle, selection, flip, light, depth, and particles.</span>
|
||||
<input id="paintColor" type="color" value="#6bd06b" hidden />
|
||||
<div id="editHint" class="hint">Palette color is used for pixels, lights, particles, and depth marks. Shortcuts: B/E/F/I/L/R/S, Ctrl/Cmd+Z, arrows move a selection.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card stack summonCard">
|
||||
<div class="cardTitle">Finish</div>
|
||||
<div class="actionRow finishActions">
|
||||
<button id="saveAndPlace" class="primary bigPrimary"><span>Save + Place on Island</span><span id="finishQuotaBadge" class="quotaBadge finishQuotaBadge" aria-live="polite"></span></button>
|
||||
<button id="saveAndPlace" class="primary bigPrimary"><span>Publish</span><span id="finishQuotaBadge" class="quotaBadge finishQuotaBadge" aria-live="polite"></span></button>
|
||||
<button id="saveAsset" class="secondary">Save to Collection</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -164,7 +166,7 @@
|
|||
<div class="cardTitle">Collection</div>
|
||||
<button id="showHiddenAssets" class="tool" type="button">Hidden</button>
|
||||
</div>
|
||||
<p class="hint">Your works are permanent here. The island is a temporary exhibition: place your own new or past works; remix only copies lineage and pixels to the canvas.</p>
|
||||
<p class="hint">Your works are permanent here. The island is a temporary exhibition.</p>
|
||||
<div id="hiddenAssetPanel" class="hiddenAssetPanel" hidden>
|
||||
<div class="cardTitle">Hidden</div>
|
||||
<div id="hiddenAssetList" class="assetList compactAssetList"></div>
|
||||
|
|
@ -190,36 +192,6 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section id="tab-data" class="tabPanel devOnly">
|
||||
<div class="card stack">
|
||||
<div class="cardTitle">Local save</div>
|
||||
<p class="hint">Works are durable collection works; island placements are temporary exhibition objects. Export JSON if you want to move or test a local world.</p>
|
||||
<div class="actionRow wrap">
|
||||
<button id="exportData">Export full JSON</button>
|
||||
<button id="exportCompact">Export compact</button>
|
||||
<button id="exportSnapshot">Export snapshot</button>
|
||||
<button id="exportAssetBundle">Export asset bundle</button>
|
||||
<button id="importData">Import JSON</button>
|
||||
<button id="exportPng" type="button">Export PNG</button>
|
||||
<button id="importPng" type="button">Import PNG</button>
|
||||
<input id="pngImportInput" type="file" accept="image/png,image/*" hidden />
|
||||
<button id="resetAll" class="danger">Reset all</button>
|
||||
</div>
|
||||
<div id="syncStats" class="syncStats"></div>
|
||||
<textarea id="dataBox" rows="10" placeholder="Exported JSON appears here."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="card stack">
|
||||
<div class="cardTitle">Phase 5 guardrails</div>
|
||||
<p class="hint">Local pre-server checks for object volume, orphaned placements, terrain compatibility, hidden objects, and report logs.</p>
|
||||
<div id="guardrailStats" class="syncStats"></div>
|
||||
<div class="actionRow wrap">
|
||||
<button id="validateWorld" type="button">Validate world</button>
|
||||
<button id="exportModerationReport" type="button">Export moderation report</button>
|
||||
<button id="clearReports" class="danger" type="button">Clear reports</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -245,6 +217,17 @@
|
|||
</div>
|
||||
<div id="toast" class="toast" hidden></div>
|
||||
|
||||
<div id="confirmDialog" class="reportDialog" hidden>
|
||||
<div class="reportCard" role="dialog" aria-modal="true" aria-labelledby="confirmTitle">
|
||||
<div id="confirmTitle" class="cardTitle">Delete work</div>
|
||||
<p id="confirmMessage" class="hint"></p>
|
||||
<div class="actionRow wrap reportActions">
|
||||
<button id="confirmOk" class="danger" type="button">Delete</button>
|
||||
<button id="confirmCancel" type="button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="reportDialog" class="reportDialog" hidden>
|
||||
<div class="reportCard" role="dialog" aria-modal="true" aria-labelledby="reportTitle">
|
||||
<div id="reportTitle" class="cardTitle">Report object</div>
|
||||
|
|
|
|||
|
|
@ -420,7 +420,8 @@
|
|||
sync: state.sync || { lastEventId: null },
|
||||
worldMode: state.worldMode === 'shared' ? 'shared' : 'local',
|
||||
serverSync: state.serverSync || { lastServerEventId: null, pendingCommands: [] },
|
||||
tombstones: state.tombstones || { assets: {}, objects: {} }
|
||||
tombstones: state.tombstones || { assets: {}, objects: {} },
|
||||
deletedSeedAssetNames: Array.isArray(state.deletedSeedAssetNames) ? state.deletedSeedAssetNames : []
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -445,7 +446,8 @@
|
|||
sync: input.sync || { lastEventId: null },
|
||||
worldMode: input.worldMode === 'shared' ? 'shared' : 'local',
|
||||
serverSync: input.serverSync || { lastServerEventId: null, pendingCommands: [] },
|
||||
tombstones: input.tombstones || { assets: {}, objects: {} }
|
||||
tombstones: input.tombstones || { assets: {}, objects: {} },
|
||||
deletedSeedAssetNames: Array.isArray(input.deletedSeedAssetNames) ? input.deletedSeedAssetNames : []
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,18 @@ class WorldPolicyTest(unittest.TestCase):
|
|||
self.assertIn("obj-a", state["tombstones"]["objects"])
|
||||
self.assertEqual(len(state["dynamicSummons"]), 1)
|
||||
|
||||
def test_non_owner_can_delete_asset_and_all_placements(self):
|
||||
state = base_state()
|
||||
allowed, _ = can_delete_asset(state, {"id": "alice"}, "asset-b")
|
||||
self.assertTrue(allowed)
|
||||
result = apply_command(state, {"id": "alice"}, {"type": "asset.delete", "assetId": "asset-b"}, at=100)
|
||||
self.assertTrue(result["accepted"])
|
||||
self.assertTrue(apply_server_event(state, result["event"]))
|
||||
self.assertNotIn("asset-b", [asset["id"] for asset in state["assets"]])
|
||||
self.assertEqual(state["dynamicSummons"], [])
|
||||
self.assertIn("asset-b", state["tombstones"]["assets"])
|
||||
self.assertIn("dyn-b", state["tombstones"]["objects"])
|
||||
|
||||
|
||||
def test_world_phase_event_updates_authoritative_clock(self):
|
||||
state = base_state()
|
||||
|
|
|
|||
|
|
@ -88,9 +88,7 @@ def can_delete_asset(state: MutableMapping[str, Any], account: MutableMapping[st
|
|||
if not asset:
|
||||
return False, {"reason": "asset_not_found"}
|
||||
owner = str(asset.get("ownerAccountId") or "")
|
||||
if actor == owner or is_admin(account):
|
||||
return True, {"reason": None, "ownerAccountId": owner}
|
||||
return False, {"reason": "not_asset_owner", "ownerAccountId": owner}
|
||||
return True, {"reason": None, "ownerAccountId": owner}
|
||||
|
||||
|
||||
def is_object_tombstoned(state: MutableMapping[str, Any], object_id: str, version: int = 0) -> bool:
|
||||
|
|
@ -277,7 +275,7 @@ def apply_command(state: MutableMapping[str, Any], account: MutableMapping[str,
|
|||
version = int(asset.get("version") or 0) + 1
|
||||
object_tombstones: List[Dict[str, Any]] = []
|
||||
for _, obj in iter_objects(state):
|
||||
if str(obj.get("assetId") or "") == asset_id and (is_admin(account) or owner_of_object(state, obj) == actor):
|
||||
if str(obj.get("assetId") or "") == asset_id:
|
||||
object_tombstones.append({"id": str(obj["id"]), "deletedAt": at or now_ms(), "deletedBy": actor, "version": int(obj.get("version") or 0) + 1})
|
||||
tombstone = {"id": asset_id, "deletedAt": at or now_ms(), "deletedBy": actor, "version": version}
|
||||
return _accepted(_server_event("asset.delete", actor, {"assetId": asset_id, "assetVersion": version, "tombstone": tombstone, "objectTombstones": object_tombstones}, at))
|
||||
|
|
|
|||
436
styles.css
436
styles.css
|
|
@ -97,7 +97,6 @@ button { -webkit-tap-highlight-color: transparent; }
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.authorCard {
|
||||
width: 190px;
|
||||
border: 2px solid var(--line);
|
||||
|
|
@ -423,7 +422,6 @@ input[type="checkbox"] { width: auto; margin: 0 8px 0 0; }
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
.editorToolRow { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||
.editorHistoryRow { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||
.editorMoveRow { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||
|
|
@ -475,16 +473,53 @@ button:disabled, .tool:disabled { opacity: .45; cursor: not-allowed; transform:
|
|||
.lineageNote { min-height: 18px; color: var(--muted); font-size: 12px; }
|
||||
|
||||
.assetList { display: grid; gap: 10px; }
|
||||
.collectionToolbar {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
border: 2px solid rgba(36,48,68,.22);
|
||||
background: #f8fbff;
|
||||
}
|
||||
.collectionSummary {
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
color: var(--ink);
|
||||
}
|
||||
.collectionSearch {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 8px 9px;
|
||||
border: 2px solid rgba(36,48,68,.28);
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
.collectionFilters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.collectionFilter {
|
||||
flex: 0 0 auto;
|
||||
padding: 6px 8px;
|
||||
border: 2px solid rgba(36,48,68,.24);
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
font-size: 11px;
|
||||
}
|
||||
.collectionFilter.active {
|
||||
border-color: var(--line);
|
||||
background: #dff4ff;
|
||||
}
|
||||
.assetCard {
|
||||
display: grid;
|
||||
grid-template-columns: 68px 1fr;
|
||||
gap: 10px;
|
||||
border: 2px solid var(--line);
|
||||
background: #fff9e8;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
box-shadow: 3px 3px 0 rgba(36,48,68,.12);
|
||||
box-shadow: 0 2px 0 rgba(36,48,68,.08);
|
||||
}
|
||||
.assetCard.selected { background: #e6ffef; }
|
||||
.assetCard.selected { background: #eefbf3; border-color: #3f7d58; }
|
||||
.assetPreview {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
|
@ -494,6 +529,25 @@ button:disabled, .tool:disabled { opacity: .45; cursor: not-allowed; transform:
|
|||
}
|
||||
.assetMeta strong { display: block; font-size: 14px; }
|
||||
.assetMeta span { display: block; margin-top: 3px; color: var(--muted); font-size: 12px; line-height: 1.3; }
|
||||
.assetStatusRow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.assetMeta .assetStatus {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
margin-top: 0;
|
||||
padding: 2px 6px;
|
||||
border: 1px solid rgba(36,48,68,.2);
|
||||
background: #f5f5f5;
|
||||
color: #3d4652;
|
||||
font-size: 10px;
|
||||
font-weight: 900;
|
||||
}
|
||||
.assetMeta .assetStatus.placed { background: #e3f8e8; color: #28633e; }
|
||||
.assetMeta .assetStatus.role { background: #eef2ff; color: #3e4f78; }
|
||||
.assetActions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -528,8 +582,6 @@ button:disabled, .tool:disabled { opacity: .45; cursor: not-allowed; transform:
|
|||
.legend .grass { background: #8bce76; }
|
||||
.legend .highland { background: #a7be80; }
|
||||
|
||||
#dataBox { min-height: 160px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; }
|
||||
|
||||
.toast {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
|
|
@ -554,7 +606,6 @@ button:disabled, .tool:disabled { opacity: .45; cursor: not-allowed; transform:
|
|||
.toolRow { grid-template-columns: 44px repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
/* refinements */
|
||||
.toolHud {
|
||||
top: 142px;
|
||||
right: 18px;
|
||||
|
|
@ -670,11 +721,9 @@ button:disabled, .tool:disabled { opacity: .45; cursor: not-allowed; transform:
|
|||
.sideSwitcher button { white-space: nowrap; }
|
||||
.heroEditor .hint { line-height: 1.35; }
|
||||
|
||||
|
||||
.compactEditorTop { margin-bottom: 4px; min-height: 0; }
|
||||
body, button, input, select, textarea { font-smooth: never; -webkit-font-smoothing: none; }
|
||||
|
||||
/* latest layout fixes */
|
||||
.studioDrawer { width: min(600px, calc(100vw - 24px)); }
|
||||
.paintLayout {
|
||||
display: grid;
|
||||
|
|
@ -707,7 +756,6 @@ body, button, input, select, textarea { font-smooth: never; -webkit-font-smoothi
|
|||
.paletteGrid { grid-template-columns: repeat(10, 1fr); max-height: none; }
|
||||
}
|
||||
|
||||
/* Selection bubble + outline controls */
|
||||
.palettePanel { display: grid; gap: 8px; }
|
||||
.selectionBubble {
|
||||
position: absolute;
|
||||
|
|
@ -790,7 +838,6 @@ body, button, input, select, textarea { font-smooth: never; -webkit-font-smoothi
|
|||
background: var(--sun);
|
||||
}
|
||||
|
||||
/* Pixel-ish UI refinements */
|
||||
body, button, input, select, textarea {
|
||||
font-family: "Courier New", "Lucida Console", Monaco, monospace;
|
||||
letter-spacing: .02em;
|
||||
|
|
@ -833,7 +880,6 @@ button, .tab, .tool, .iconButton, .cardTitle, .logo {
|
|||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Simplified current UI */
|
||||
body, button, input, select, textarea {
|
||||
font-family: "MS Gothic", "Osaka-Mono", "DotumChe", "Courier New", ui-monospace, monospace;
|
||||
letter-spacing: .02em;
|
||||
|
|
@ -851,10 +897,10 @@ body, button, input, select, textarea {
|
|||
width: 100%;
|
||||
text-align: left;
|
||||
margin: 10px 0 6px;
|
||||
cursor: pointer;
|
||||
cursor: default;
|
||||
}
|
||||
.librarySectionTitle::after {
|
||||
content: " click: genre";
|
||||
content: "";
|
||||
float: right;
|
||||
color: var(--muted);
|
||||
font-size: 10px;
|
||||
|
|
@ -890,7 +936,6 @@ body, button, input, select, textarea {
|
|||
.advancedRow { grid-template-columns: 1fr 1fr 1fr; }
|
||||
}
|
||||
|
||||
|
||||
.syncStats {
|
||||
padding: 8px 10px;
|
||||
border-radius: 12px;
|
||||
|
|
@ -900,7 +945,6 @@ body, button, input, select, textarea {
|
|||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* Phase 5: keep advanced depth controls inside the drawer. */
|
||||
.advancedRow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -920,9 +964,6 @@ body, button, input, select, textarea {
|
|||
flex: 1 0 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
#guardrailStats {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.bubbleActions {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
|
@ -931,7 +972,6 @@ body, button, input, select, textarea {
|
|||
grid-column: span 2;
|
||||
}
|
||||
|
||||
/* Phase 5 follow-up: object hidden list + report dialog + font priority. */
|
||||
body, button, input, select, textarea {
|
||||
font-family: "3x5 MT Pixel Font", "Press Start 2P", "PixelMplus10", "DotGothic16", "MS Gothic", "Osaka-Mono", "Courier New", ui-monospace, monospace;
|
||||
}
|
||||
|
|
@ -964,12 +1004,10 @@ body, button, input, select, textarea {
|
|||
.reportActions button { min-width: 0; }
|
||||
.assetActions button { min-width: 0; }
|
||||
|
||||
|
||||
.toggleList { display:flex; flex-direction:column; gap:10px; }
|
||||
.checkRow { display:flex; align-items:center; gap:10px; font-size:13px; color:#243044; }
|
||||
.checkRow input { width:16px; height:16px; }
|
||||
|
||||
/* Phase 5 UI polish: larger readable controls while keeping palette code labels small. */
|
||||
body, button, input, select, textarea { font-size: 15px; }
|
||||
.logo { font-size: 24px; }
|
||||
.subline { font-size: 14px; }
|
||||
|
|
@ -1001,8 +1039,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.displayLimitField input { font-size: 16px; min-height: 36px; width: 110px; }
|
||||
.rotationBadge { font-size: 13px; color: #4d5b72; }
|
||||
|
||||
|
||||
/* exhibition/account polish */
|
||||
.authorCard { min-width: 210px; }
|
||||
.authorCard small { display:block; margin-top:6px; font-size:12px; line-height:1.35; opacity:.82; }
|
||||
.miniButton { margin-top:6px; padding:6px 8px; border:2px solid #243044; background:#fff3d9; box-shadow:2px 2px 0 rgba(36,48,68,.22); cursor:pointer; font-size:12px; }
|
||||
|
|
@ -1016,8 +1052,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.hiddenReasonText { color:#6f2f3b; font-weight:700; }
|
||||
.exhibitionNote { padding:8px 10px; background:#fff3d9; border:2px solid rgba(36,48,68,.2); margin-bottom:8px; font-size:14px; line-height:1.45; }
|
||||
|
||||
|
||||
/* compact account bar / remove right-side HUD */
|
||||
.toolHud { display: none !important; }
|
||||
.topHud { min-height: 50px; padding: 8px 12px; gap: 10px; align-items: center; }
|
||||
.authorCard {
|
||||
|
|
@ -1043,7 +1077,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.authorCard small { max-width: 100%; }
|
||||
}
|
||||
|
||||
/* 2026 polish pass: make the island the visual default, keep creation and collection one click away. */
|
||||
.islandTopBar {
|
||||
top: 12px;
|
||||
left: 50%;
|
||||
|
|
@ -1084,9 +1117,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.topButton.primary { background: var(--mint) !important; }
|
||||
.utilityControl { display: none !important; }
|
||||
.edgeAdd { display: none !important; }
|
||||
.devOnly { display: none !important; }
|
||||
|
||||
/* Beginner-first editor: show only Pencil/Erase/Fill/Pick + Undo/Redo until Advanced is enabled. */
|
||||
.advancedTool { display: none !important; }
|
||||
.studioDrawer.advancedTools .advancedTool { display: inline-flex !important; }
|
||||
.studioDrawer.advancedTools .toolRow .advancedTool { display: block !important; }
|
||||
|
|
@ -1094,10 +1124,10 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.editorHistoryRow { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
|
||||
.studioDrawer.advancedTools .editorToolRow { grid-template-columns: repeat(5, minmax(0, 1fr)) !important; }
|
||||
.studioDrawer.advancedTools .editorHistoryRow { grid-template-columns: repeat(6, minmax(0, 1fr)) !important; }
|
||||
#editHint { opacity: .78; }
|
||||
#editHint::after { content: " Advanced reveals line, rectangle, selection, flip, light, depth, and particles."; }
|
||||
#editHint { display: none !important; }
|
||||
#advancedHint { display: none !important; }
|
||||
#editHint::after { content: none; }
|
||||
|
||||
/* Let the island breathe under the lighter navigation. */
|
||||
#worldCanvas { background: #8dd7ff; }
|
||||
.selectionBubble {
|
||||
border-width: 2px !important;
|
||||
|
|
@ -1110,7 +1140,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.topButton { padding: 7px 8px !important; }
|
||||
}
|
||||
|
||||
/* v8 account, editor, and placement refinements */
|
||||
.islandClickHint {
|
||||
position: fixed;
|
||||
left: 14px;
|
||||
|
|
@ -1240,7 +1269,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.advancedDrawRow, .advancedActionRow { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
|
||||
}
|
||||
|
||||
/* v9 menu and particle-range refinements */
|
||||
.cuteMenuButton {
|
||||
flex: 0 0 auto;
|
||||
width: 38px;
|
||||
|
|
@ -1285,7 +1313,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.particleRangeButtons { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
|
||||
/* v10 bottom DRAW access and remix bottom sheet */
|
||||
.drawDockButton {
|
||||
position: fixed;
|
||||
z-index: 28;
|
||||
|
|
@ -1379,8 +1406,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.islandClickHint { display: none; }
|
||||
}
|
||||
|
||||
|
||||
/* v15 analog day-night clock */
|
||||
.analogClock {
|
||||
--clock-rotate: 0deg;
|
||||
position: fixed;
|
||||
|
|
@ -1460,8 +1485,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.analogClock { top: 82px; right: 10px; transform: scale(.86); transform-origin: top right; }
|
||||
}
|
||||
|
||||
|
||||
/* v16 layout refinements */
|
||||
.inlineField {
|
||||
display: grid;
|
||||
grid-template-columns: 86px minmax(0, 1fr);
|
||||
|
|
@ -1496,7 +1519,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.analogClock { top: 8px; right: 10px; transform: scale(.72); transform-origin: top right; }
|
||||
}
|
||||
|
||||
/* Settings tab alignment */
|
||||
#tab-settings .card {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -1523,7 +1545,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
/* v17 requested UI tightening */
|
||||
.drawQuotaBadge {
|
||||
position: fixed;
|
||||
z-index: 27;
|
||||
|
|
@ -1617,7 +1638,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.paletteGrid { grid-template-columns: repeat(8, minmax(26px, 1fr)) !important; }
|
||||
}
|
||||
|
||||
/* v18 requested UI + palette corrections */
|
||||
.drawDockButton,
|
||||
#saveAndPlace.bigPrimary {
|
||||
display: inline-flex !important;
|
||||
|
|
@ -1663,7 +1683,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.paletteGrid { grid-template-columns: repeat(10, minmax(20px, 1fr)) !important; }
|
||||
}
|
||||
|
||||
/* v19 requested create-layout cleanup */
|
||||
.studioDrawer {
|
||||
width: min(1060px, calc(100vw - 24px)) !important;
|
||||
}
|
||||
|
|
@ -1795,7 +1814,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
#editHint { max-width: none; }
|
||||
}
|
||||
|
||||
/* v20 requested refinements */
|
||||
.studioDrawer {
|
||||
width: min(424px, calc(100vw - 20px)) !important;
|
||||
}
|
||||
|
|
@ -1864,8 +1882,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
.creationMetaRow { grid-template-columns: minmax(0, 1fr) 58px 12px 58px !important; }
|
||||
}
|
||||
|
||||
/* v21 correction: keep palette to the right of the canvas on desktop.
|
||||
The previous left-drawer shrink made the editor too narrow and forced the palette below. */
|
||||
@media (min-width: 761px) {
|
||||
.studioDrawer {
|
||||
width: min(960px, calc(100vw - 20px)) !important;
|
||||
|
|
@ -1897,8 +1913,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
}
|
||||
}
|
||||
|
||||
/* v22 requested: compact left drawer + Create tab header layout
|
||||
Desktop drawer is capped to roughly 40% of the viewport instead of occupying most of the screen. */
|
||||
@media (min-width: 761px) {
|
||||
.studioDrawer {
|
||||
width: clamp(440px, 40vw, 620px) !important;
|
||||
|
|
@ -2022,7 +2036,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
}
|
||||
}
|
||||
|
||||
/* v23 requested: canvas-surroundings redesign + palette cleanup */
|
||||
:root {
|
||||
--editorSurface: #efe9dc;
|
||||
--editorPanel: #f6f1e5;
|
||||
|
|
@ -2155,18 +2168,14 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
}
|
||||
|
||||
.advancedPanel {
|
||||
border: 5px solid var(--editorAccent) !important;
|
||||
background: #ececeb !important;
|
||||
padding: 16px 16px 12px !important;
|
||||
margin-top: 2px !important;
|
||||
border: 0 !important;
|
||||
background: transparent !important;
|
||||
padding: 8px 0 0 !important;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.advancedPanelHeader {
|
||||
color: #7f7d7a !important;
|
||||
font-size: clamp(28px, 3vw, 54px) !important;
|
||||
line-height: 1.05 !important;
|
||||
letter-spacing: .01em !important;
|
||||
margin: 0 0 10px !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.advancedPanel .advancedDrawRow,
|
||||
|
|
@ -2273,7 +2282,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
}
|
||||
}
|
||||
|
||||
/* v24: align canvas, primary tools, and palette exactly as one editor block */
|
||||
.paintMainColumn {
|
||||
display: grid !important;
|
||||
grid-template-rows: auto auto auto !important;
|
||||
|
|
@ -2305,7 +2313,6 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
}
|
||||
}
|
||||
|
||||
/* v25: requested layout-only cleanup. Keep existing button design; move controls around the canvas. */
|
||||
:root {
|
||||
--createGap: 12px;
|
||||
}
|
||||
|
|
@ -2526,3 +2533,308 @@ body, button, input, select, textarea { font-size: 15px; }
|
|||
font-weight: 900 !important;
|
||||
}
|
||||
}
|
||||
|
||||
#tab-draw .card.editorCard.heroEditor {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.creationMetaRow {
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.creationMetaRow .metaInput,
|
||||
.creationMetaRow select.metaInput {
|
||||
height: 28px !important;
|
||||
min-height: 28px !important;
|
||||
padding: 3px 7px !important;
|
||||
font-size: 12px !important;
|
||||
line-height: 1.1 !important;
|
||||
border-width: 2px !important;
|
||||
}
|
||||
|
||||
.creationMetaRow .dimensionSeparator {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
|
||||
.paintLayout {
|
||||
align-items: start !important;
|
||||
}
|
||||
|
||||
.drawControlPanel {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
align-self: start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.drawControlPanel .basicEditorTools,
|
||||
.drawControlPanel .editorUtilityRow {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.drawControlPanel .advancedPanel {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#saveAndPlace.bigPrimary {
|
||||
min-width: 0 !important;
|
||||
max-width: 100% !important;
|
||||
white-space: normal !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
line-height: 1.15 !important;
|
||||
flex-wrap: wrap !important;
|
||||
row-gap: 3px !important;
|
||||
padding-left: 8px !important;
|
||||
padding-right: 8px !important;
|
||||
}
|
||||
|
||||
#saveAndPlace.bigPrimary > span:first-child {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
#saveAndPlace .finishQuotaBadge {
|
||||
max-width: 100% !important;
|
||||
white-space: normal !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
line-height: 1.1 !important;
|
||||
font-size: 9px !important;
|
||||
padding: 2px 5px !important;
|
||||
}
|
||||
|
||||
.collectionViewTabs.tabs {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
border: 2px solid var(--line);
|
||||
box-shadow: 3px 3px 0 rgba(36,48,68,.12);
|
||||
margin: 2px 0 0;
|
||||
padding: 6px !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
.collectionViewTab.tab {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 7px 5px !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
@media (min-width: 761px) {
|
||||
.studioDrawer {
|
||||
width: clamp(520px, 42vw, 760px) !important;
|
||||
}
|
||||
|
||||
.creationMetaRow {
|
||||
grid-template-columns: minmax(120px, 1.35fr) clamp(42px, 4vw, 58px) 12px clamp(42px, 4vw, 58px) minmax(82px, .9fr) !important;
|
||||
gap: 8px !important;
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.paintLayout {
|
||||
grid-template-columns: minmax(0, 1fr) clamp(148px, 12vw, 210px) !important;
|
||||
gap: 12px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.paintMainColumn {
|
||||
grid-template-rows: auto auto !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.paintMainColumn #paintCanvas {
|
||||
max-height: min(58vh, 560px) !important;
|
||||
}
|
||||
|
||||
.paintMainColumn .palettePanel {
|
||||
min-height: 0 !important;
|
||||
align-self: start !important;
|
||||
padding: 7px !important;
|
||||
}
|
||||
|
||||
.paintMainColumn .paletteGrid {
|
||||
grid-template-columns: repeat(10, minmax(0, 1fr)) !important;
|
||||
gap: 3px !important;
|
||||
}
|
||||
|
||||
.paintMainColumn .paletteSwatch,
|
||||
.paintMainColumn .paletteGrid button {
|
||||
border-width: 2px !important;
|
||||
}
|
||||
|
||||
.drawControlPanel .basicEditorTools {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
.drawControlPanel .editorUtilityRow {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
.drawControlPanel .basicEditorTools .tool,
|
||||
.drawControlPanel .editorUtilityRow .tool {
|
||||
min-height: 36px !important;
|
||||
padding: 7px 6px !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.drawControlPanel .advancedPanel {
|
||||
padding: 8px !important;
|
||||
max-height: calc(100vh - 330px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.drawControlPanel .advancedPanelHeader {
|
||||
display: block !important;
|
||||
font-size: 12px !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.advancedPanel .advancedDrawRow,
|
||||
.advancedPanel .advancedActionRow,
|
||||
.advancedPanel .extraAdvancedRow {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
|
||||
gap: 5px !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.advancedPanel .tool {
|
||||
min-height: 30px !important;
|
||||
padding: 5px 4px !important;
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.advancedPanel .particleControlGroup {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 761px) and (max-height: 780px) {
|
||||
.drawerHead {
|
||||
padding-top: 4px !important;
|
||||
padding-bottom: 3px !important;
|
||||
}
|
||||
.drawerHead p {
|
||||
display: none;
|
||||
}
|
||||
.tabs {
|
||||
padding-top: 4px !important;
|
||||
padding-bottom: 4px !important;
|
||||
}
|
||||
.drawControlPanel .advancedPanel {
|
||||
max-height: calc(100vh - 285px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.creationMetaRow .metaInput,
|
||||
.creationMetaRow select.metaInput {
|
||||
height: 26px !important;
|
||||
min-height: 26px !important;
|
||||
font-size: 12px !important;
|
||||
padding: 3px 6px !important;
|
||||
}
|
||||
|
||||
.paintLayout {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 10px !important;
|
||||
}
|
||||
|
||||
.paintMainColumn {
|
||||
grid-template-rows: auto auto !important;
|
||||
}
|
||||
|
||||
.paintMainColumn .paletteGrid {
|
||||
grid-template-columns: repeat(8, minmax(0, 1fr)) !important;
|
||||
}
|
||||
|
||||
.drawControlPanel {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.drawControlPanel .advancedPanel {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
#saveAndPlace.bigPrimary {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.collectionViewTab.tab {
|
||||
font-size: 11px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 761px) {
|
||||
.studioDrawer.advancedTools .drawControlPanel .editorToolRow,
|
||||
.studioDrawer.advancedTools .drawControlPanel .basicEditorTools,
|
||||
.studioDrawer.advancedTools .drawControlPanel .editorUtilityRow {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
}
|
||||
|
||||
:root { --drawer-z: 70; --bubble-z: 34; }
|
||||
[hidden], .advancedToolGroup[hidden], .advancedOnly[hidden], #particleDirectionWrap[hidden] { display: none !important; }
|
||||
.studioDrawer { z-index: var(--drawer-z) !important; }
|
||||
.selectionBubble { z-index: var(--bubble-z) !important; }
|
||||
.studioDrawer:not(.advancedTools) .advancedPanel { display: none !important; }
|
||||
|
||||
.tool {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
gap: 6px !important;
|
||||
border: 2px solid var(--line, #243044) !important;
|
||||
color: var(--line, #243044) !important;
|
||||
background: var(--toolBg, #f7f4ea) !important;
|
||||
box-shadow: 2px 2px 0 rgba(36,48,68,.14) !important;
|
||||
}
|
||||
.tool:where(:not(:disabled)):hover { transform: translate(-1px, -1px); box-shadow: 3px 3px 0 rgba(36,48,68,.16) !important; }
|
||||
.tool.active { color: #fffdf5 !important; background: var(--toolActiveBg, var(--toolAccent, #243044)) !important; }
|
||||
.toolIcon, .toolPixelIcon, .tool .pixelIcon { display:inline-block; width:14px; height:14px; flex:0 0 14px; image-rendering:pixelated; }
|
||||
.toolBrush { --toolBg:#dcfce7; --toolAccent:#16a34a; --toolActiveBg:#15803d; }
|
||||
.toolErase { --toolBg:#fee2e2; --toolAccent:#ef4444; --toolActiveBg:#dc2626; }
|
||||
.toolFill { --toolBg:#fef3c7; --toolAccent:#f59e0b; --toolActiveBg:#d97706; }
|
||||
.toolPick { --toolBg:#e0f2fe; --toolAccent:#0284c7; --toolActiveBg:#0369a1; }
|
||||
.toolAdvanced { --toolBg:#ede9fe; --toolAccent:#7c3aed; --toolActiveBg:#6d28d9; }
|
||||
.toolUndo, .toolRedo { --toolBg:#f3f4f6; --toolAccent:#64748b; --toolActiveBg:#475569; }
|
||||
.toolLine, .toolDepth { --toolBg:#dbeafe; --toolAccent:#2563eb; --toolActiveBg:#1d4ed8; }
|
||||
.toolRect, .toolParticle { --toolBg:#ccfbf1; --toolAccent:#0d9488; --toolActiveBg:#0f766e; }
|
||||
.toolSelect { --toolBg:#fae8ff; --toolAccent:#c026d3; --toolActiveBg:#a21caf; }
|
||||
.toolDoor { --toolBg:#ffedd5; --toolAccent:#c2410c; --toolActiveBg:#9a3412; }
|
||||
.toolOutline { --toolBg:#e0e7ff; --toolAccent:#4f46e5; --toolActiveBg:#4338ca; }
|
||||
.toolFlipH, .toolFlipV { --toolBg:#fce7f3; --toolAccent:#db2777; --toolActiveBg:#be185d; }
|
||||
.toolClearSel { --toolBg:#f1f5f9; --toolAccent:#475569; --toolActiveBg:#334155; }
|
||||
.toolLight { --toolBg:#fef9c3; --toolAccent:#ca8a04; --toolActiveBg:#a16207; }
|
||||
.tool.danger, #clearPaint { --toolBg:#ffe4e6; --toolAccent:#e11d48; --toolActiveBg:#be123c; }
|
||||
#toolPick.toolPick, #redoPaint.toolRedo { border-right: 2px solid var(--line, #243044) !important; }
|
||||
|
||||
@media (min-width: 761px) {
|
||||
.studioDrawer.advancedTools { width: clamp(560px, 48vw, 720px) !important; max-width: calc(100vw - 20px) !important; }
|
||||
.studioDrawer.advancedTools .paintLayout { grid-template-columns: minmax(0, 1fr) clamp(220px, 18vw, 270px) !important; gap: 10px !important; }
|
||||
.studioDrawer.advancedTools .paintMainColumn #paintCanvas { max-height: min(58vh, 560px) !important; max-width: none !important; justify-self: stretch !important; }
|
||||
.studioDrawer.advancedTools .palettePanel { width: min(100%, 620px) !important; padding: 6px !important; justify-self: stretch !important; }
|
||||
.studioDrawer.advancedTools .paletteGrid { grid-template-columns: repeat(14, minmax(16px, 1fr)) !important; gap: 3px !important; }
|
||||
.studioDrawer.advancedTools .drawControlPanel { display:grid !important; grid-template-columns: minmax(76px, 90px) minmax(120px, 1fr) !important; grid-template-rows:auto auto !important; gap:7px 8px !important; }
|
||||
.studioDrawer.advancedTools .basicEditorTools { grid-column:1 !important; grid-row:1 !important; }
|
||||
.studioDrawer.advancedTools .editorUtilityRow { grid-column:1 !important; grid-row:2 !important; }
|
||||
.studioDrawer.advancedTools .advancedPanel { grid-column:2 !important; grid-row:1 / span 2 !important; display:grid !important; padding:7px !important; overflow:visible !important; }
|
||||
.studioDrawer.advancedTools .advancedDrawRow,
|
||||
.studioDrawer.advancedTools .advancedActionRow,
|
||||
.studioDrawer.advancedTools .extraAdvancedRow { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap:5px !important; }
|
||||
.studioDrawer.advancedTools .advancedPanel :is(.tool, select, .particleControl) { min-height:30px !important; padding:5px !important; font-size:10px !important; }
|
||||
.studioDrawer.advancedTools .particleControlGroup { grid-column:1 / -1 !important; }
|
||||
}
|
||||
|
||||
.collectionToolbar { display:flex !important; align-items:center !important; gap:6px !important; flex-wrap:nowrap !important; padding:7px !important; overflow:hidden !important; }
|
||||
.collectionSummary { flex:0 0 auto !important; white-space:nowrap !important; font-size:11px !important; }
|
||||
.collectionSearch { flex:0 0 108px !important; width:108px !important; min-width:92px !important; min-height:26px !important; padding:3px 6px !important; font-size:10px !important; }
|
||||
.collectionFilters { flex:1 1 auto !important; min-width:0 !important; display:flex !important; flex-wrap:nowrap !important; gap:5px !important; overflow-x:auto !important; overflow-y:hidden !important; scrollbar-width:thin; }
|
||||
.collectionFilter { flex:0 0 auto !important; padding:4px 7px !important; font-size:10px !important; white-space:nowrap !important; }
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.collectionToolbar { align-items:stretch !important; overflow-x:auto !important; }
|
||||
.collectionSummary { display:none !important; }
|
||||
.collectionSearch { flex-basis:104px !important; width:104px !important; }
|
||||
.studioDrawer { z-index: var(--drawer-z) !important; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue