@homie-rocks/studio 0.24.4 → 0.24.5
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 +24 -1
- package/README.md +3 -2
- package/chat/CHAT.md +36 -5
- package/lib/build.mjs +3 -0
- package/lib/scaffold.mjs +10 -4
- package/lib/template-history.json +39 -0
- package/netplay/NETPLAY.md +9 -5
- package/package.json +1 -1
- package/site/SITE.md +4 -0
- package/worker/chat-page.mjs +173 -28
- package/worker/chat.mjs +4 -1
- package/worker/version.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -17,9 +17,32 @@ To bring a studio up to date, tell Claude: "Upgrade my studio to the newest Homi
|
|
|
17
17
|
`npx -y @homie-rocks/studio@latest upgrade`, which shows what's new since the version your studio pins (from this
|
|
18
18
|
file) and what the upgrade would change, and changes nothing until you agree.
|
|
19
19
|
|
|
20
|
+
## [0.24.5] - 2026-10-03
|
|
21
|
+
|
|
22
|
+
**Plugin 0.25.5** · [#39](https://github.com/homie-rocks/homie/pull/39)
|
|
23
|
+
|
|
24
|
+
Room chat keeps off your game's HUD: on a phone the Chat pill is a round icon, as the room button is, and your game can say where the strip of new lines sits, or keep new lines in the Chat sheet, so a game with a busy HUD keeps typed chat.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- game.json `"screen": { "chat": … }` says where room chat's strip of new lines sits on each screen: `at` (a corner, `top-center` or `bottom-center`), `x` / `y` to move it in from its side and its edge, and `"lines": "sheet-only"` to keep new lines in the Chat sheet while the pill counts them. `desk`, `phone`, `sideways` and `tv` (the big screen's corner of lines) each take their own, changing only what they name. A game whose HUD or controls sit where the strip shows no longer has to keep chat to emoji to stay clear. `chat/CHAT.md` has every field; the template's AGENTS.md and the game skill say when to use it.
|
|
29
|
+
- `homie-studio build` warns about a wrong field in `screen.chat` (the play page uses the default there), and says so when a place or `"sheet-only"` is written in game.json `"chat"` (the room's rules) instead.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- The Chat pill follows the room button: on a phone (either way up), and beside a room button your game keeps an icon (`screen.share` `"label": false`), it is a round icon with the count of new lines on its corner. On a computer it still says "Chat", unless the word would take the room button's band off the screen or into the middle third of its width, where games keep a clock or a title; then it stays the icon until the window changes size.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- On a phone, the Chat pill's word no longer covers the HUD beside the room button: the first digit of a clock, the last letter of a title.
|
|
38
|
+
|
|
39
|
+
### Upgrade notes
|
|
40
|
+
|
|
41
|
+
- Nothing to do: a game without `screen.chat` shows the strip where it did. If you kept a game's chat to emoji only so the strip stayed off its HUD, tell Claude: "Put room chat where my game has room, and let players type again." It places the strip with `screen.chat` and resets the game's chat rules (`homie-studio chat rules <game> --reset`).
|
|
42
|
+
|
|
20
43
|
## [0.24.4] - 2026-10-03
|
|
21
44
|
|
|
22
|
-
**Plugin 0.25.4** · [#37](https://github.com/homie-rocks/homie/pull/37)
|
|
45
|
+
**Plugin 0.25.4** · [#37](https://github.com/homie-rocks/homie/pull/37) · [release-2026-10-03-studio-0.24.4](https://github.com/homie-rocks/homie/releases/tag/release-2026-10-03-studio-0.24.4)
|
|
23
46
|
|
|
24
47
|
Your AI guides think with Cloudflare's Clef decision model, which does what a player asks; Clef runs on your own computer while you build; and your games can ask it for their own decisions (tactics, a director's call, a turn) in about a quarter of a second.
|
|
25
48
|
|
package/README.md
CHANGED
|
@@ -316,8 +316,9 @@ npx homie-studio chat remove night-rush pub-3 <line id> # take a
|
|
|
316
316
|
npx homie-studio chat words # the built-in word list the floor holds
|
|
317
317
|
```
|
|
318
318
|
|
|
319
|
-
- **The play page:** a Chat pill beside the room button (the corner `screen.share` keeps clear of the HUD
|
|
320
|
-
sheet (the last minutes, the reactions, the quick lines, typing, Report), a short
|
|
319
|
+
- **The play page:** a Chat pill beside the room button (the corner `screen.share` keeps clear of the HUD; a round
|
|
320
|
+
icon on a phone), its sheet (the last minutes, the reactions, the quick lines, typing, Report), a short strip of
|
|
321
|
+
new lines (game.json `screen.chat` puts it where the game has room, or keeps new lines in the sheet), and two
|
|
321
322
|
switches each player keeps: "Show my messages over my character" and "Show chat on this screen". The big screen
|
|
322
323
|
shows the lines in a corner; the watch page has a Chat button; homie.rocks shows a room's chat on its card.
|
|
323
324
|
- **Rules** per game (game.json `"chat"`) and per server, in the office or with `chat rules`: off, emoji, quick
|
package/chat/CHAT.md
CHANGED
|
@@ -14,9 +14,12 @@ studio's owner is responsible for, read [OWNERS.md](OWNERS.md).
|
|
|
14
14
|
|
|
15
15
|
- **The Chat pill** on the play page, beside the room button: the room's last minutes, the
|
|
16
16
|
reactions, the quick lines, typing where allowed, Report on a line, and two switches ("Show my
|
|
17
|
-
messages over my character", "Show chat on this screen").
|
|
17
|
+
messages over my character", "Show chat on this screen"). On a phone it is a round icon, as the
|
|
18
|
+
room button is, with the count of new lines on its corner; on a computer it says "Chat" unless
|
|
19
|
+
the word would reach the middle of the top edge, where games keep a clock or a title.
|
|
18
20
|
- **The float:** every reaction rises up every screen in the room.
|
|
19
|
-
- **The
|
|
21
|
+
- **The strip:** a new line shows for a moment where the play page's status chip sits (the bottom
|
|
22
|
+
left), or where the game says (below).
|
|
20
23
|
- **The big screen** (`/<game>/tv`) shows the room's lines in a corner; the **watch page** has a
|
|
21
24
|
Chat button; **homie.rocks** shows the room's chat on its card when the studio allows it.
|
|
22
25
|
|
|
@@ -49,9 +52,37 @@ studio's owner is responsible for, read [OWNERS.md](OWNERS.md).
|
|
|
49
52
|
| `block`, `allow` | the studio's own words to hold (`word*` inside words) or to let through | none |
|
|
50
53
|
|
|
51
54
|
`"chat": false` turns it off. A kids server, and every beginner server, keeps chat to emoji and
|
|
52
|
-
quick lines whatever the game says.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
quick lines whatever the game says.
|
|
56
|
+
|
|
57
|
+
**Where it sits on the screen, in game.json `"screen"`**, so a game with a busy HUD keeps typed
|
|
58
|
+
chat without anything covering its clock, its title or its controls. The Chat pill sits in the
|
|
59
|
+
room button's band, so `"screen": { "share": … }` places both (`"label": false` keeps both round
|
|
60
|
+
icons). `"screen": { "chat": … }` places the strip of new lines and the big screen's corner:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
"screen": {
|
|
64
|
+
"share": { "phone": { "at": "top-left", "label": false } },
|
|
65
|
+
"chat": {
|
|
66
|
+
"desk": { "at": "top-left", "y": 130 },
|
|
67
|
+
"phone": { "lines": "sheet-only" },
|
|
68
|
+
"tv": { "at": "top-right" }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Field | | Default |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `at` | `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center` or `bottom-right` | the bottom left (the bottom right when the room button is at the bottom left) |
|
|
76
|
+
| `x`, `y` | move it in from its side and its edge, in CSS pixels (0 to 600; in the middle of an edge, `x` moves it either way). Sharing the room button's place, it also sits past the button | 0 |
|
|
77
|
+
| `lines` | `strip` (new lines show for a moment), or `sheet-only` (they wait in the Chat sheet and the pill counts them) | `strip` |
|
|
78
|
+
| `desk`, `phone`, `sideways`, `tv` | a computer, a phone held upright, a phone turned sideways (else as `phone`), the big screen: each changes only what it names | |
|
|
79
|
+
|
|
80
|
+
A plain string (`"chat": "bottom-right"`) is a place for every device, and `false` is
|
|
81
|
+
`"lines": "sheet-only"` for every device. The big screen has no sheet: it takes a corner only
|
|
82
|
+
(the top level's when that is a corner, else the one its join card leaves free) and keeps its
|
|
83
|
+
lines unless its own `tv` entry says `"sheet-only"`, which leaves it the float. A wrong field is
|
|
84
|
+
the default on the page, and `homie-studio build` says which. Look at the play page on a phone
|
|
85
|
+
and a computer while a round is on and somebody says something, and at `/<id>/tv`.
|
|
55
86
|
|
|
56
87
|
**Speech bubbles over characters**, with the port kit (the starters draw them):
|
|
57
88
|
|
package/lib/build.mjs
CHANGED
|
@@ -47,6 +47,7 @@ import { STUDIO_VERSION } from './version.mjs';
|
|
|
47
47
|
import { licenseOf, remixRow } from '../worker/license.mjs';
|
|
48
48
|
import { SERVER_LIMITS, serverOf } from '../worker/servers.mjs';
|
|
49
49
|
import { chatProblems } from '../worker/chat.mjs';
|
|
50
|
+
import { screenChatProblems } from '../worker/chat-page.mjs';
|
|
50
51
|
import { vocabularyOf } from '../worker/brain.mjs';
|
|
51
52
|
import { shopForBuild } from './shop.mjs';
|
|
52
53
|
import { audienceOf } from '../worker/shop-rules.mjs';
|
|
@@ -323,6 +324,8 @@ export async function build(root, { only = null, log = () => {}, deploy = proces
|
|
|
323
324
|
if (chatBad.length) throw new Error(`games/${g.id}/game.json: ${chatBad.join('; ')}`);
|
|
324
325
|
// A game's own decisions (NETPLAY.md section 20): game.json "decide" is true or false (the Worker answers net.decide only then).
|
|
325
326
|
if (g.decide !== undefined && typeof g.decide !== 'boolean') throw new Error(`games/${g.id}/game.json: "decide" is true (the host may ask the studio's decision model, within the AI brains' day) or false`);
|
|
327
|
+
// Where chat sits on the screen (game.json "screen": { "chat" }, 0.24.5): a wrong field is the default on the page.
|
|
328
|
+
for (const p of screenChatProblems(g.screen?.chat)) log(`warning: games/${g.id}/game.json: ${p}; the play page uses the default there`);
|
|
326
329
|
// The game's own source, for other studios to remix (game.json "share": { "source": false } keeps it private).
|
|
327
330
|
if (g.share?.source !== false) writeFileSync(join(out, 'source.json'), `${JSON.stringify(sourceOf(g.dir, g.id, { studio: studio.name ?? null, game: g.name ?? g.id, license: g.license }))}\n`);
|
|
328
331
|
// Its assets' licences, and the address and SHA-256 of each one a remix may carry (`game remix` fetches them).
|
package/lib/scaffold.mjs
CHANGED
|
@@ -221,10 +221,16 @@ studio's pinned copy, never a registry lookup of the bare name.
|
|
|
221
221
|
- Import it as \`import { createNetplay } from '@homie-rocks/studio/netplay'\`.
|
|
222
222
|
- Phones and computers: touch controls on phones only, keys on computers; keep the
|
|
223
223
|
centre of the screen clear during play.
|
|
224
|
-
- The play page's small room button (Invite, Big screen, the room code) sits top right
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
- The play page's small room button (Invite, Big screen, the room code) sits top right, with the Chat pill
|
|
225
|
+
beside it (a round icon on a phone, as the room button is). If the game's scoreboard or a bar is there, move
|
|
226
|
+
both in game.json \`"screen": { "share": … }\`: a corner or \`top-center\`, per device (\`desk\`, \`phone\`,
|
|
227
|
+
\`sideways\`), with an \`x\` / \`y\` offset in pixels, and \`"label": false\` to keep them small icons
|
|
228
|
+
(\`node_modules/@homie-rocks/studio/site/SITE.md\`). Room chat's strip of new lines shows for a moment at the
|
|
229
|
+
bottom left: if the game's HUD or controls are there, \`"screen": { "chat": … }\` puts it where the game has
|
|
230
|
+
room (\`at\`: a corner, \`top-center\` or \`bottom-center\`; \`x\` / \`y\`; per device, and \`tv\` for the big
|
|
231
|
+
screen), or \`"lines": "sheet-only"\` keeps new lines in the Chat sheet, the pill counting them, so a game with
|
|
232
|
+
a busy HUD keeps typed chat (\`node_modules/@homie-rocks/studio/chat/CHAT.md\`). Look at both on a phone and a
|
|
233
|
+
computer while a round is on and somebody says something.
|
|
228
234
|
- **Watch any player.** Every live room can be watched at \`/<id>/watch?room=<room>\` (each room on the Rooms page
|
|
229
235
|
and the landing has a Watch button): the game itself, drawn by the watcher's own browser, which never takes a
|
|
230
236
|
seat, with a strip of the players to switch between (a tap, keys 1-9, A for Auto, O for the whole room). Point the
|
|
@@ -1156,6 +1156,45 @@
|
|
|
1156
1156
|
}
|
|
1157
1157
|
}
|
|
1158
1158
|
}
|
|
1159
|
+
},
|
|
1160
|
+
"0.24.5": {
|
|
1161
|
+
"files": {
|
|
1162
|
+
"CLAUDE.md": "ddf2b2a0376202f6",
|
|
1163
|
+
"README.md": "684b3cca8001673c",
|
|
1164
|
+
"HANDOFF.md": "ce7a8fd91c6bbee2",
|
|
1165
|
+
"games/README.md": "5ad32fe80c61f117",
|
|
1166
|
+
"music/README.md": "a3dc042dadd39df8",
|
|
1167
|
+
"videos/README.md": "16babc3437d5545b",
|
|
1168
|
+
"posts/README.md": "37841aae476bd540",
|
|
1169
|
+
"site/README.md": "ca131cd0472464fb",
|
|
1170
|
+
"site/src/worker.mjs": "ca3f1630f2420ad3"
|
|
1171
|
+
},
|
|
1172
|
+
"sections": {
|
|
1173
|
+
"AGENTS.md": {
|
|
1174
|
+
"(intro)": "214aa46eb3a895d5",
|
|
1175
|
+
"## Layout": "8baf49e4291e7e88",
|
|
1176
|
+
"## Commands (all through the pinned CLI in node_modules)": "d80ec56e4b4fc4c6",
|
|
1177
|
+
"## The site": "ab922bb6bde23293",
|
|
1178
|
+
"## Making games": "abf2f0d59341512d",
|
|
1179
|
+
"## The Game Codex and progress": "0738ef719cef3a4c",
|
|
1180
|
+
"## Art direction and models": "124772c374de7390",
|
|
1181
|
+
"## Running live games (the back office)": "4aa5f513a7cd2c3b",
|
|
1182
|
+
"## Servers and AI seats": "98d08705087a33ec",
|
|
1183
|
+
"## Selling things (the shop)": "b04b0a2b25f524f2",
|
|
1184
|
+
"## Continuing a build from the Claude app": "0d68081e924ca419",
|
|
1185
|
+
"## Rules": "7e8458adc1b81ece",
|
|
1186
|
+
"## Beta": "9d02638207a26e73"
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
"named": {
|
|
1190
|
+
"Homie Arcade": {
|
|
1191
|
+
"sections": {
|
|
1192
|
+
"AGENTS.md": {
|
|
1193
|
+
"## Commands (all through the pinned CLI in node_modules)": "b35df2c19cd4b534"
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1159
1198
|
}
|
|
1160
1199
|
}
|
|
1161
1200
|
}
|
package/netplay/NETPLAY.md
CHANGED
|
@@ -1340,16 +1340,20 @@ ticker.
|
|
|
1340
1340
|
### What the page does (worker/chat-page.mjs)
|
|
1341
1341
|
|
|
1342
1342
|
- **Play:** a Chat pill in the room button's band (game.json `screen.share` already keeps that
|
|
1343
|
-
corner clear of the game's HUD
|
|
1343
|
+
corner clear of the game's HUD; a round icon on a phone and beside a room button kept an icon,
|
|
1344
|
+
and on a computer wherever its word would reach the middle third of the top), with a count of
|
|
1345
|
+
unread lines; its sheet: the room's last
|
|
1344
1346
|
minutes, a row of reactions (2.5 rem circles that pop), the quick lines, typing where the rules
|
|
1345
1347
|
allow it (and why not where they do not: "Sign in to type here. Emoji are open to everyone."),
|
|
1346
1348
|
Report on a line, and two switches kept in this browser: **Show my messages over my character**
|
|
1347
|
-
and **Show chat on this screen** (off: no
|
|
1348
|
-
show for a moment where the status chip sits
|
|
1349
|
-
|
|
1349
|
+
and **Show chat on this screen** (off: no strip, no float, no bubbles in this game). New lines
|
|
1350
|
+
show for a moment where the status chip sits; game.json `"screen": { "chat": … }` moves that
|
|
1351
|
+
strip per device (`at`, `x` / `y`, `desk`, `phone`, `sideways`, `tv`) or keeps new lines in the
|
|
1352
|
+
sheet (`"lines": "sheet-only"`, or `false`: only the pill's count). chat/CHAT.md has the fields.
|
|
1350
1353
|
- **Float:** every reaction rises up every screen in the room, as on the television: at most six
|
|
1351
1354
|
a second and eighteen at once, 2.4 s each with a little sway. The sender's own floats at once.
|
|
1352
|
-
- **TV** (`/<game>/tv`): the room's lines in the corner the QR card does not use
|
|
1355
|
+
- **TV** (`/<game>/tv`): the room's lines in the corner the QR card does not use (or game.json
|
|
1356
|
+
`screen.chat`'s corner), no typing.
|
|
1353
1357
|
- **Watch:** a Chat button in the band; its panel over the stage (a bottom sheet on a phone).
|
|
1354
1358
|
|
|
1355
1359
|
**Privacy.** Nothing a person says is stored: the room keeps the last 50 lines of the last 15
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homie-rocks/studio",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.5",
|
|
4
4
|
"description": "A studio for games, music and video on its own Cloudflare: scaffold, multiplayer starters on the netplay contract, a port toolkit that makes existing single-player web games multiplayer, the site Worker with public rooms (Table/Lobby Durable Objects and D1, all on the free Workers plan, no payment method), a site with the hub's sections (Home, Games, Music, Videos, Rooms, Posts with feeds) in the studio's own look, an epic landing page for every game, big songs and videos in the studio's own R2 once it has storage (uploaded, checked by SHA-256, served at the same addresses), deploy, and the homie.rocks directory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/site/SITE.md
CHANGED
|
@@ -169,6 +169,10 @@ where the game has room, per device:
|
|
|
169
169
|
- A button at the bottom left sends the "finding a room" chip to the bottom right.
|
|
170
170
|
- On a server, the server's pill sits beside the button, on its inner side in the same band, never under it: on a
|
|
171
171
|
phone held upright it is one more dot; elsewhere its name shows for a few seconds, then it is a dot too.
|
|
172
|
+
- Room chat's Chat pill sits in the same band: a round icon on a phone (either way up) and beside a button kept an
|
|
173
|
+
icon; on a computer it says "Chat" unless the word would take the band off the screen or into the middle third of
|
|
174
|
+
its width, where games keep a clock or a title. Its strip of new lines (bottom left by default) moves with
|
|
175
|
+
`"screen": { "chat": … }`, per device, or stays in the Chat sheet (`"lines": "sheet-only"`): chat/CHAT.md.
|
|
172
176
|
|
|
173
177
|
Look at the play page on a computer and a phone (both ways up) once a round is on: the button must not sit on a
|
|
174
178
|
score, a timer or a bar. A room code the relay cannot use (1 to 32 letters, digits, `-` or `_`) is refused
|
package/worker/chat-page.mjs
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* screen (the TV view) and the watch page. It speaks on the page's own watch socket, so every game has it, old builds
|
|
4
4
|
* too, with no game code:
|
|
5
5
|
*
|
|
6
|
-
* play a Chat pill beside the room button (the corner game.json screen.share keeps clear of the game's HUD
|
|
7
|
-
* its sheet (the room's last minutes, the room's emoji, its
|
|
8
|
-
* Report on a line, and for the studio's owner Remove, Mute and
|
|
9
|
-
* status chip sits
|
|
6
|
+
* play a Chat pill beside the room button (the corner game.json screen.share keeps clear of the game's HUD; the
|
|
7
|
+
* round icon on a phone, as the room button is), its sheet (the room's last minutes, the room's emoji, its
|
|
8
|
+
* quick lines, typing where the rules allow, Report on a line, and for the studio's owner Remove, Mute and
|
|
9
|
+
* Kick), a short strip of new lines where the status chip sits or where game.json screen.chat puts it
|
|
10
|
+
* (chatPlaces; "sheet-only" keeps them in the sheet), and reactions floating up the screen
|
|
10
11
|
* tv the room's lines in a corner (no typing: a television is read across a room) and the float
|
|
11
12
|
* watch a Chat button in the watch page's band, its panel over the stage, and the float
|
|
12
13
|
*
|
|
@@ -22,17 +23,86 @@ import { REACTIONS } from './chat.mjs';
|
|
|
22
23
|
import { REPORT_REASONS, REPORT_WORDS } from './chat-store.mjs';
|
|
23
24
|
import { NET_PALETTE } from './room.mjs';
|
|
24
25
|
|
|
26
|
+
/** Where room chat's strip of new lines may sit: a corner, or the middle of the top or bottom edge. */
|
|
27
|
+
export const CHAT_PLACES = ['top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'];
|
|
28
|
+
const CHAT_CORNERS = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
|
|
29
|
+
const CHAT_DEVICES = ['desk', 'phone', 'sideways', 'tv'];
|
|
30
|
+
const CHAT_FIELDS = ['at', 'x', 'y', 'lines'];
|
|
31
|
+
|
|
32
|
+
/** One entry of game.json `screen.chat` as written (a place, false, or an object), keeping only the fields that are right. */
|
|
33
|
+
function chatEntry(v) {
|
|
34
|
+
if (v === false) return { lines: 'sheet-only' };
|
|
35
|
+
if (typeof v === 'string') return CHAT_PLACES.includes(v) ? { at: v } : {};
|
|
36
|
+
if (!v || typeof v !== 'object' || Array.isArray(v)) return {};
|
|
37
|
+
const o = {};
|
|
38
|
+
if (CHAT_PLACES.includes(v.at)) o.at = v.at;
|
|
39
|
+
for (const k of ['x', 'y']) if (v[k] !== undefined && v[k] !== null && v[k] !== '' && Number.isFinite(Number(v[k]))) o[k] = Math.round(Number(v[k]));
|
|
40
|
+
if (v.lines === 'strip' || v.lines === 'sheet-only') o.lines = v.lines;
|
|
41
|
+
return o;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Where room chat sits on each screen, from game.json `screen.chat` (0.24.5), so a game with a busy HUD keeps its chat
|
|
46
|
+
* off its clock, its title and its controls. The Chat pill itself sits in the room button's band (`screen.share`
|
|
47
|
+
* places both); this places the strip of new lines that shows for a moment, and the big screen's corner of lines:
|
|
48
|
+
*
|
|
49
|
+
* "chat": "bottom-right" every device's strip there (and the big screen's corner)
|
|
50
|
+
* "chat": false no strip: new lines wait in the sheet, the pill counts them
|
|
51
|
+
* "chat": { "at": "top-left", "y": 120 } moved 120 px in from its edge (x: from its side)
|
|
52
|
+
* "chat": { "lines": "strip", "phone": { "lines": "sheet-only" }, "tv": { "at": "top-right" } }
|
|
53
|
+
*
|
|
54
|
+
* `at` is a place from CHAT_PLACES (default: the bottom left, or the bottom right when the room button is there); `x`
|
|
55
|
+
* and `y` move it in, 0 to 600 px (in the middle of an edge, `x` moves it either way); `lines` is "strip" (the default)
|
|
56
|
+
* or "sheet-only". `desk`, `phone` (held upright), `sideways` (else as `phone`) and `tv` (the big screen) each change
|
|
57
|
+
* only what they name. The big screen has no sheet: it takes a corner only, its place follows the top level's when that
|
|
58
|
+
* is a corner, and its lines stay unless its own entry says "sheet-only" (then it shows only the float).
|
|
59
|
+
*/
|
|
60
|
+
export function chatPlaces(value) {
|
|
61
|
+
const top = chatEntry(value);
|
|
62
|
+
const per = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
63
|
+
const done = (o) => {
|
|
64
|
+
const at = o.at ?? null;
|
|
65
|
+
const clamp = (n, lo) => Math.max(lo, Math.min(600, n ?? 0));
|
|
66
|
+
return { at, x: clamp(o.x, at && at.endsWith('center') ? -600 : 0), y: clamp(o.y, 0), lines: o.lines ?? 'strip' };
|
|
67
|
+
};
|
|
68
|
+
const phone = { ...top, ...chatEntry(per.phone) };
|
|
69
|
+
const { lines: _l, ...where } = top;
|
|
70
|
+
const own = chatEntry(per.tv);
|
|
71
|
+
if (own.at && !CHAT_CORNERS.includes(own.at)) delete own.at;
|
|
72
|
+
const tv = { ...(CHAT_CORNERS.includes(where.at) ? where : {}), ...own };
|
|
73
|
+
return { desk: done({ ...top, ...chatEntry(per.desk) }), phone: done(phone), sideways: done({ ...phone, ...chatEntry(per.sideways) }), tv: done(tv) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** game.json `screen.chat` as the build reads it: the problems in words (the page uses the default for anything wrong). */
|
|
77
|
+
export function screenChatProblems(value) {
|
|
78
|
+
if (value === undefined || value === null || value === false) return [];
|
|
79
|
+
const out = [];
|
|
80
|
+
const one = (v, name, top) => {
|
|
81
|
+
if (v === false) return;
|
|
82
|
+
if (typeof v === 'string') { if (!CHAT_PLACES.includes(v)) out.push(`${name} "${v}" is not a place (${CHAT_PLACES.join(', ')})`); return; }
|
|
83
|
+
if (!v || typeof v !== 'object' || Array.isArray(v)) { out.push(`${name} is a place, false, or { "at", "x", "y", "lines" }`); return; }
|
|
84
|
+
for (const k of Object.keys(v)) {
|
|
85
|
+
if (top && CHAT_DEVICES.includes(k)) continue;
|
|
86
|
+
if (!CHAT_FIELDS.includes(k)) out.push(`${name}.${k} is not a field (${k === 'corner' || k === 'place' ? 'the place is "at"' : `${CHAT_FIELDS.join(', ')}${top ? `, or ${CHAT_DEVICES.join(', ')}` : ''}`})`);
|
|
87
|
+
}
|
|
88
|
+
if (v.at !== undefined && !CHAT_PLACES.includes(v.at)) out.push(`${name}.at "${v.at}" is not a place (${CHAT_PLACES.join(', ')})`);
|
|
89
|
+
if (name.endsWith('.tv') && v.at !== undefined && !CHAT_CORNERS.includes(v.at)) out.push(`${name}.at: the big screen takes a corner`);
|
|
90
|
+
for (const k of ['x', 'y']) if (v[k] !== undefined && !Number.isFinite(Number(v[k]))) out.push(`${name}.${k} is a number of pixels`);
|
|
91
|
+
if (v.lines !== undefined && v.lines !== 'strip' && v.lines !== 'sheet-only') out.push(`${name}.lines is "strip" or "sheet-only"`);
|
|
92
|
+
};
|
|
93
|
+
one(value, 'screen.chat', true);
|
|
94
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) for (const d of CHAT_DEVICES) if (value[d] !== undefined) one(value[d], `screen.chat.${d}`, false);
|
|
95
|
+
return out.slice(0, 8);
|
|
96
|
+
}
|
|
97
|
+
|
|
25
98
|
/** What a page's chat starts from (`window.__HOMIE_CHAT`): its surface, the game, and who this browser is to the studio. */
|
|
26
|
-
export function chatBoot(g, { surface = 'play', owner = false, acct = false, member = false, room = null
|
|
27
|
-
const screen = g?.screen?.chat;
|
|
28
|
-
const ticker = screen === false ? false : typeof screen === 'string' ? screen : screen && typeof screen === 'object' && typeof screen.at === 'string' ? screen.at : null;
|
|
99
|
+
export function chatBoot(g, { surface = 'play', owner = false, acct = false, member = false, room = null } = {}) {
|
|
29
100
|
return {
|
|
30
101
|
surface, game: g.id, name: g.name, owner: Boolean(owner), acct: Boolean(acct), member: Boolean(member), palette: NET_PALETTE,
|
|
31
102
|
reactions: REACTIONS, reasons: REPORT_REASONS.map((k) => ({ k, text: REPORT_WORDS[k] })),
|
|
32
103
|
...(room ? { room } : {}),
|
|
33
|
-
// game.json "screen": { "chat":
|
|
34
|
-
|
|
35
|
-
...(place ? { place } : {}),
|
|
104
|
+
// game.json "screen": { "chat": … }: where the strip of new lines sits on each device, or "sheet-only" (chatPlaces).
|
|
105
|
+
places: chatPlaces(g?.screen?.chat),
|
|
36
106
|
account: '/account/',
|
|
37
107
|
};
|
|
38
108
|
}
|
|
@@ -42,7 +112,11 @@ export const CHAT_CSS = `
|
|
|
42
112
|
.cpill .cico { width: 15px; height: 15px; flex: none; }
|
|
43
113
|
.cpill .cbadge { min-width: 16px; height: 16px; padding: 0 4px; box-sizing: border-box; border-radius: 8px; background: var(--hot); color: #0b0b10; font: 800 10px/16px ui-sans-serif, system-ui, sans-serif; text-align: center; }
|
|
44
114
|
.cpill.dim .cbadge { display: inline-block; }
|
|
115
|
+
/* The pill as the room button's round icon (on a phone, beside a room button kept an icon, or where its word would reach
|
|
116
|
+
the game's clock or title): its word goes, and the count rides its corner. */
|
|
117
|
+
.cpill.icon-only, .cpill.icon-only:hover, .cpill.icon-only:focus-visible, .cpill.icon-only[aria-expanded="true"] { position: relative; width: 34px; padding: 0; justify-content: center; }
|
|
45
118
|
.cpill.icon-only span.cword { display: none; }
|
|
119
|
+
.cpill.icon-only .cbadge { position: absolute; top: -5px; right: -5px; min-width: 16px; box-shadow: 0 0 0 2px rgba(6,9,16,.85); }
|
|
46
120
|
.csheet { display: flex; flex-direction: column; gap: 8px; width: min(340px, calc(100vw - 16px)); }
|
|
47
121
|
.chead { display: flex; align-items: center; gap: 8px; margin: 0 2px; }
|
|
48
122
|
.chead b { font: 800 15px/1.2 ui-sans-serif, system-ui, sans-serif; letter-spacing: -.01em; }
|
|
@@ -88,24 +162,25 @@ export const CHAT_CSS = `
|
|
|
88
162
|
.creport { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 6px 0 2px; }
|
|
89
163
|
.creport p { grid-column: 1 / -1; margin: 0; color: #c3cad9; font: 600 12.5px/1.35 ui-sans-serif, system-ui, sans-serif; }
|
|
90
164
|
.creport button { min-height: 32px; padding: 0 8px; border-radius: 9px; border: 1px solid rgba(255,255,255,.16); background: transparent; color: inherit; font: 700 12px/1.2 ui-sans-serif, system-ui, sans-serif; cursor: pointer; text-align: left; }
|
|
91
|
-
|
|
92
|
-
.ctick
|
|
93
|
-
.ctick.at-bottom-
|
|
94
|
-
.ctick.at-bottom-
|
|
95
|
-
.ctick.at-
|
|
96
|
-
.ctick.at-top-
|
|
97
|
-
.ctick.at-top-
|
|
165
|
+
/* The strip of new lines (game.json screen.chat: its place, moved in by --chat-x / --chat-y). */
|
|
166
|
+
.ctick { --chat-x: 0px; --chat-y: 0px; position: fixed; z-index: 5; display: flex; flex-direction: column; gap: 4px; max-width: min(320px, calc(100vw - 24px)); pointer-events: none; }
|
|
167
|
+
.ctick.at-bottom-left { left: calc(max(10px, env(safe-area-inset-left)) + var(--chat-x)); bottom: calc(max(10px, env(safe-area-inset-bottom)) + 36px + var(--chat-y)); align-items: flex-start; }
|
|
168
|
+
.ctick.at-bottom-right { right: calc(max(10px, env(safe-area-inset-right)) + var(--chat-x)); bottom: calc(max(10px, env(safe-area-inset-bottom)) + 36px + var(--chat-y)); align-items: flex-end; }
|
|
169
|
+
.ctick.at-bottom-center { left: calc(50% + var(--chat-x)); transform: translateX(-50%); bottom: calc(max(10px, env(safe-area-inset-bottom)) + 36px + var(--chat-y)); align-items: center; }
|
|
170
|
+
.ctick.at-top-left { left: calc(max(10px, env(safe-area-inset-left)) + var(--chat-x)); top: calc(max(10px, env(safe-area-inset-top)) + 48px + var(--chat-y)); align-items: flex-start; flex-direction: column-reverse; }
|
|
171
|
+
.ctick.at-top-right { right: calc(max(10px, env(safe-area-inset-right)) + var(--chat-x)); top: calc(max(10px, env(safe-area-inset-top)) + 48px + var(--chat-y)); align-items: flex-end; flex-direction: column-reverse; }
|
|
172
|
+
.ctick.at-top-center { left: calc(50% + var(--chat-x)); transform: translateX(-50%); top: calc(max(10px, env(safe-area-inset-top)) + 48px + var(--chat-y)); align-items: center; flex-direction: column-reverse; }
|
|
98
173
|
.ctick div { padding: 5px 10px; border-radius: 12px; background: rgba(6,9,16,.66); color: #eef1f8; font: 600 13px/1.3 ui-sans-serif, system-ui, -apple-system, sans-serif; overflow-wrap: anywhere; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); animation: ctin .2s ease-out; transition: opacity .5s; }
|
|
99
174
|
.ctick div b { font-weight: 800; margin-right: 6px; }
|
|
100
175
|
.ctick div.cout { opacity: 0; }
|
|
101
176
|
@keyframes ctin { from { opacity: 0; transform: translateY(6px); } }
|
|
102
177
|
.cfloat { position: fixed; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; z-index: 4; }
|
|
103
|
-
.ctv { position: fixed; z-index: 6; box-sizing: border-box; width: min(26rem, 30vw); max-height: 42vh; display: flex; flex-direction: column; justify-content: flex-end; gap: .3em; padding: .55em .8em; border-radius: 16px; background: rgba(8,12,22,.7); border: 1px solid rgba(255,255,255,.12); color: #eef1f8; font: 600 clamp(14px, 1.6vmin, 24px)/1.35 ui-sans-serif, system-ui, -apple-system, sans-serif; pointer-events: none; overflow: hidden; }
|
|
178
|
+
.ctv { --chat-x: 0px; --chat-y: 0px; position: fixed; z-index: 6; box-sizing: border-box; width: min(26rem, 30vw); max-height: 42vh; display: flex; flex-direction: column; justify-content: flex-end; gap: .3em; padding: .55em .8em; border-radius: 16px; background: rgba(8,12,22,.7); border: 1px solid rgba(255,255,255,.12); color: #eef1f8; font: 600 clamp(14px, 1.6vmin, 24px)/1.35 ui-sans-serif, system-ui, -apple-system, sans-serif; pointer-events: none; overflow: hidden; }
|
|
104
179
|
.ctv:empty { display: none; }
|
|
105
|
-
.ctv.at-bottom-left { left: max(16px, env(safe-area-inset-left)); bottom: max(16px, env(safe-area-inset-bottom)); }
|
|
106
|
-
.ctv.at-bottom-right { right: max(16px, env(safe-area-inset-right)); bottom: max(16px, env(safe-area-inset-bottom)); }
|
|
107
|
-
.ctv.at-top-left { left: max(16px, env(safe-area-inset-left)); top: max(16px, env(safe-area-inset-top)); justify-content: flex-start; }
|
|
108
|
-
.ctv.at-top-right { right: max(16px, env(safe-area-inset-right)); top: max(16px, env(safe-area-inset-top)); justify-content: flex-start; }
|
|
180
|
+
.ctv.at-bottom-left { left: calc(max(16px, env(safe-area-inset-left)) + var(--chat-x)); bottom: calc(max(16px, env(safe-area-inset-bottom)) + var(--chat-y)); }
|
|
181
|
+
.ctv.at-bottom-right { right: calc(max(16px, env(safe-area-inset-right)) + var(--chat-x)); bottom: calc(max(16px, env(safe-area-inset-bottom)) + var(--chat-y)); }
|
|
182
|
+
.ctv.at-top-left { left: calc(max(16px, env(safe-area-inset-left)) + var(--chat-x)); top: calc(max(16px, env(safe-area-inset-top)) + var(--chat-y)); justify-content: flex-start; }
|
|
183
|
+
.ctv.at-top-right { right: calc(max(16px, env(safe-area-inset-right)) + var(--chat-x)); top: calc(max(16px, env(safe-area-inset-top)) + var(--chat-y)); justify-content: flex-start; }
|
|
109
184
|
.ctv div { overflow-wrap: anywhere; transition: opacity 1s; }
|
|
110
185
|
.ctv div.cout { opacity: 0; }
|
|
111
186
|
.ctv b { font-weight: 800; margin-right: .4em; }
|
|
@@ -221,21 +296,41 @@ export const CHAT_JS = String.raw`(function () {
|
|
|
221
296
|
if (parts.length) raf = requestAnimationFrame(paint);
|
|
222
297
|
}
|
|
223
298
|
|
|
224
|
-
/* ------------------------------------------------------------
|
|
299
|
+
/* ------------------------------------------------------------ where chat sits on this screen (game.json screen.chat) */
|
|
300
|
+
// This screen as the room button knows it (desk, phone held upright, phone turned sideways), else by its size.
|
|
301
|
+
function device() {
|
|
302
|
+
if (surface === 'tv') return 'tv';
|
|
303
|
+
var s = window.__shell && window.__shell.share;
|
|
304
|
+
if (s && s.device) return s.device;
|
|
305
|
+
var w = window.innerWidth || 0; var h = window.innerHeight || 0;
|
|
306
|
+
return w && h && Math.min(w, h) <= 540 ? (w > h ? 'sideways' : 'phone') : 'desk';
|
|
307
|
+
}
|
|
308
|
+
function placeHere() { var p = cfg.places || {}; return p[device()] || p.desk || { at: null, x: 0, y: 0, lines: 'strip' }; }
|
|
309
|
+
|
|
310
|
+
/* ------------------------------------------------------------ the strip: new lines, for a moment, where the chip sits */
|
|
225
311
|
var tickEl = null;
|
|
226
312
|
function tickPlace() {
|
|
227
|
-
|
|
313
|
+
var p = placeHere();
|
|
314
|
+
if (p.at) return p.at;
|
|
228
315
|
var s = window.__shell && window.__shell.share;
|
|
229
316
|
return s && s.at === 'bottom-left' ? 'bottom-right' : 'bottom-left';
|
|
230
317
|
}
|
|
231
318
|
function tick(m) {
|
|
232
|
-
if (surface === 'tv' ||
|
|
319
|
+
if (surface === 'tv' || !st.show || st.open) return;
|
|
320
|
+
// "sheet-only": a busy HUD keeps new lines in the sheet; the pill's count says they are there.
|
|
321
|
+
var p = placeHere();
|
|
322
|
+
if (p.lines === 'sheet-only') return;
|
|
233
323
|
if (!tickEl) {
|
|
234
324
|
var host = (surface === 'watch' ? document.querySelector('.stage') : null) || document.body;
|
|
235
325
|
if (!host) return;
|
|
236
326
|
tickEl = el('div', 'ctick'); tickEl.setAttribute('aria-hidden', 'true'); host.appendChild(tickEl); if (surface === 'watch') tickEl.style.position = 'absolute';
|
|
237
327
|
}
|
|
238
|
-
|
|
328
|
+
var at = tickPlace();
|
|
329
|
+
tickEl.className = 'ctick at-' + at;
|
|
330
|
+
// Moved in by the game's x / y, and past the room button when the strip shares its place.
|
|
331
|
+
var s = window.__shell && window.__shell.share; var same = surface === 'play' && s && s.at === at;
|
|
332
|
+
tickEl.style.setProperty('--chat-x', ((p.x || 0) + (same ? s.x || 0 : 0)) + 'px');
|
|
333
|
+
tickEl.style.setProperty('--chat-y', ((p.y || 0) + (same ? s.y || 0 : 0)) + 'px');
|
|
239
334
|
var row = el('div');
|
|
240
335
|
if (m.name) { var b = el('b', '', m.name); b.style.color = colourOf(m); row.appendChild(b); }
|
|
241
336
|
row.appendChild(document.createTextNode(m.glyph && m.react ? m.glyph : (m.text || '')));
|
|
@@ -247,13 +342,16 @@ export const CHAT_JS = String.raw`(function () {
|
|
|
247
342
|
/* ------------------------------------------------------------ the TV's corner */
|
|
248
343
|
var tvEl = null;
|
|
249
344
|
function tvLine(m) {
|
|
250
|
-
|
|
345
|
+
var p = placeHere();
|
|
346
|
+
if (!st.show || p.lines === 'sheet-only') return;
|
|
251
347
|
if (!tvEl) {
|
|
252
348
|
if (!document.body) return;
|
|
253
349
|
tvEl = el('div', 'ctv');
|
|
254
350
|
var join = document.querySelector('[data-join]');
|
|
255
351
|
var at = join && /join-bottom-left/.test(join.className) ? 'bottom-right' : join && /join-top-left|join-top-right/.test(join.className) ? 'bottom-left' : 'bottom-left';
|
|
256
|
-
tvEl.className = 'ctv at-' + (
|
|
352
|
+
tvEl.className = 'ctv at-' + (p.at || at);
|
|
353
|
+
tvEl.style.setProperty('--chat-x', (p.x || 0) + 'px');
|
|
354
|
+
tvEl.style.setProperty('--chat-y', (p.y || 0) + 'px');
|
|
257
355
|
document.body.appendChild(tvEl);
|
|
258
356
|
}
|
|
259
357
|
var row = el('div', m.t === 'react' ? 'creact' : '');
|
|
@@ -277,6 +375,7 @@ export const CHAT_JS = String.raw`(function () {
|
|
|
277
375
|
pill = el('button', 'pill cpill'); pill.type = 'button'; pill.setAttribute('data-chat-toggle', ''); pill.setAttribute('aria-expanded', 'false'); pill.setAttribute('aria-controls', 'chat-sheet'); pill.setAttribute('aria-label', 'Room chat');
|
|
278
376
|
pill.innerHTML = ICON; pill.appendChild(el('span', 'cword', 'Chat')); badge = el('b', 'cbadge'); badge.hidden = true; pill.appendChild(badge);
|
|
279
377
|
band.appendChild(pill);
|
|
378
|
+
watchBand(band);
|
|
280
379
|
sheet = el('div', 'sheet csheet'); sheet.id = 'chat-sheet'; sheet.setAttribute('role', 'dialog'); sheet.setAttribute('aria-label', 'Room chat'); sheet.hidden = true;
|
|
281
380
|
ui.appendChild(sheet);
|
|
282
381
|
// One sheet at a time: the room's and the server's close this one, and this one closes theirs.
|
|
@@ -318,6 +417,52 @@ export const CHAT_JS = String.raw`(function () {
|
|
|
318
417
|
renderFeed();
|
|
319
418
|
paintRules();
|
|
320
419
|
}
|
|
420
|
+
/*
|
|
421
|
+
* The pill's word, as the room button's: on a phone (either way up), or beside a room button that game.json keeps an
|
|
422
|
+
* icon ("label": false), the pill is the round icon from the start. Elsewhere its word shows unless, with it, the band
|
|
423
|
+
* would leave the screen or reach from its corner into the middle third of the screen's width (where games keep a
|
|
424
|
+
* clock or a title); once it has, it stays the icon until the screen changes size, so it never flickers as the room
|
|
425
|
+
* button's own label comes and goes.
|
|
426
|
+
*/
|
|
427
|
+
var narrow = false;
|
|
428
|
+
function shareHere() { var s = window.__shell && window.__shell.share; return s || {}; }
|
|
429
|
+
function wordCollides() {
|
|
430
|
+
var band = pill && pill.parentNode;
|
|
431
|
+
var w = window.innerWidth || 0;
|
|
432
|
+
if (!band || !band.getBoundingClientRect || !w) return false;
|
|
433
|
+
var had = pill.classList.contains('icon-only');
|
|
434
|
+
if (had) pill.classList.remove('icon-only');
|
|
435
|
+
var r = band.getBoundingClientRect();
|
|
436
|
+
if (had) pill.classList.add('icon-only');
|
|
437
|
+
if (!r.width) return false;
|
|
438
|
+
if (r.left < 4 || r.right > w - 4) return true;
|
|
439
|
+
var at = shareHere().at || 'top-right';
|
|
440
|
+
if (/center/.test(at)) return false;
|
|
441
|
+
return /right/.test(at) ? r.left < w * 2 / 3 : r.right > w / 3;
|
|
442
|
+
}
|
|
443
|
+
function fitPill() {
|
|
444
|
+
if (!pill || surface !== 'play') return;
|
|
445
|
+
var s = shareHere();
|
|
446
|
+
if (!narrow && s.device !== 'phone' && s.device !== 'sideways' && s.label !== false) narrow = wordCollides();
|
|
447
|
+
var icon = s.device === 'phone' || s.device === 'sideways' || s.label === false || narrow;
|
|
448
|
+
if (icon) { pill.classList.add('icon-only'); pill.title = 'Room chat'; } else { pill.classList.remove('icon-only'); pill.title = ''; }
|
|
449
|
+
st.compact = icon;
|
|
450
|
+
}
|
|
451
|
+
var fitQueued = false;
|
|
452
|
+
function fitSoon() {
|
|
453
|
+
if (fitQueued) return;
|
|
454
|
+
fitQueued = true;
|
|
455
|
+
var go = function () { fitQueued = false; fitPill(); };
|
|
456
|
+
if (window.requestAnimationFrame) window.requestAnimationFrame(go); else setTimeout(go, 16);
|
|
457
|
+
}
|
|
458
|
+
function watchBand(band) {
|
|
459
|
+
fitPill();
|
|
460
|
+
// The band changes as the room code arrives, the server's pill shows and the labels fade to dots: look again.
|
|
461
|
+
try { if (window.ResizeObserver) new ResizeObserver(fitSoon).observe(band); } catch (e) {}
|
|
462
|
+
// A new size (a turned phone, a resized window): the room button moves first, then the word is measured afresh.
|
|
463
|
+
window.addEventListener('resize', function () { setTimeout(function () { narrow = false; fitPill(); }, 60); });
|
|
464
|
+
}
|
|
465
|
+
st.fit = fitPill;
|
|
321
466
|
function openChat(on, quiet) {
|
|
322
467
|
if (!sheet) return;
|
|
323
468
|
st.open = Boolean(on);
|
package/worker/chat.mjs
CHANGED
|
@@ -209,7 +209,10 @@ export function chatProblems(raw) {
|
|
|
209
209
|
const out = [];
|
|
210
210
|
if (raw.mode !== undefined && !CHAT_MODES.includes(raw.mode)) out.push('chat.mode is off, emoji, lines or text');
|
|
211
211
|
for (const k of ['who', 'react']) if (raw[k] !== undefined && !CHAT_WHO.includes(raw[k])) out.push(`chat.${k} is anyone, signed-in or members`);
|
|
212
|
-
|
|
212
|
+
// Where chat sits on the screen is the page's (game.json "screen": { "chat" }), never the room's rules.
|
|
213
|
+
for (const k of ['corner', 'place', 'at']) if (raw[k] !== undefined && (k !== 'at' || typeof raw.at === 'string')) out.push(`chat.${k}: where chat sits on the screen goes in "screen": { "chat": { "at": … } }`);
|
|
214
|
+
if (raw.lines === 'strip' || raw.lines === 'sheet-only') out.push(`chat.lines is the quick lines ({ "id": "text" }); "${raw.lines}" goes in "screen": { "chat": { "lines": "${raw.lines}" } }`);
|
|
215
|
+
else if (raw.lines !== undefined && raw.lines !== null) {
|
|
213
216
|
const pairs = Array.isArray(raw.lines) ? raw.lines.map((x) => [x?.id, x?.text]) : typeof raw.lines === 'object' ? Object.entries(raw.lines) : null;
|
|
214
217
|
if (!pairs) out.push('chat.lines is { "id": "text" }');
|
|
215
218
|
else {
|
package/worker/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The @homie-rocks/studio version this Worker code is (package.json's; a test keeps them equal). Cache-busts /_homie/site.js. */
|
|
2
|
-
export const STUDIO_VERSION_TAG = '0.24.
|
|
2
|
+
export const STUDIO_VERSION_TAG = '0.24.5';
|