updated
This commit is contained in:
parent
2d42f7f487
commit
89a32dc28e
4 changed files with 490 additions and 679 deletions
24
README.md
24
README.md
|
|
@ -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.
|
||||
|
|
|
|||
60
index.html
60
index.html
|
|
@ -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>
|
||||
|
|
|
|||
137
styles.css
137
styles.css
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue