@nano133/nanoterra 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +51 -7
- package/ai/SPEC.md +2 -1
- package/dist/admin/panel/dom.d.ts +11 -0
- package/dist/admin/panel/dom.js +33 -0
- package/dist/admin/panel/gameCard.d.ts +5 -2
- package/dist/admin/panel/gameCard.js +8 -4
- package/dist/admin/panel/icons.d.ts +121 -0
- package/dist/admin/panel/icons.js +41 -0
- package/dist/admin/panel/index.d.ts +11 -2
- package/dist/admin/panel/index.js +6 -1
- package/dist/admin/panel/mount.js +101 -67
- package/dist/admin/panel/orders.d.ts +23 -0
- package/dist/admin/panel/orders.js +130 -0
- package/dist/admin/panel/overview.d.ts +22 -0
- package/dist/admin/panel/overview.js +75 -0
- package/dist/admin/panel/players.d.ts +7 -0
- package/dist/admin/panel/players.js +61 -0
- package/dist/admin/panel/shelf.d.ts +15 -3
- package/dist/admin/panel/shelf.js +385 -84
- package/dist/admin/panel/shelfData.js +1 -1
- package/dist/admin/panel/tabs.d.ts +23 -0
- package/dist/admin/panel/tabs.js +68 -0
- package/dist/looks/change.d.ts +5 -5
- package/dist/looks/change.js +12 -7
- package/dist/looks/index.d.ts +2 -2
- package/dist/looks/index.js +1 -1
- package/dist/looks/json.d.ts +3 -2
- package/dist/looks/json.js +16 -9
- package/dist/looks/look.d.ts +26 -6
- package/dist/looks/look.js +37 -12
- package/package.json +1 -1
- package/src/admin/panel/dom.ts +34 -0
- package/src/admin/panel/gameCard.ts +8 -4
- package/src/admin/panel/icons.ts +46 -0
- package/src/admin/panel/index.ts +11 -2
- package/src/admin/panel/mount.ts +120 -101
- package/src/admin/panel/orders.ts +172 -0
- package/src/admin/panel/overview.ts +134 -0
- package/src/admin/panel/players.ts +79 -0
- package/src/admin/panel/shelf.ts +549 -121
- package/src/admin/panel/shelfData.ts +1 -1
- package/src/admin/panel/style.css +1775 -270
- package/src/admin/panel/tabs.ts +94 -0
- package/src/looks/change.ts +12 -9
- package/src/looks/index.ts +2 -2
- package/src/looks/json.ts +12 -7
- package/src/looks/look.ts +47 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 (2026-10-30)
|
|
4
|
+
|
|
5
|
+
- **Wear slots** (`@nano133/nanoterra/looks`, and the Shelf of `/admin/panel`): a look fills one wear slot, and a player wears one look in each slot at once. An outfit (no
|
|
6
|
+
`wear`, or `"outfit"`: `OUTFIT`) is the character's own look, with its `change` (the body colour and the hat), as before. Any other `wear` is the game's own slot (a tool's
|
|
7
|
+
skin, a charm: `"wear": "shovel"`): an item there has no `change`. `lookProblems` refuses a bad slot name (field `wear`) and a `change` on an item (field `change`), and
|
|
8
|
+
now asks for one free OUTFIT (a list of outfits only keeps the starter's words); `checkLooks` leaves `wear` out for an outfit and `change` out for an item; `looksJson`
|
|
9
|
+
writes `wear` after `priceUsd`; `removalProblems` refuses a changed wear slot on an owned look (field `wear`); `diffLooks` lists `"wear"`. New: `OUTFIT`, `wearOf`,
|
|
10
|
+
`isOutfit`, the type `Change`. **`Look.change` is optional in the type now** (read an outfit's colours after `isOutfit`): a game that reads `look.change.body` directly
|
|
11
|
+
changes that line. The Shelf shows an item's slot and a tag picture, edits its wear slot (locked for an owned look and the last free outfit) in place of the body and hat,
|
|
12
|
+
and its message for an AI assistant names the wear rule. A file of outfits only reads and writes exactly as before. Echo Island sells shovel and Pinger skins and charms
|
|
13
|
+
with it (Wes, 2026-10-30).
|
|
14
|
+
|
|
15
|
+
## 0.4.0 (2026-10-30)
|
|
16
|
+
|
|
17
|
+
- **The admin panel (`@nano133/nanoterra/admin/panel`) has a new layout: the game at a glance, in tabs.** A top bar (who is signed in, Live or Test mode, the bot
|
|
18
|
+
check, Sign out), the warnings as one banner, four numbers (Earned, Open now, To send back, Players; each opens its list), then five tabs: Overview (best sellers
|
|
19
|
+
with their share of the money, the last five orders, the switches as On or Off, the shelf in numbers, first steps for a game with no order), Cosmetics (the
|
|
20
|
+
Shelf), Orders (a filter with counts, a search, a table on a wide panel, 20 at a time), Players (with a search) and Settings (the Game card, the sign-ins, the
|
|
21
|
+
facts). Wes asked for less scrolling and reading, clear sections, and a much better cosmetics screen (2026-10-30).
|
|
22
|
+
- **The Shelf**: a grid of cards with a big picture on a tint of the look's colour, the price with its ladder step, the share of the sales, slot chips; filters and a
|
|
23
|
+
sort. Its editor opens one card at a time, with grouped fields (the ladder's seven steps as keys, colour swatches, the hats as pictures), "New", "Changed",
|
|
24
|
+
"Owned" and problem marks on each card, a short change bar at the bottom of the screen, and "Put back" for a removed look. The editor's rules do not change: the
|
|
25
|
+
Shelf still writes nothing, and an owned look's id and slots stay.
|
|
26
|
+
- New exports: `shelfFilter`, `shelfSort`, `BODY_COLOURS`, `ordersView`, `orderMatches`, `overviewView`, `bestSellers`, `playersView`, `playerMatches`, `tabBar`,
|
|
27
|
+
`TABS`, `icon`, and their types. `gameCard` takes an optional fourth argument, `onChange(state)`. `mountAdmin`'s options and the handler's routes do not change.
|
|
28
|
+
- New CSS custom properties: `--n133-ground`, `--n133-warn`, `--n133-bad`, `--n133-shadow`. The stylesheet uses a container query on the panel (`container: n133`).
|
|
29
|
+
Words that changed: the totals are "Earned", "Open now" and "To send back"; "Owned looks" is "Players who own a look"; "Copy a message for my AI" is "Copy for
|
|
30
|
+
my AI" (its tooltip says the rest). A sign-out now takes the drawn data off the page.
|
|
31
|
+
|
|
3
32
|
## 0.3.2 (2026-10-29)
|
|
4
33
|
|
|
5
34
|
- **The Game card's character switch says what On means when a game offers a choice.** Echo Island lets a player choose between the Nanoterra character
|
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Each path loads only what it needs. A page that reads a ticket loads no Three.js
|
|
|
66
66
|
| `@nano133/nanoterra/input` | let go of held keys and sticks when the player leaves the page, and pause: `releaseWhenLost`, `pauseWhenLeft`, `swallowsKeyup` (since 0.3.1) | no | 0.5 kB |
|
|
67
67
|
| `@nano133/nanoterra/admin` | the admin of a game: the guard, the handler, the store type (server code; since 0.2.0) | no | 3.7 kB |
|
|
68
68
|
| `@nano133/nanoterra/admin/d1`, `/admin/firestore` | the admin store for D1 (the starter's tables) or Firestore (a small interface, no Firebase dependency): `createD1Store`, `createFirestoreStore` (since 0.2.0) | no | 1.5 kB each |
|
|
69
|
-
| `@nano133/nanoterra/admin/panel` | the admin page, drawn in the browser into one element, and its stylesheet `@nano133/nanoterra/admin/style.css` (since 0.2.0) | no | 10.6 kB on 0.3.0
|
|
69
|
+
| `@nano133/nanoterra/admin/panel` | the admin page, drawn in the browser into one element, and its stylesheet `@nano133/nanoterra/admin/style.css` (since 0.2.0) | no | 18.9 kB with the tabbed layout (10.6 kB on 0.3.0, 8.5 kB on 0.2.0) + 5.9 kB of CSS (minified) |
|
|
70
70
|
| `@nano133/nanoterra/admin/conformance` | test cases that a game runs against its own admin (a dev dependency in practice; since 0.2.0) | no | n/a |
|
|
71
71
|
|
|
72
72
|
Measured 2026-10-26: `esbuild --bundle --minify --format=esm --external:three`, then `gzip -9`, without Three.js.
|
|
@@ -380,11 +380,38 @@ type Look = {
|
|
|
380
380
|
id: string; // 1 to 40 lowercase letters, digits or dashes: "mint-cap"
|
|
381
381
|
name: string; // 1 to 40 characters
|
|
382
382
|
priceUsd: number; // US dollars, at most 2 decimals; 0 is the free look
|
|
383
|
-
|
|
383
|
+
wear?: string; // the wear slot it fills: none (an outfit), or your game's own slot ("shovel"): see "Wear slots" below
|
|
384
|
+
change?: { body: string; hat?: "cap" | "beanie" | "crown" | "tophat"; hatColor?: string }; // an outfit's colours ("#rrggbb"); an item of another slot has none
|
|
384
385
|
slots?: Record<string, string>; // what YOUR game defines for itself: see "Slots" below. Optional.
|
|
385
386
|
};
|
|
386
387
|
```
|
|
387
388
|
|
|
389
|
+
### Wear slots: an outfit, and the items beside it
|
|
390
|
+
|
|
391
|
+
A look fills one **wear slot**, and a player wears one look in each slot at once. An **outfit** (no `wear`, or `"wear": "outfit"`) is the character's own look: its body colour and
|
|
392
|
+
its hat (`change`). Every other wear slot is **your game's own**: a tool's skin, a charm, a trail. An item there has **no `change`** (it does not change the body); your game knows
|
|
393
|
+
what it is from its id or its `slots`:
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{ "id": "classic", "name": "Classic", "priceUsd": 0, "change": { "body": "#4aa8ff" } },
|
|
397
|
+
{ "id": "toy-spade", "name": "Toy Spade", "priceUsd": 0.1, "wear": "shovel" },
|
|
398
|
+
{ "id": "echo-bat", "name": "Echo Bat", "priceUsd": 1, "wear": "pinger", "slots": { "ring": "ripples" } }
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
- A wear slot's name is like a slot's: 1 to 24 lowercase letters, digits or dashes, starting with a letter (`SLOT_NAME_PATTERN`); `outfit` (`OUTFIT`) is the outfit's.
|
|
402
|
+
- **Keep one free OUTFIT:** every player starts with it. A free item is fine beside it, but it does not count.
|
|
403
|
+
- `"change"` on an item is a mistake (`lookProblems`, field `change`): an item never changes the body. An outfit still needs its `change.body`.
|
|
404
|
+
- `checkLooks` leaves `wear` out for an outfit (no `wear` and `"wear": "outfit"` are the same look) and `change` out for an item; `looksJson` writes `wear` after `priceUsd`, on the
|
|
405
|
+
look's own line. `wearOf(look)` gives the slot (`OUTFIT` for an outfit); `isOutfit(look)` tells an outfit (with its `change`) from an item.
|
|
406
|
+
- **Players own a look by its id, so the wear slot of a look that a player owns must not change:** a shovel that a player owns stays a shovel. `removalProblems` reports it with the
|
|
407
|
+
field `"wear"`; `diffLooks` lists it as `"wear"`.
|
|
408
|
+
- The Shelf shows an item's slot on its card ("Worn in the shovel slot") and a tag in place of the character picture (give the Shelf your own `picture` to draw the real item), and
|
|
409
|
+
its editor has a **Wear slot** field (locked for an owned look and the last free outfit) in place of the body and hat of an outfit.
|
|
410
|
+
- What a player wears in each slot is your game's to keep (the starter keeps the outfit in the browser); ownership stays by id, as before.
|
|
411
|
+
|
|
412
|
+
A file of outfits only reads and writes exactly as it did. `change` is optional in the type now: read an outfit's colours after `isOutfit(look)` (or `look.change?.body`). This is a
|
|
413
|
+
minor change (0.5.0); a game on 0.4 or before drops `wear`, so a game with items must move to the new version in the game, the server and the Shelf.
|
|
414
|
+
|
|
388
415
|
### Slots: what your game defines on a look
|
|
389
416
|
|
|
390
417
|
A game with its own parts (a pair of wings, a trail colour) needs a place for them on a look. That place is `slots`, an optional object of names and values:
|
|
@@ -585,10 +612,24 @@ mountAdmin(document.getElementById("admin")!, {
|
|
|
585
612
|
});
|
|
586
613
|
```
|
|
587
614
|
|
|
588
|
-
The panel draws
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
615
|
+
The panel draws, from the top: a bar with who is signed in (the short address and a copy key), Live or Test mode, the bot check and Sign out; what is still off (the
|
|
616
|
+
status warnings, one banner); four numbers (Earned, Open now, To send back, Players: a press opens the list behind each); then five tabs. **Overview**: the best-selling
|
|
617
|
+
looks with their share of the money, the last five orders, the two switches as On or Off and the shelf in numbers (a game with no order yet gets three first steps).
|
|
618
|
+
**Cosmetics**: the **Shelf** (below). **Orders**: a filter by status with its count, a search (a look, an address or a block), "Check payments now" when the game gave a
|
|
619
|
+
check, "Paid late" with the address to send the money back to, 20 orders at a time; a card each on a phone, a table on a wide panel. **Players**: each player who owns a
|
|
620
|
+
look, with their looks. **Settings**: the **Game** card (the two switches), the Sign-ins card and the facts (the address, the mode, the nodes, the last price). The tabs are
|
|
621
|
+
real tabs (the arrow keys, Home and End work); every tab is made once, so a draft, a search and a switch keep their state, and the panel never writes the page's address.
|
|
622
|
+
A visitor who is signed out gets the game's sign-in; a wrong wallet, a Nanoterra sign-in and a missing owner address each get their own screen and see no data (a sign-out
|
|
623
|
+
takes the drawn data off the page). Options: `api` (the handler's base path), `noAddressHelp`, `lookPicture(look)` (your own picture of a look for the Shelf), `repoHelp`,
|
|
624
|
+
`loginsNote` and `fetch`.
|
|
625
|
+
|
|
626
|
+
**The Shelf** shows the looks your game sells now in a grid of cards: a big picture on a soft tint of the look's colour, the price with its step on the ladder ("25¢ ·
|
|
627
|
+
statement"), what it has sold and its share of the sales, its slots as chips; filters (All, For sale, Free, Not sold yet) and a sort (Best selling, price, name, the
|
|
628
|
+
file's order). **Edit the shelf** opens the draft: one card at a time opens with its fields in groups (the name; the price, with the ladder's seven steps as keys; the
|
|
629
|
+
look: body colour swatches and any colour, the hat as pictures, the hat colour; the slots), and each card says "New", "Changed", "Owned" or how many problems it has.
|
|
630
|
+
A bar at the bottom of the screen holds the state of the draft and the two copy keys ("Copy looks.json", "Copy for my AI"); under the cards are the full list of
|
|
631
|
+
problems, GitHub, Start over, what changed and the new file. A removed look can be put back. The pure helpers are exported for a game's own page: `shelfFilter`,
|
|
632
|
+
`shelfSort`, `bestSellers`, `orderMatches`, `playerMatches`.
|
|
592
633
|
|
|
593
634
|
**The Shelf's pictures on the real character** (since 0.3.0). The panel has no Three.js, so it takes the pictures from you, lazily:
|
|
594
635
|
|
|
@@ -612,7 +653,10 @@ flat picture stays. `createLookPreview({ size?, body?, record?, background?, key
|
|
|
612
653
|
**The Shelf never writes.** It edits a draft in the owner's browser and gives text to copy (`looks.json`, or a message for an AI assistant): there is no call in the panel that
|
|
613
654
|
changes a look, a price or an order, and a test proves that editing the Shelf sends no request. A change of looks is a change of your repository. The **Game** card writes only the
|
|
614
655
|
two settings. The stylesheet is scoped under `.n133-admin`, so it cannot leak into your page; to theme it set `--n133-ink`, `--n133-ink-soft`, `--n133-paper`, `--n133-line`,
|
|
615
|
-
`--n133-gem`, `--n133-gem-dark`, `--n133-sea`, `--n133-radius
|
|
656
|
+
`--n133-gem`, `--n133-gem-dark`, `--n133-sea`, `--n133-radius`, `--n133-font`, `--n133-ground` (the wells inside a card), `--n133-warn`, `--n133-bad` or `--n133-shadow` on the
|
|
657
|
+
element that holds it. The keys, inputs, switches, swatches and price steps are 44 px or more; the tabs, filter keys and copy keys 40 px. The layout follows the panel's own
|
|
658
|
+
width (a container query), not the window's: on a phone two cards a row and the five tabs as icons over their words; three cards a row and the orders as a table from
|
|
659
|
+
760 px; four cards from 1000 px. The panel can be up to 1200 px wide: the page that holds it sets the width (the starter's admin page is 900 px).
|
|
616
660
|
|
|
617
661
|
The wallet sign-in stays yours (`signIn`), because only your game can start a request in the ADMIN scope with its own cookie. The panel never signs anyone in.
|
|
618
662
|
|
package/ai/SPEC.md
CHANGED
|
@@ -100,9 +100,10 @@ export const LOOKS = checkLooks(rawLooksJson); // throws with the first mistake
|
|
|
100
100
|
- One free look (`priceUsd: 0`) at least. Never change or remove the id of a look that someone owns. Prices in US dollars with at most 2 decimals; the server reads the price from the file.
|
|
101
101
|
- A look only changes how the character looks. No random look, no loot box, no look that changes a score.
|
|
102
102
|
- `slots` (optional, at most 8: a name and a word or a `#rrggbb` colour) is where the game keeps its own parts of a look. Read `look.slots` from `checkLooks`. Never change the slots of a look that someone owns: add a new look.
|
|
103
|
+
- `wear` (optional) is the wear slot a look fills: none for an outfit (the character's colours and hat, `change`), or the game's own slot (`"shovel"`, `"charm"`) for an item, which has no `change`. A player wears one look in each slot. Keep one free outfit. Never change the wear slot of a look that someone owns. Read outfits with `isOutfit(look)`.
|
|
103
104
|
- The game is the seller. Never send looks, prices or sales to another site.
|
|
104
105
|
|
|
105
|
-
**Check:** `checkLooks` passes at start; a removed look that is owned is refused; a changed slot on an owned look is refused.
|
|
106
|
+
**Check:** `checkLooks` passes at start; a removed look that is owned is refused; a changed slot or wear slot on an owned look is refused.
|
|
106
107
|
|
|
107
108
|
## 8. Leaving the page (any game that a player walks in with keys or a stick)
|
|
108
109
|
|
|
@@ -4,4 +4,15 @@ export declare function h<K extends keyof HTMLElementTagNameMap>(tag: K, attrs?:
|
|
|
4
4
|
/** "nano_1abc…wxyz" */
|
|
5
5
|
export declare const shortAddress: (a: string) => string;
|
|
6
6
|
export declare function copy(text: string, button: HTMLButtonElement): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* A small copy key: an icon and the word "Copy". A press puts `value` on the clipboard and says "Copied" for a moment; only the word changes, so the icon
|
|
9
|
+
* stays. `label` names what is copied, for a screen reader and the tooltip ("Copy the player's address").
|
|
10
|
+
*/
|
|
11
|
+
export declare function copyKey(value: string, label: string, icon?: Element): HTMLButtonElement;
|
|
12
|
+
/** "just now", "5 min ago", "3 h ago", "2 days ago". A time in the future reads "just now". */
|
|
13
|
+
export declare function ago(ms: number, now?: number): string;
|
|
14
|
+
/** "3 orders", "1 order" */
|
|
15
|
+
export declare const plural: (n: number, word: string) => string;
|
|
16
|
+
/** "$1.25" from cents. */
|
|
17
|
+
export declare const dollars: (cents: number) => string;
|
|
7
18
|
export {};
|
package/dist/admin/panel/dom.js
CHANGED
|
@@ -31,3 +31,36 @@ export async function copy(text, button) {
|
|
|
31
31
|
}
|
|
32
32
|
setTimeout(() => (button.textContent = was), 1500);
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* A small copy key: an icon and the word "Copy". A press puts `value` on the clipboard and says "Copied" for a moment; only the word changes, so the icon
|
|
36
|
+
* stays. `label` names what is copied, for a screen reader and the tooltip ("Copy the player's address").
|
|
37
|
+
*/
|
|
38
|
+
export function copyKey(value, label, icon) {
|
|
39
|
+
const word = h("span", { class: "copy-word" }, "Copy");
|
|
40
|
+
const button = h("button", { class: "btn btn-small btn-quiet copy-key", type: "button", "aria-label": label, title: label }, icon ?? null, word);
|
|
41
|
+
button.addEventListener("click", async () => {
|
|
42
|
+
try {
|
|
43
|
+
await navigator.clipboard.writeText(value);
|
|
44
|
+
word.textContent = "Copied";
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
word.textContent = "Copy failed";
|
|
48
|
+
}
|
|
49
|
+
setTimeout(() => (word.textContent = "Copy"), 1500);
|
|
50
|
+
});
|
|
51
|
+
return button;
|
|
52
|
+
}
|
|
53
|
+
/** "just now", "5 min ago", "3 h ago", "2 days ago". A time in the future reads "just now". */
|
|
54
|
+
export function ago(ms, now = Date.now()) {
|
|
55
|
+
const m = Math.max(0, Math.round((now - ms) / 60_000));
|
|
56
|
+
if (m < 1)
|
|
57
|
+
return "just now";
|
|
58
|
+
if (m < 60)
|
|
59
|
+
return `${m} min ago`;
|
|
60
|
+
const hours = Math.round(m / 60);
|
|
61
|
+
return hours < 48 ? `${hours} h ago` : `${Math.round(hours / 24)} days ago`;
|
|
62
|
+
}
|
|
63
|
+
/** "3 orders", "1 order" */
|
|
64
|
+
export const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
65
|
+
/** "$1.25" from cents. */
|
|
66
|
+
export const dollars = (cents) => `$${(cents / 100).toFixed(2)}`;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type GameState } from "../switches.js";
|
|
2
2
|
import { type Api } from "./api.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The card for the settings `initial`. It keeps its own state: a press shows "Saving…", then "Saved." or the server's words. `defaultsFile` is where the game's defaults are
|
|
5
|
+
* written ("game.config.json"). `onChange` hears each saved state (the panel's Overview shows the two switches too).
|
|
6
|
+
*/
|
|
7
|
+
export declare function gameCard(api: Api, initial: GameState, defaultsFile?: string, onChange?: (state: GameState) => void): HTMLElement;
|
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
import { CHARACTER_NEEDS_SIGNIN, CHARACTER_SIGNIN_OFF, REVIEW_LINK, REVIEW_NOTE, SWITCHES, pressOf, signinOn, stateOf } from "../switches.js";
|
|
5
5
|
import { ApiError } from "./api.js";
|
|
6
6
|
import { h } from "./dom.js";
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The card for the settings `initial`. It keeps its own state: a press shows "Saving…", then "Saved." or the server's words. `defaultsFile` is where the game's defaults are
|
|
9
|
+
* written ("game.config.json"). `onChange` hears each saved state (the panel's Overview shows the two switches too).
|
|
10
|
+
*/
|
|
11
|
+
export function gameCard(api, initial, defaultsFile = "the game's own settings", onChange) {
|
|
9
12
|
let state = initial;
|
|
10
13
|
const rows = new Map();
|
|
11
14
|
/** Under the character switch: it works only through sign-in, so it says so, and says what a player gets while sign-in is Off. */
|
|
12
15
|
const needs = h("p", { class: "fine switch-needs" });
|
|
13
|
-
const card = h("section", { class: "admin-card game-card", "aria-labelledby": "game-title" }, h("h2", { id: "game-title" }, "Game"));
|
|
14
|
-
card.append(h("p", { class: "fine" }, `Two choices about your game. A press saves at once: you push nothing and deploy nothing. They start from ${defaultsFile}, and a press here wins.`));
|
|
16
|
+
const card = h("section", { class: "admin-card game-card", "aria-labelledby": "game-title" }, h("div", { class: "card-head" }, h("h2", { id: "game-title" }, "Game")));
|
|
17
|
+
card.append(h("p", { class: "fine card-intro" }, `Two choices about your game. A press saves at once: you push nothing and deploy nothing. They start from ${defaultsFile}, and a press here wins.`));
|
|
15
18
|
const draw = () => {
|
|
16
19
|
for (const [id, row] of rows) {
|
|
17
20
|
const on = stateOf(id, state);
|
|
@@ -31,6 +34,7 @@ export function gameCard(api, initial, defaultsFile = "the game's own settings")
|
|
|
31
34
|
state = await api.post("/game", pressOf(id, state));
|
|
32
35
|
row.status.textContent = "Saved.";
|
|
33
36
|
draw();
|
|
37
|
+
onChange?.(state);
|
|
34
38
|
}
|
|
35
39
|
catch (e) {
|
|
36
40
|
row.status.textContent = e instanceof ApiError ? e.message : "The game's server did not answer. Nothing was changed.";
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
declare const SHAPES: {
|
|
2
|
+
overview: [string, {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
rx: number;
|
|
8
|
+
}][];
|
|
9
|
+
shirt: [string, {
|
|
10
|
+
d: string;
|
|
11
|
+
}][];
|
|
12
|
+
receipt: [string, {
|
|
13
|
+
d: string;
|
|
14
|
+
}][];
|
|
15
|
+
players: ([string, {
|
|
16
|
+
cx: number;
|
|
17
|
+
cy: number;
|
|
18
|
+
r: number;
|
|
19
|
+
}] | [string, {
|
|
20
|
+
d: string;
|
|
21
|
+
}])[];
|
|
22
|
+
settings: ([string, {
|
|
23
|
+
cx: number;
|
|
24
|
+
cy: number;
|
|
25
|
+
r: number;
|
|
26
|
+
}] | [string, {
|
|
27
|
+
d: string;
|
|
28
|
+
}])[];
|
|
29
|
+
coin: ([string, {
|
|
30
|
+
cx: number;
|
|
31
|
+
cy: number;
|
|
32
|
+
r: number;
|
|
33
|
+
}] | [string, {
|
|
34
|
+
d: string;
|
|
35
|
+
}])[];
|
|
36
|
+
clock: ([string, {
|
|
37
|
+
cx: number;
|
|
38
|
+
cy: number;
|
|
39
|
+
r: number;
|
|
40
|
+
}] | [string, {
|
|
41
|
+
d: string;
|
|
42
|
+
}])[];
|
|
43
|
+
back: [string, {
|
|
44
|
+
d: string;
|
|
45
|
+
}][];
|
|
46
|
+
tag: ([string, {
|
|
47
|
+
d: string;
|
|
48
|
+
}] | [string, {
|
|
49
|
+
cx: number;
|
|
50
|
+
cy: number;
|
|
51
|
+
r: number;
|
|
52
|
+
}])[];
|
|
53
|
+
copy: ([string, {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
rx: number;
|
|
59
|
+
}] | [string, {
|
|
60
|
+
d: string;
|
|
61
|
+
}])[];
|
|
62
|
+
lock: ([string, {
|
|
63
|
+
x: number;
|
|
64
|
+
y: number;
|
|
65
|
+
width: number;
|
|
66
|
+
height: number;
|
|
67
|
+
rx: number;
|
|
68
|
+
}] | [string, {
|
|
69
|
+
d: string;
|
|
70
|
+
}])[];
|
|
71
|
+
plus: [string, {
|
|
72
|
+
d: string;
|
|
73
|
+
}][];
|
|
74
|
+
search: ([string, {
|
|
75
|
+
cx: number;
|
|
76
|
+
cy: number;
|
|
77
|
+
r: number;
|
|
78
|
+
}] | [string, {
|
|
79
|
+
d: string;
|
|
80
|
+
}])[];
|
|
81
|
+
alert: [string, {
|
|
82
|
+
d: string;
|
|
83
|
+
}][];
|
|
84
|
+
check: [string, {
|
|
85
|
+
d: string;
|
|
86
|
+
}][];
|
|
87
|
+
external: [string, {
|
|
88
|
+
d: string;
|
|
89
|
+
}][];
|
|
90
|
+
refresh: [string, {
|
|
91
|
+
d: string;
|
|
92
|
+
}][];
|
|
93
|
+
out: [string, {
|
|
94
|
+
d: string;
|
|
95
|
+
}][];
|
|
96
|
+
pencil: [string, {
|
|
97
|
+
d: string;
|
|
98
|
+
}][];
|
|
99
|
+
close: [string, {
|
|
100
|
+
d: string;
|
|
101
|
+
}][];
|
|
102
|
+
dup: ([string, {
|
|
103
|
+
x: number;
|
|
104
|
+
y: number;
|
|
105
|
+
width: number;
|
|
106
|
+
height: number;
|
|
107
|
+
rx: number;
|
|
108
|
+
}] | [string, {
|
|
109
|
+
d: string;
|
|
110
|
+
}])[];
|
|
111
|
+
trash: [string, {
|
|
112
|
+
d: string;
|
|
113
|
+
}][];
|
|
114
|
+
sparkle: [string, {
|
|
115
|
+
d: string;
|
|
116
|
+
}][];
|
|
117
|
+
};
|
|
118
|
+
export type IconName = keyof typeof SHAPES;
|
|
119
|
+
/** An icon of `size` CSS pixels (default 20). */
|
|
120
|
+
export declare function icon(name: IconName, size?: number): SVGSVGElement;
|
|
121
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// The panel's small line icons, drawn as inline SVG with createElementNS (no icon font, no innerHTML: fine under a strict CSP). Each is 24 units square, drawn
|
|
2
|
+
// with the text colour (currentColor), and hidden from screen readers: the word next to it says what it is.
|
|
3
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
4
|
+
const SHAPES = {
|
|
5
|
+
overview: [["rect", { x: 3, y: 3, width: 7.5, height: 7.5, rx: 2 }], ["rect", { x: 13.5, y: 3, width: 7.5, height: 7.5, rx: 2 }], ["rect", { x: 3, y: 13.5, width: 7.5, height: 7.5, rx: 2 }], ["rect", { x: 13.5, y: 13.5, width: 7.5, height: 7.5, rx: 2 }]],
|
|
6
|
+
shirt: [["path", { d: "M8.5 3.5 4 6 2.5 10.5l3.5 1.5V21h12v-9l3.5-1.5L20 6l-4.5-2.5a3.5 3.5 0 0 1-7 0Z" }]],
|
|
7
|
+
receipt: [["path", { d: "M5 3h14v18l-2.5-1.5L14 21l-2-1.5L10 21l-2.5-1.5L5 21Z" }], ["path", { d: "M9 8h6M9 12h6M9 16h3" }]],
|
|
8
|
+
players: [["circle", { cx: 9, cy: 8, r: 3.5 }], ["path", { d: "M2.5 20a6.5 6.5 0 0 1 13 0" }], ["path", { d: "M16 4.8a3.5 3.5 0 0 1 0 6.4M18 14.2a6.5 6.5 0 0 1 3.5 5.8" }]],
|
|
9
|
+
settings: [["circle", { cx: 12, cy: 12, r: 3 }], ["path", { d: "M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3M5.3 5.3l2.1 2.1M16.6 16.6l2.1 2.1M5.3 18.7l2.1-2.1M16.6 7.4l2.1-2.1" }]],
|
|
10
|
+
coin: [["circle", { cx: 12, cy: 12, r: 9 }], ["path", { d: "M12 6.5v11M14.8 9.2a2.6 2.6 0 0 0-2.6-1.7h-.6a2.2 2.2 0 0 0 0 4.4h.8a2.2 2.2 0 0 1 0 4.4h-.7a2.6 2.6 0 0 1-2.6-1.7" }]],
|
|
11
|
+
clock: [["circle", { cx: 12, cy: 12, r: 9 }], ["path", { d: "M12 7v5l3.5 2" }]],
|
|
12
|
+
back: [["path", { d: "M9 6 4 11l5 5" }], ["path", { d: "M4 11h10.5a5.5 5.5 0 0 1 0 11H11" }]],
|
|
13
|
+
tag: [["path", { d: "M3 12.2V4a1 1 0 0 1 1-1h8.2l8.6 8.6a1.5 1.5 0 0 1 0 2.1l-6.9 6.9a1.5 1.5 0 0 1-2.1 0Z" }], ["circle", { cx: 7.5, cy: 7.5, r: 1.5 }]],
|
|
14
|
+
copy: [["rect", { x: 8, y: 8, width: 13, height: 13, rx: 2.5 }], ["path", { d: "M16 8V5.5A2.5 2.5 0 0 0 13.5 3h-8A2.5 2.5 0 0 0 3 5.5v8A2.5 2.5 0 0 0 5.5 16H8" }]],
|
|
15
|
+
lock: [["rect", { x: 4.5, y: 10.5, width: 15, height: 10.5, rx: 2.5 }], ["path", { d: "M8 10.5V7.5a4 4 0 0 1 8 0v3" }]],
|
|
16
|
+
plus: [["path", { d: "M12 5v14M5 12h14" }]],
|
|
17
|
+
search: [["circle", { cx: 10.5, cy: 10.5, r: 6.5 }], ["path", { d: "m15.5 15.5 5 5" }]],
|
|
18
|
+
alert: [["path", { d: "M10.3 3.9 2.4 17.6A2 2 0 0 0 4.1 20.6h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" }], ["path", { d: "M12 9v4.5M12 17h.01" }]],
|
|
19
|
+
check: [["path", { d: "m4.5 12.5 5 5 10-11" }]],
|
|
20
|
+
external: [["path", { d: "M14 4h6v6M20 4l-9 9" }], ["path", { d: "M18 14v4.5a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 4 18.5v-11A1.5 1.5 0 0 1 5.5 6H10" }]],
|
|
21
|
+
refresh: [["path", { d: "M20 11a8 8 0 0 0-14.3-4.9L4 8M4 4v4h4" }], ["path", { d: "M4 13a8 8 0 0 0 14.3 4.9L20 16M20 20v-4h-4" }]],
|
|
22
|
+
out: [["path", { d: "M15 4h3.5A1.5 1.5 0 0 1 20 5.5v13a1.5 1.5 0 0 1-1.5 1.5H15" }], ["path", { d: "M10 8l-4 4 4 4M6 12h10" }]],
|
|
23
|
+
pencil: [["path", { d: "M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16Z" }], ["path", { d: "m13.5 6.5 4 4" }]],
|
|
24
|
+
close: [["path", { d: "M6 6l12 12M18 6 6 18" }]],
|
|
25
|
+
dup: [["rect", { x: 8, y: 8, width: 13, height: 13, rx: 2.5 }], ["path", { d: "M14.5 11.5v6M11.5 14.5h6M16 8V5.5A2.5 2.5 0 0 0 13.5 3h-8A2.5 2.5 0 0 0 3 5.5v8A2.5 2.5 0 0 0 5.5 16H8" }]],
|
|
26
|
+
trash: [["path", { d: "M4 7h16M9.5 7V4.5h5V7M6.5 7l1 13h9l1-13" }]],
|
|
27
|
+
sparkle: [["path", { d: "M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2.5 2.5M15.5 15.5 18 18M6 18l2.5-2.5M15.5 8.5 18 6" }]],
|
|
28
|
+
};
|
|
29
|
+
/** An icon of `size` CSS pixels (default 20). */
|
|
30
|
+
export function icon(name, size = 20) {
|
|
31
|
+
const svg = document.createElementNS(NS, "svg");
|
|
32
|
+
for (const [k, v] of Object.entries({ viewBox: "0 0 24 24", width: size, height: size, fill: "none", stroke: "currentColor", "stroke-width": 1.8, "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", focusable: "false", class: `icon icon-${name}` }))
|
|
33
|
+
svg.setAttribute(k, String(v));
|
|
34
|
+
for (const [tag, attrs] of SHAPES[name]) {
|
|
35
|
+
const el = document.createElementNS(NS, tag);
|
|
36
|
+
for (const [k, v] of Object.entries(attrs))
|
|
37
|
+
el.setAttribute(k, String(v));
|
|
38
|
+
svg.append(el);
|
|
39
|
+
}
|
|
40
|
+
return svg;
|
|
41
|
+
}
|
|
@@ -3,8 +3,17 @@ export type { MountedAdmin, MountOptions } from "./mount.js";
|
|
|
3
3
|
export { ApiError, createApi } from "./api.js";
|
|
4
4
|
export type { Api } from "./api.js";
|
|
5
5
|
export { gameCard } from "./gameCard.js";
|
|
6
|
-
export { lookPicture, previewPicture, shelfView } from "./shelf.js";
|
|
6
|
+
export { BODY_COLOURS, lookPicture, previewPicture, shelfFilter, shelfSort, shelfView } from "./shelf.js";
|
|
7
7
|
export { DEFAULT_LOGINS_NOTE, signinsCard } from "./signins.js";
|
|
8
|
-
export type { ShelfData } from "./shelf.js";
|
|
8
|
+
export type { ShelfData, ShelfFilter, ShelfSort } from "./shelf.js";
|
|
9
|
+
export { orderMatches, ordersView } from "./orders.js";
|
|
10
|
+
export type { OrderFilter, OrdersView } from "./orders.js";
|
|
11
|
+
export { bestSellers, overviewView } from "./overview.js";
|
|
12
|
+
export type { BestSeller, OverviewView } from "./overview.js";
|
|
13
|
+
export { playerMatches, playersView } from "./players.js";
|
|
14
|
+
export { TABS, tabBar } from "./tabs.js";
|
|
15
|
+
export type { TabBar, TabId } from "./tabs.js";
|
|
16
|
+
export { icon } from "./icons.js";
|
|
17
|
+
export type { IconName } from "./icons.js";
|
|
9
18
|
export { aiPrompt, centsOf, changeText, ownedIds, shelfStats, soldText } from "./shelfData.js";
|
|
10
19
|
export type { LookStats, ShelfOrder, ShelfPlayer } from "./shelfData.js";
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
export { mountAdmin } from "./mount.js";
|
|
9
9
|
export { ApiError, createApi } from "./api.js";
|
|
10
10
|
export { gameCard } from "./gameCard.js";
|
|
11
|
-
export { lookPicture, previewPicture, shelfView } from "./shelf.js";
|
|
11
|
+
export { BODY_COLOURS, lookPicture, previewPicture, shelfFilter, shelfSort, shelfView } from "./shelf.js";
|
|
12
12
|
export { DEFAULT_LOGINS_NOTE, signinsCard } from "./signins.js";
|
|
13
|
+
export { orderMatches, ordersView } from "./orders.js";
|
|
14
|
+
export { bestSellers, overviewView } from "./overview.js";
|
|
15
|
+
export { playerMatches, playersView } from "./players.js";
|
|
16
|
+
export { TABS, tabBar } from "./tabs.js";
|
|
17
|
+
export { icon } from "./icons.js";
|
|
13
18
|
export { aiPrompt, centsOf, changeText, ownedIds, shelfStats, soldText } from "./shelfData.js";
|