This commit is contained in:
33333-33333 2026-06-01 21:17:09 +09:00
commit 89a32dc28e
4 changed files with 490 additions and 679 deletions

View file

@ -1,25 +1,17 @@
# Pixel Island Summoner
Local prototype. Open `index.html` with Live Server, or run:
Local Python rewrite of the original JavaScript browser prototype.
## Run
```bash
python3 -m http.server 5500
python app.py
```
Then open `http://localhost:5500`.
## Current controls
- Open Pixel Studio with the large `+` button.
- Left drag on the pixel canvas: draw.
- Right drag or middle drag on the pixel canvas: pan.
- Mouse wheel on the pixel canvas: zoom.
- Use `Light` to paint light cells with the selected palette color.
- Use `Set Outline` to apply the selected palette color as a thin sprite outline.
- Click map objects to select them and show their bubble.
The app uses Python's built-in Tkinter UI toolkit, so there are no third-party dependencies.
## Notes
- Data is saved in `localStorage`.
- The vote bubble is local only.
- Server-side moderation is not implemented yet; see the TODO comment in `app.js` near `voteSelected()`.
- Local data is saved to `pixel_island_save.json`.
- You can draw pixel assets, save them to the library, summon them onto the island, erase placed objects, and export or import JSON save data.
- Mouse wheel zooms the island. In Pan / Inspect mode, drag the island to move the camera.

948
app.js

File diff suppressed because it is too large Load diff

View file

@ -33,26 +33,18 @@
<button id="modePlace" class="iconButton" title="Summon selected asset">Place</button>
<button id="modeErase" class="iconButton danger" title="Remove asset">Erase</button>
<div class="divider"></div>
<button id="zoomOut" class="iconButton" title="Zoom out">-</button>
<button id="zoomOut" class="iconButton" title="Zoom out"></button>
<button id="zoomIn" class="iconButton" title="Zoom in">+</button>
<button id="resetView" class="iconButton" title="Reset view">Reset</button>
<button id="settingsToggle" class="iconButton" title="World settings">Settings</button>
</aside>
<section id="settingsPanel" class="hud settingsPanel" hidden aria-label="World settings">
<label class="toggleLine">
<input id="lightingToggle" type="checkbox" checked />
Lighting
</label>
</section>
<section id="studioDrawer" class="studioDrawer" aria-label="Pixel Studio">
<div class="drawerHead">
<div>
<h1>Pixel Studio</h1>
<p>Make static scenery or moving island life.</p>
</div>
<button id="closeEditor" class="ghostButton" title="Close">x</button>
<button id="closeEditor" class="ghostButton" title="Close">×</button>
</div>
<nav class="tabs" aria-label="Studio tabs">
@ -67,10 +59,10 @@
<div class="quickSetupBar twoCols">
<label class="field">Canvas size
<select id="assetSize">
<option value="8" selected>8 x 8</option>
<option value="16">16 x 16</option>
<option value="32">32 x 32</option>
<option value="64">64 x 64</option>
<option value="8" selected>8×8</option>
<option value="16">16×16</option>
<option value="32">32×32</option>
<option value="64">64×64</option>
</select>
</label>
<label class="field">Role
@ -89,8 +81,8 @@
</label>
<div class="editorTop compactEditorTop">
<div id="sideSwitcher" class="sideSwitcher" hidden aria-label="Sprite direction">
<button id="editLeft" title="Left-facing sprite">Left</button>
<button id="editRight" class="active" title="Right-facing sprite is the main/front direction">Right</button>
<button id="editLeft" title="Left-facing sprite">Left</button>
<button id="editRight" class="active" title="Right-facing sprite is the main/front direction">Right</button>
</div>
</div>
@ -98,30 +90,23 @@
<canvas id="paintCanvas" width="512" height="512" aria-label="Pixel editor"></canvas>
<div class="palettePanel">
<div id="paletteGrid" class="paletteGrid" aria-label="Color palette"></div>
<div class="outlineControls">
<button id="setOutline" class="tool" type="button">Set Outline</button>
<button id="clearOutline" class="tool" type="button">No Outline</button>
<span id="outlineStatus">Outline: none</span>
</div>
</div>
</div>
<div class="toolRow">
<button id="toolBrush" class="tool active">Draw</button>
<button id="toolErase" class="tool">Erase</button>
<button id="toolFill" class="tool">Fill</button>
<button id="toolPick" class="tool">Pick</button>
<button id="toolLight" class="tool">Light</button>
<button id="toolDoor" class="tool buildingOnly">Door</button>
<button id="clearPaint" class="tool danger">Clear</button>
</div>
<div class="toolRow editorActions">
<button id="undoPaint" class="tool" type="button">Undo</button>
<button id="redoPaint" class="tool" type="button">Redo</button>
<button id="mirrorLeft" class="tool" type="button">Mirror Left</button>
<div class="advancedRow">
<button id="toggleAdvanced" class="tool" type="button">Advanced Draw</button>
<button id="toolLight" class="tool advancedOnly" hidden>Light</button>
<button id="toolDepth" class="tool advancedOnly" hidden>Depth</button>
<span id="advancedHint" class="hint" hidden>Light/Depth tools are here. Depth paints visible blue height marks; Shift/right-click erases.</span>
</div>
<input id="paintColor" type="color" value="#6bd06b" hidden />
<div id="editHint" class="hint">Palette color is used for both pixels and light cells.</div>
<div id="editHint" class="hint">Palette color is used for pixels, lights, and depth marks.</div>
</div>
<div class="card stack summonCard">
@ -137,8 +122,15 @@
<section id="tab-library" class="tabPanel">
<div class="card stack">
<div class="cardTitle">Library</div>
<div class="libraryHead">
<div class="cardTitle">Library</div>
<button id="showHiddenAssets" class="tool" type="button">Hidden</button>
</div>
<p class="hint">Click an asset to select it and jump to it on the map. Copy Edit creates a new derivative.</p>
<div id="hiddenAssetPanel" class="hiddenAssetPanel" hidden>
<div class="cardTitle">Hidden assets</div>
<div id="hiddenAssetList" class="assetList compactAssetList"></div>
</div>
<div id="assetList" class="assetList"></div>
</div>
</section>
@ -164,9 +156,9 @@
<div id="bubbleRemixFrom" class="bubbleRemixFrom" hidden></div>
<div id="bubbleRemixCount" class="bubbleRemixCount">Remixed: 0</div>
<div class="bubbleVotes">
<button id="voteUp" type="button">Up</button>
<button id="voteUp" type="button"></button>
<span id="voteScore">0</span>
<button id="voteDown" type="button">Down</button>
<button id="voteDown" type="button"></button>
</div>
<div class="bubbleActions">
<button id="bubbleRemix" type="button">Remix</button>
@ -176,10 +168,6 @@
<div id="toast" class="toast" hidden></div>
</div>
<script src="./js/vector.js"></script>
<script src="./js/lighting.js"></script>
<script src="./js/render-pipeline.js"></script>
<script src="./js/editor-actions.js"></script>
<script src="./app.js"></script>
</body>
</html>

View file

@ -31,11 +31,6 @@ body {
button, input, select, textarea { font: inherit; }
button { -webkit-tap-highlight-color: transparent; }
button:disabled {
cursor: not-allowed;
opacity: .48;
transform: none;
}
.app {
position: relative;
@ -183,7 +178,7 @@ button:disabled {
.edgeAdd:hover { background: var(--pink-dark); }
.edgeAdd:active { transform: translateY(calc(-50% + 2px)); box-shadow: 2px 2px 0 rgba(34,45,63,.22); }
.toolHud {
.toolHud {
right: 14px;
top: 116px;
display: grid;
@ -192,14 +187,6 @@ button:disabled {
padding: 9px;
}
.settingsPanel {
top: 142px;
right: 126px;
z-index: 6;
padding: 10px 12px;
min-width: 150px;
}
.iconButton,
button,
.ghostButton,
@ -430,16 +417,12 @@ input[type="checkbox"] { width: auto; margin: 0 8px 0 0; }
.toolRow {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(58px, 1fr));
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 6px;
align-items: center;
margin-bottom: 8px;
}
.editorActions {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.toolRow input { margin: 0; }
.tool { padding: 7px 5px; font-size: 11px; }
@ -563,7 +546,7 @@ input[type="checkbox"] { width: auto; margin: 0 8px 0 0; }
.toolHud { top: auto; bottom: 122px; right: 10px; }
.infoHud { left: 10px; right: 10px; width: auto; }
.studioDrawer { width: calc(100vw - 18px); }
.toolRow { grid-template-columns: repeat(auto-fit, minmax(54px, 1fr)); }
.toolRow { grid-template-columns: 44px repeat(3, minmax(0, 1fr)); }
}
/* refinements */
@ -600,7 +583,7 @@ input[type="checkbox"] { width: auto; margin: 0 8px 0 0; }
max-height: min(42vh, 330px);
}
.toolRow {
grid-template-columns: repeat(auto-fit, minmax(58px, 1fr));
grid-template-columns: repeat(5, minmax(0, 1fr));
margin-top: 0;
}
.paletteGrid {
@ -721,21 +704,6 @@ body, button, input, select, textarea { font-smooth: never; -webkit-font-smoothi
/* Selection bubble + outline controls */
.palettePanel { display: grid; gap: 8px; }
.outlineControls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 5px;
align-items: center;
font-size: 10px;
}
.outlineControls span {
grid-column: 1 / -1;
display: block;
border: 2px solid rgba(36,48,68,.28);
background: #fffaf0;
padding: 5px 6px;
font-weight: 900;
}
.selectionBubble {
position: absolute;
left: 0;
@ -816,3 +784,100 @@ body, button, input, select, textarea { font-smooth: never; -webkit-font-smoothi
.bubbleVotes button.activeVote {
background: var(--sun);
}
/* Pixel-ish UI refinements */
body, button, input, select, textarea {
font-family: "Courier New", "Lucida Console", Monaco, monospace;
letter-spacing: .02em;
}
button, .tab, .tool, .iconButton, .cardTitle, .logo {
text-transform: uppercase;
font-weight: 900;
}
.libraryHead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.hiddenAssetPanel {
border: 2px dashed rgba(36,48,68,.35);
background: #fff3d6;
padding: 8px;
}
.compactAssetList .assetCard { opacity: .82; }
.librarySectionTitle {
margin: 12px 0 6px;
padding: 5px 7px;
background: #ffe6f0;
border: 2px solid var(--line);
box-shadow: 3px 3px 0 rgba(36,48,68,.12);
font-size: 12px;
font-weight: 900;
}
.advancedRow {
display: grid;
grid-template-columns: max-content 1fr;
gap: 8px;
align-items: center;
margin-top: 8px;
}
.advancedOnly.active { background: #bde9ff; }
.depthLegend {
font-size: 10px;
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;
-webkit-font-smoothing: none;
text-rendering: geometricPrecision;
}
.toolRow { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.advancedRow {
grid-template-columns: max-content max-content max-content 1fr;
gap: 6px;
}
.librarySectionTitle {
display: block;
width: 100%;
text-align: left;
margin: 10px 0 6px;
cursor: pointer;
}
.librarySectionTitle::after {
content: " click: genre";
float: right;
color: var(--muted);
font-size: 10px;
}
.assetSectionGrid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
max-height: 360px;
overflow-y: auto;
padding-right: 4px;
}
.libraryEmptyNote {
color: var(--muted);
font-size: 12px;
border: 2px dashed rgba(36,48,68,.2);
padding: 8px;
background: #fff9e8;
}
.assetSectionGrid .assetCard {
grid-template-columns: 54px 1fr;
gap: 8px;
}
.assetSectionGrid .assetPreview {
width: 52px;
height: 52px;
}
.assetSectionGrid .assetActions button { padding: 4px 5px; font-size: 10px; }
@media (max-width: 720px) {
.assetSectionGrid { grid-template-columns: 1fr; max-height: none; }
.advancedRow { grid-template-columns: 1fr 1fr 1fr; }
}