@onnie81/murdoku-spor 1.0.0 → 1.2.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/Caddyfile +8 -0
- package/README.md +37 -6
- package/client/src/art/objects.jsx +152 -91
- package/client/src/audio.js +266 -0
- package/client/src/components/Board.jsx +81 -15
- package/client/src/components/CluePanel.jsx +2 -2
- package/client/src/components/PlayView.jsx +193 -74
- package/client/src/components/SettingsView.jsx +18 -0
- package/client/src/components/Tray.jsx +6 -5
- package/client/src/game.js +17 -1
- package/client/src/i18n/en.js +10 -2
- package/client/src/i18n/es.js +14 -2
- package/client/src/i18n/index.js +21 -0
- package/client/src/objectModel.js +25 -0
- package/client/src/store.js +8 -1
- package/client/src/styles.css +89 -1
- package/docker-compose.yml +37 -6
- package/murdoku.bundle +0 -0
- package/package.json +2 -1
- package/scripts/npm-server-bootstrap.sh +4 -2
- package/server/api.js +1 -1
- package/server/public/assets/index-CWjzD9nk.js +40 -0
- package/server/public/assets/index-DUtSdtnb.css +1 -0
- package/server/public/index.html +2 -2
- package/shared/clues.js +103 -37
- package/shared/generator.js +84 -16
- package/shared/model.js +60 -37
- package/server/public/assets/index-COhad2G1.js +0 -40
- package/server/public/assets/index-h4AwMTqN.css +0 -1
package/Caddyfile
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Automatic HTTPS for Murdoku. Caddy obtains + renews a Let's Encrypt
|
|
2
|
+
# certificate for the domain and reverse-proxies to the app container.
|
|
3
|
+
# The domain comes from $MURDOKU_DOMAIN (default games.onniehex.dev).
|
|
4
|
+
|
|
5
|
+
{$MURDOKU_DOMAIN:games.onniehex.dev} {
|
|
6
|
+
encode zstd gzip
|
|
7
|
+
reverse_proxy murdoku:8080
|
|
8
|
+
}
|
package/README.md
CHANGED
|
@@ -30,8 +30,17 @@ victim** is the killer.
|
|
|
30
30
|
long-press to mark X, keyboard shortcuts (1-9, N, X, U, R, E).
|
|
31
31
|
- **Multilingual** — English and Spanish shipped; clues are structured data
|
|
32
32
|
rendered through per-language templates, so adding a language is one file.
|
|
33
|
-
- **
|
|
34
|
-
|
|
33
|
+
- **Furniture that matters** — blockers, seats you can be *seated on*
|
|
34
|
+
(armchairs, two-square sofas), beds you can be *lying in*, and wall-mounted
|
|
35
|
+
windows/paintings/mirrors that live on the walls themselves (windows count
|
|
36
|
+
from both sides). Clues use all of it: "was seated on the sofa", "was not
|
|
37
|
+
lying in any bed", "was beside a window".
|
|
38
|
+
- **Original art & sound** — 12 hand-drawn SVG suspect portraits, colorful
|
|
39
|
+
furniture sprites, 8 scene themes; synthesized noir soundtrack and sound
|
|
40
|
+
effects (WebAudio, zero assets, all original).
|
|
41
|
+
- **Phone-first play screen** — fullscreen mode, an always-visible lateral
|
|
42
|
+
suspect overlay that turns translucent while you place, floating controls,
|
|
43
|
+
and a clue bottom-sheet.
|
|
35
44
|
|
|
36
45
|
## Quick start (Docker)
|
|
37
46
|
|
|
@@ -50,16 +59,38 @@ docker run --rm -v murdoku-data:/data -v "$PWD:/out" debian \
|
|
|
50
59
|
|
|
51
60
|
### HTTPS / domain
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
The stack ships a **Caddy** service that terminates TLS and reverse-proxies the
|
|
63
|
+
app. On `docker compose up -d` it automatically obtains and renews a **Let's
|
|
64
|
+
Encrypt** certificate for the domain — no manual certs. Requirements:
|
|
65
|
+
|
|
66
|
+
- ports **80 and 443** open to the internet (Caddy needs 80/443 for the ACME challenge),
|
|
67
|
+
- a DNS **A record** `games.onniehex.dev` → the server's public IP, **DNS-only**
|
|
68
|
+
(not proxied behind another CDN, or the challenge can't reach Caddy).
|
|
69
|
+
|
|
70
|
+
Override the domain with the `MURDOKU_DOMAIN` env var. Port `5000` stays mapped
|
|
71
|
+
straight to the app for direct testing / health checks. The app sets
|
|
72
|
+
`trust proxy`, so `Secure` cookies and client IPs work correctly behind Caddy.
|
|
73
|
+
|
|
74
|
+
### Cloud delivery (npm)
|
|
75
|
+
|
|
76
|
+
Published as `@onnie81/murdoku-spor`. First-time server setup:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
curl -fsSL https://registry.npmjs.org/@onnie81/murdoku-spor/-/murdoku-spor-<version>.tgz \
|
|
80
|
+
| tar xz && sudo bash package/scripts/npm-server-bootstrap.sh
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This installs Docker/git if missing, unpacks a real git repo (from the bundled
|
|
84
|
+
history) into `/opt/murdoku`, builds, and installs a systemd timer that polls
|
|
85
|
+
npm every 60s and rebuilds on each new published version.
|
|
56
86
|
|
|
57
87
|
### Configuration
|
|
58
88
|
|
|
59
89
|
| Env | Default | Meaning |
|
|
60
90
|
|---|---|---|
|
|
61
|
-
| `PORT` | `8080` | HTTP port |
|
|
91
|
+
| `PORT` | `8080` | app HTTP port (inside the container) |
|
|
62
92
|
| `DATA_DIR` | `/data` (container) / `./data` (bare) | SQLite location |
|
|
93
|
+
| `MURDOKU_DOMAIN` | `games.onniehex.dev` | domain Caddy requests a cert for |
|
|
63
94
|
|
|
64
95
|
## Development
|
|
65
96
|
|
|
@@ -1,158 +1,219 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Colorful original furniture art.
|
|
2
|
+
// FloorIcon — single-cell pieces, viewBox 0 0 100 100
|
|
3
|
+
// WideIcon — sofa & bed spanning two cells, viewBox 0 0 200 100
|
|
4
|
+
// WallIcon — wall-mounted pieces (window/painting/mirror), viewBox 0 0 100 36
|
|
2
5
|
import React from 'react';
|
|
3
6
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
7
|
+
const INK = '#241e33';
|
|
8
|
+
const O = { stroke: INK, strokeWidth: 2.5, strokeLinejoin: 'round', strokeLinecap: 'round' };
|
|
9
|
+
const WOOD = '#8a5a33', WOOD_D = '#6d4426', BRASS = '#c9a227', CREAM = '#f4efe2';
|
|
6
10
|
|
|
7
|
-
const
|
|
11
|
+
const FLOOR = {
|
|
8
12
|
piano: (
|
|
9
13
|
<>
|
|
10
|
-
<path {...
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
+
<path {...O} d="M20,28 L70,28 Q86,32 86,52 Q86,72 62,72 L20,72 Z" fill="#2b2436" />
|
|
15
|
+
<path d="M24,32 L66,32 Q80,35 80,50 Q80,66 60,66 L24,66 Z" fill="#3a3350" opacity="0.8" />
|
|
16
|
+
<rect {...O} x="14" y="60" width="46" height="14" rx="3" fill={CREAM} />
|
|
17
|
+
{[20, 27, 34, 41, 48, 55].map(x => <rect key={x} x={x} y="60" width="3" height="8" fill={INK} />)}
|
|
18
|
+
<rect x="20" y="74" width="6" height="12" fill={WOOD_D} />
|
|
19
|
+
<rect x="52" y="74" width="6" height="12" fill={WOOD_D} />
|
|
20
|
+
<circle cx="74" cy="30" r="3" fill={BRASS} />
|
|
14
21
|
</>
|
|
15
22
|
),
|
|
16
23
|
armchair: (
|
|
17
24
|
<>
|
|
18
|
-
<path {...
|
|
19
|
-
<rect {...
|
|
20
|
-
<rect {...
|
|
21
|
-
<rect {...
|
|
25
|
+
<path {...O} d="M24,30 Q50,18 76,30 L76,60 L24,60 Z" fill="#2e7d6e" />
|
|
26
|
+
<rect {...O} x="28" y="52" width="44" height="16" rx="6" fill="#3f9683" />
|
|
27
|
+
<rect {...O} x="14" y="42" width="14" height="30" rx="7" fill="#256a5d" />
|
|
28
|
+
<rect {...O} x="72" y="42" width="14" height="30" rx="7" fill="#256a5d" />
|
|
29
|
+
<rect x="30" y="72" width="7" height="12" fill={WOOD} />
|
|
30
|
+
<rect x="62" y="72" width="7" height="12" fill={WOOD} />
|
|
22
31
|
</>
|
|
23
32
|
),
|
|
24
33
|
bookshelf: (
|
|
25
34
|
<>
|
|
26
|
-
<rect {...
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
{[
|
|
30
|
-
<rect key={i}
|
|
35
|
+
<rect {...O} x="22" y="14" width="56" height="72" rx="3" fill={WOOD} />
|
|
36
|
+
<rect x="27" y="20" width="46" height="26" fill="#513a20" />
|
|
37
|
+
<rect x="27" y="52" width="46" height="26" fill="#513a20" />
|
|
38
|
+
{[['#e5484d', 29], ['#0090ff', 37], ['#ffc53d', 45], ['#46a758', 52], ['#6e56cf', 60]].map(([f, x], i) => (
|
|
39
|
+
<rect key={i} x={x} y={i % 2 ? 26 : 24} width="7" height={i % 2 ? 20 : 22} fill={f} stroke={INK} strokeWidth="1.5" />
|
|
31
40
|
))}
|
|
32
|
-
{[
|
|
33
|
-
<rect key={i}
|
|
41
|
+
{[['#12a594', 30], ['#e93d82', 38], ['#c9a227', 46], ['#ad7f58', 54], ['#0090ff', 62]].map(([f, x], i) => (
|
|
42
|
+
<rect key={i} x={x} y={i % 2 ? 58 : 56} width="7" height={i % 2 ? 20 : 22} fill={f} stroke={INK} strokeWidth="1.5" />
|
|
34
43
|
))}
|
|
35
44
|
</>
|
|
36
45
|
),
|
|
37
46
|
plant: (
|
|
38
47
|
<>
|
|
39
|
-
<path {...
|
|
40
|
-
<path {...
|
|
41
|
-
<path {...
|
|
42
|
-
<path {...
|
|
43
|
-
<
|
|
48
|
+
<path {...O} d="M50,52 Q30,48 26,26 Q46,30 50,52 Z" fill="#46a758" />
|
|
49
|
+
<path {...O} d="M50,52 Q70,48 74,26 Q54,30 50,52 Z" fill="#5a9e66" />
|
|
50
|
+
<path {...O} d="M50,50 Q48,26 50,14 Q58,28 50,50 Z" fill="#3f8d50" />
|
|
51
|
+
<path {...O} d="M34,58 L66,58 L60,84 L40,84 Z" fill="#b5623a" />
|
|
52
|
+
<rect {...O} x="31" y="54" width="38" height="8" rx="3" fill="#c9744a" />
|
|
44
53
|
</>
|
|
45
54
|
),
|
|
46
55
|
clock: (
|
|
47
56
|
<>
|
|
48
|
-
<rect {...
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
<
|
|
57
|
+
<rect {...O} x="34" y="12" width="32" height="76" rx="5" fill={WOOD_D} />
|
|
58
|
+
<rect x="38" y="46" width="24" height="34" rx="3" fill="#513a20" />
|
|
59
|
+
<circle {...O} cx="50" cy="29" r="12" fill={CREAM} />
|
|
60
|
+
<path d="M50,29 L50,21 M50,29 L56,32" stroke={INK} strokeWidth="2.5" strokeLinecap="round" />
|
|
61
|
+
<line x1="50" y1="48" x2="50" y2="70" stroke={BRASS} strokeWidth="3" />
|
|
62
|
+
<circle cx="50" cy="72" r="6" fill={BRASS} stroke={INK} strokeWidth="2" />
|
|
53
63
|
</>
|
|
54
64
|
),
|
|
55
65
|
chest: (
|
|
56
66
|
<>
|
|
57
|
-
<path {...
|
|
58
|
-
<rect {...
|
|
59
|
-
<rect
|
|
60
|
-
<
|
|
61
|
-
<
|
|
67
|
+
<path {...O} d="M20,44 Q20,26 50,26 Q80,26 80,44 L80,52 L20,52 Z" fill="#9a6537" />
|
|
68
|
+
<rect {...O} x="20" y="52" width="60" height="26" rx="3" fill={WOOD} />
|
|
69
|
+
<rect x="30" y="28" width="6" height="49" fill={BRASS} stroke={INK} strokeWidth="1.5" />
|
|
70
|
+
<rect x="64" y="28" width="6" height="49" fill={BRASS} stroke={INK} strokeWidth="1.5" />
|
|
71
|
+
<rect {...O} x="44" y="46" width="12" height="14" rx="2" fill={BRASS} />
|
|
72
|
+
<circle cx="50" cy="53" r="2" fill={INK} />
|
|
62
73
|
</>
|
|
63
74
|
),
|
|
64
75
|
stove: (
|
|
65
76
|
<>
|
|
66
|
-
<rect {...
|
|
67
|
-
<rect {...
|
|
68
|
-
<
|
|
69
|
-
<circle
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<>
|
|
75
|
-
<path {...S} d="M20,32 L20,76 M80,44 L80,76" />
|
|
76
|
-
<path {...F} d="M20,52 L80,52 L80,66 L20,66 Z" opacity="0.7" />
|
|
77
|
-
<rect {...F} x="24" y="44" width="18" height="9" rx="4" opacity="0.9" />
|
|
78
|
-
<path {...F} d="M20,32 Q26,26 32,32 L32,44 L20,44 Z" opacity="0.5" />
|
|
77
|
+
<rect {...O} x="24" y="36" width="52" height="44" rx="5" fill="#3a3644" />
|
|
78
|
+
<rect {...O} x="32" y="46" width="24" height="22" rx="3" fill="#f76b15" />
|
|
79
|
+
<path d="M36,64 Q40,52 44,64 Q48,52 52,64 Z" fill="#ffc53d" />
|
|
80
|
+
<circle cx="66" cy="46" r="3.5" fill={BRASS} />
|
|
81
|
+
<circle cx="66" cy="56" r="3.5" fill={BRASS} />
|
|
82
|
+
<path {...O} d="M62,36 L62,20 Q62,14 70,14" fill="none" strokeWidth="5" stroke="#3a3644" />
|
|
83
|
+
<rect x="28" y="80" width="8" height="7" fill={INK} />
|
|
84
|
+
<rect x="64" y="80" width="8" height="7" fill={INK} />
|
|
79
85
|
</>
|
|
80
86
|
),
|
|
81
87
|
desk: (
|
|
82
88
|
<>
|
|
83
|
-
<rect {...
|
|
84
|
-
<rect {...
|
|
85
|
-
<
|
|
86
|
-
<
|
|
87
|
-
<rect
|
|
89
|
+
<rect {...O} x="16" y="34" width="68" height="9" rx="2" fill={WOOD} />
|
|
90
|
+
<rect {...O} x="22" y="43" width="26" height="24" fill={WOOD_D} />
|
|
91
|
+
<rect x="26" y="47" width="18" height="7" fill={WOOD} stroke={INK} strokeWidth="1.5" />
|
|
92
|
+
<circle cx="35" cy="50.5" r="1.8" fill={BRASS} />
|
|
93
|
+
<rect x="26" y="57" width="18" height="7" fill={WOOD} stroke={INK} strokeWidth="1.5" />
|
|
94
|
+
<circle cx="35" cy="60.5" r="1.8" fill={BRASS} />
|
|
95
|
+
<path d="M76,43 L76,78 M22,67 L22,78" stroke={INK} strokeWidth="4" strokeLinecap="round" />
|
|
96
|
+
<rect x="50" y="27" width="22" height="8" rx="1" fill={CREAM} stroke={INK} strokeWidth="1.5" transform="rotate(-5 61 31)" />
|
|
97
|
+
<rect x="52" y="35" width="26" height="4" fill="#3f5c57" />
|
|
88
98
|
</>
|
|
89
99
|
),
|
|
90
100
|
statue: (
|
|
91
101
|
<>
|
|
92
|
-
<rect {...
|
|
93
|
-
<rect {...
|
|
94
|
-
<path {...
|
|
102
|
+
<rect {...O} x="30" y="72" width="40" height="12" fill="#7d7691" />
|
|
103
|
+
<rect {...O} x="36" y="62" width="28" height="10" fill="#9a93b0" />
|
|
104
|
+
<path {...O} d="M44,62 Q40,48 46,40 Q42,34 47,28 Q44,20 50,18 Q56,20 53,28 Q58,34 54,40 Q60,48 56,62 Z" fill="#b8b2c4" />
|
|
105
|
+
<circle cx="50" cy="22" r="1.5" fill="#8d87a0" />
|
|
95
106
|
</>
|
|
96
107
|
),
|
|
97
108
|
telescope: (
|
|
98
109
|
<>
|
|
99
|
-
<path {...
|
|
100
|
-
<path {...
|
|
101
|
-
<
|
|
102
|
-
<
|
|
110
|
+
<path {...O} d="M28,58 L62,28 L72,40 L38,68 Z" fill={BRASS} />
|
|
111
|
+
<path {...O} d="M60,22 L78,36 L73,42 L56,28 Z" fill="#a3821a" />
|
|
112
|
+
<rect x="40" y="48" width="14" height="6" fill="#a3821a" transform="rotate(-41 47 51)" />
|
|
113
|
+
<path d="M46,62 L34,86 M46,62 L60,86" stroke={INK} strokeWidth="5" strokeLinecap="round" />
|
|
114
|
+
<circle {...O} cx="47" cy="60" r="7" fill="#3a3644" />
|
|
115
|
+
<circle cx="76" cy="33" r="2.5" fill="#e6f4ff" />
|
|
103
116
|
</>
|
|
104
117
|
),
|
|
105
118
|
lamp: (
|
|
106
119
|
<>
|
|
107
|
-
<
|
|
108
|
-
<
|
|
109
|
-
<
|
|
120
|
+
<ellipse cx="50" cy="34" rx="26" ry="10" fill="#ffd76a" opacity="0.35" />
|
|
121
|
+
<path {...O} d="M32,16 L68,16 L59,40 L41,40 Z" fill="#ffc53d" />
|
|
122
|
+
<line x1="50" y1="40" x2="50" y2="78" stroke={BRASS} strokeWidth="4" />
|
|
123
|
+
<path {...O} d="M34,84 Q50,76 66,84 Z" fill={WOOD_D} />
|
|
110
124
|
</>
|
|
111
125
|
),
|
|
112
|
-
|
|
126
|
+
crate: (
|
|
113
127
|
<>
|
|
114
|
-
<
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
<
|
|
128
|
+
<rect {...O} x="22" y="28" width="56" height="50" fill="#c9a06a" />
|
|
129
|
+
<line x1="22" y1="45" x2="78" y2="45" stroke={WOOD_D} strokeWidth="3" />
|
|
130
|
+
<line x1="22" y1="62" x2="78" y2="62" stroke={WOOD_D} strokeWidth="3" />
|
|
131
|
+
<rect x="38" y="32" width="24" height="9" rx="1.5" fill={CREAM} stroke={INK} strokeWidth="1.5" />
|
|
132
|
+
{[27, 73].map(x => [52, 69].map(y => <circle key={x + ':' + y} cx={x} cy={y} r="1.8" fill={WOOD_D} />))}
|
|
118
133
|
</>
|
|
119
134
|
),
|
|
120
|
-
|
|
135
|
+
harp: (
|
|
121
136
|
<>
|
|
122
|
-
<
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
<path {...O} d="M30,82 L30,30 Q30,12 50,14 Q72,16 74,44 L74,82 Z" fill="none" stroke={BRASS} strokeWidth="7" />
|
|
138
|
+
<path {...O} d="M24,80 L80,80 L76,88 L28,88 Z" fill={BRASS} />
|
|
139
|
+
{[40, 48, 56, 64].map((x, i) => (
|
|
140
|
+
<line key={x} x1={x} y1={24 + i * 4} x2={x} y2="80" stroke={CREAM} strokeWidth="2" />
|
|
141
|
+
))}
|
|
142
|
+
</>
|
|
143
|
+
)
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const WIDE = {
|
|
147
|
+
sofa: (
|
|
148
|
+
<>
|
|
149
|
+
<path {...O} d="M28,26 Q100,12 172,26 L172,56 L28,56 Z" fill="#a04848" />
|
|
150
|
+
<rect {...O} x="34" y="46" width="64" height="20" rx="7" fill="#b85c5c" />
|
|
151
|
+
<rect {...O} x="102" y="46" width="64" height="20" rx="7" fill="#b85c5c" />
|
|
152
|
+
<rect {...O} x="14" y="34" width="18" height="36" rx="9" fill="#8d3d3d" />
|
|
153
|
+
<rect {...O} x="168" y="34" width="18" height="36" rx="9" fill="#8d3d3d" />
|
|
154
|
+
<rect x="36" y="70" width="8" height="12" fill={WOOD_D} />
|
|
155
|
+
<rect x="156" y="70" width="8" height="12" fill={WOOD_D} />
|
|
156
|
+
<path d="M60,32 Q66,38 60,44 M136,32 Q142,38 136,44" stroke="#7d3535" strokeWidth="2.5" fill="none" />
|
|
126
157
|
</>
|
|
127
158
|
),
|
|
128
|
-
|
|
159
|
+
bed: (
|
|
129
160
|
<>
|
|
130
|
-
<rect {...
|
|
131
|
-
<
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
<
|
|
135
|
-
<
|
|
136
|
-
<
|
|
137
|
-
<
|
|
161
|
+
<rect {...O} x="14" y="18" width="16" height="58" rx="4" fill={WOOD} />
|
|
162
|
+
<rect {...O} x="176" y="30" width="12" height="46" rx="3" fill={WOOD} />
|
|
163
|
+
<rect {...O} x="28" y="40" width="150" height="30" rx="6" fill={CREAM} />
|
|
164
|
+
<path {...O} d="M74,40 L178,40 L178,70 L74,70 Q66,55 74,40 Z" fill="#41609e" />
|
|
165
|
+
<path d="M84,48 L170,48 M80,56 L170,56" stroke="#33518b" strokeWidth="2.5" />
|
|
166
|
+
<rect {...O} x="36" y="44" width="30" height="16" rx="7" fill="#ffffff" />
|
|
167
|
+
<rect x="32" y="70" width="8" height="10" fill={WOOD_D} />
|
|
168
|
+
<rect x="168" y="70" width="8" height="10" fill={WOOD_D} />
|
|
169
|
+
</>
|
|
170
|
+
)
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const WALL = {
|
|
174
|
+
window: (
|
|
175
|
+
<>
|
|
176
|
+
<rect x="6" y="2" width="88" height="32" rx="4" fill={CREAM} stroke={INK} strokeWidth="3" />
|
|
177
|
+
<rect x="12" y="7" width="36" height="22" fill="#2b3a5c" stroke={INK} strokeWidth="2" />
|
|
178
|
+
<rect x="52" y="7" width="36" height="22" fill="#33456b" stroke={INK} strokeWidth="2" />
|
|
179
|
+
<circle cx="72" cy="13" r="4" fill="#f4efe2" opacity="0.9" />
|
|
180
|
+
<circle cx="22" cy="12" r="1.3" fill="#cfe0ff" />
|
|
181
|
+
<circle cx="36" cy="22" r="1" fill="#cfe0ff" />
|
|
182
|
+
<path d="M52,26 L64,10" stroke="#8fa8d8" strokeWidth="1.5" opacity="0.7" />
|
|
138
183
|
</>
|
|
139
184
|
),
|
|
140
|
-
|
|
185
|
+
painting: (
|
|
141
186
|
<>
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
187
|
+
<rect x="8" y="2" width="84" height="32" rx="3" fill={BRASS} stroke={INK} strokeWidth="3" />
|
|
188
|
+
<rect x="15" y="8" width="70" height="20" fill="#8fb8d8" />
|
|
189
|
+
<path d="M15,24 L34,13 L46,22 L60,10 L85,24 L85,28 L15,28 Z" fill="#46a758" stroke={INK} strokeWidth="1" />
|
|
190
|
+
<circle cx="70" cy="13" r="3" fill="#ffd76a" />
|
|
191
|
+
</>
|
|
192
|
+
),
|
|
193
|
+
mirror: (
|
|
194
|
+
<>
|
|
195
|
+
<ellipse cx="50" cy="18" rx="42" ry="16" fill={BRASS} stroke={INK} strokeWidth="3" />
|
|
196
|
+
<ellipse cx="50" cy="18" rx="34" ry="11" fill="#cfd8e8" stroke={INK} strokeWidth="1.5" />
|
|
197
|
+
<path d="M30,22 L44,11 M40,24 L52,14" stroke="#f2f6ff" strokeWidth="2.5" opacity="0.8" />
|
|
147
198
|
</>
|
|
148
199
|
)
|
|
149
200
|
};
|
|
150
201
|
|
|
151
|
-
export function
|
|
202
|
+
export function FloorIcon({ type, size = 40, className = '' }) {
|
|
152
203
|
return (
|
|
153
|
-
<svg viewBox="0 0 100 100" width={size} height={size} className={`obj-icon ${className}`}
|
|
154
|
-
|
|
155
|
-
{ICONS[type] || <circle {...S} cx="50" cy="50" r="26" />}
|
|
204
|
+
<svg viewBox="0 0 100 100" width={size} height={size} className={`obj-icon ${className}`} role="img" aria-label={type}>
|
|
205
|
+
{FLOOR[type] || FLOOR.crate}
|
|
156
206
|
</svg>
|
|
157
207
|
);
|
|
158
208
|
}
|
|
209
|
+
|
|
210
|
+
export const hasWide = (type) => !!WIDE[type];
|
|
211
|
+
export const hasWall = (type) => !!WALL[type];
|
|
212
|
+
export const wideArt = (type) => WIDE[type] || WIDE.sofa;
|
|
213
|
+
export const wallArt = (type) => WALL[type] || WALL.window;
|
|
214
|
+
export const floorArt = (type) => FLOOR[type] || FLOOR.crate;
|
|
215
|
+
|
|
216
|
+
// kept for any legacy callers
|
|
217
|
+
export function ObjectIcon(props) {
|
|
218
|
+
return <FloorIcon {...props} />;
|
|
219
|
+
}
|