coworking-agents 0.2.2 โ 0.3.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/README.md +45 -28
- package/docs/achievements.png +0 -0
- package/docs/building.png +0 -0
- package/docs/camera.png +0 -0
- package/docs/christmas.png +0 -0
- package/docs/customise.png +0 -0
- package/docs/game-room.png +0 -0
- package/docs/halloween.png +0 -0
- package/docs/house-wall.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/mobile.png +0 -0
- package/docs/office-day.png +0 -0
- package/docs/office-night.png +0 -0
- package/docs/panel.png +0 -0
- package/docs/paper.png +0 -0
- package/docs/servers.png +0 -0
- package/docs/talk.png +0 -0
- package/docs/top-bar.png +0 -0
- package/docs/trophy-room.png +0 -0
- package/docs/usage.png +0 -0
- package/docs/work-site.png +0 -0
- package/package.json +3 -2
- package/public/art.js +1 -1
- package/public/index.html +4 -0
- package/public/js/achievements.js +14 -3
- package/public/js/collection.js +21 -4
- package/public/js/fun2.js +224 -0
- package/public/js/i18n.js +56 -8
- package/public/js/keys.js +1 -0
- package/public/js/life.js +6 -3
- package/public/js/main.js +1 -1
- package/public/js/overlay.js +5 -2
- package/public/js/panel.js +4 -0
- package/public/js/personal.js +23 -9
- package/public/js/player.js +4 -3
- package/public/js/report.js +4 -1
- package/public/js/rooms.js +9 -5
- package/public/js/scene.js +1 -0
- package/public/js/talk.js +1 -1
- package/public/js/topbar.js +3 -1
- package/public/js/views.js +118 -0
- package/public/js/work.js +93 -0
- package/public/style.css +28 -0
- package/src/demo.js +3 -1
- package/src/focus.js +14 -1
- package/src/server.js +12 -1
- package/src/usage.js +6 -3
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="
|
|
2
|
+
<img src="docs/logo.png" width="128" alt="coworking-agents logo">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">coworking-agents</h1>
|
|
@@ -10,28 +10,28 @@
|
|
|
10
10
|
|
|
11
11
|
**A live pixel-art office for your AI coding agents.** Every open session (Claude Code, Codex, โฆ) becomes a person at a desk. At a glance you see who is working, who needs you (and what they are asking), whose turn it is, and which sessions are editing the same checkout, even across different AIs.
|
|
12
12
|
|
|
13
|
-

|
|
14
14
|
|
|
15
|
-

|
|
16
16
|
|
|
17
17
|
## Tour
|
|
18
18
|
|
|
19
19
|
| | |
|
|
20
20
|
|---|---|
|
|
21
|
-
|  | **Top bar.** One HUD with the counters and the usage meters; the counter below is where agents waiting on you sit (4 per office, **+** slides to the next). |
|
|
22
|
+
|  | **Talk.** Click a waiting agent: it says what it wants at its desk. Reply right there (typed into its Terminal/iTerm tab for Claude Code and Codex CLI) or jump to it. |
|
|
23
|
+
|  | **Camera.** Click an agent and the camera flies in to its desk with a spotlight; the side panel shows everything about the session. |
|
|
24
|
+
|  | **Agent panel.** Now, last request and reply, last-hour timeline, today's numbers, tokens, context, files, subagents, certificates. |
|
|
25
|
+
|  | **Usage report.** Tokens per day for 14 days, all-time totals per AI and per model, rate-limit windows with reset times. |
|
|
26
|
+
|  | **Arrivals and departures.** A new session gets its room boarded up and built (planks, dust, caution tape); a closed one walks out and its desk is demolished. |
|
|
27
|
+
|  | **Achievements.** 86 of them, five tiers each, coins for every tier, the agent of the month on top. |
|
|
28
|
+
|  | **The Office Times.** Your week in headlines, written from measured history. |
|
|
29
|
+
|  | **Trophy room.** Glass cabinets with lit shelves, legend trophies in a glass case under spotlights, a crown under a dome for Legend of Legends. Click a trophy for its card. |
|
|
30
|
+
|  | **Holidays.** Lights, snow and Santa hats at Christmas; pumpkins, bats and witch hats at Halloween; flags at Festa Junina. |
|
|
31
|
+
|  | **Building view.** Many agents? Floors, each a live thumbnail. |
|
|
32
|
+
|  | **House wall.** Podium of the most used skills, installed skills, MCP servers and plugins. |
|
|
33
|
+
|  | **Day and night.** Lighting follows your clock; a wall switch turns the ceiling lights on or off; the rest room stays dark. |
|
|
34
|
+
|  | **Phone.** Works on small screens too. |
|
|
35
35
|
|
|
36
36
|
## Why
|
|
37
37
|
|
|
@@ -85,7 +85,7 @@ Running `npx coworking-agents` a second time reuses the office that is already r
|
|
|
85
85
|
- **Nap corner:** sessions idle for a long time (20+ min) lie down on bean bags.
|
|
86
86
|
- **Floors and Building view.** With many rooms the office gains floors, each with its own shared wing. A floor picker appears at the top, and **Building view** shows every floor as a live thumbnail with counts of who is working and who needs you; click one to go there.
|
|
87
87
|
|
|
88
|
-

|
|
89
89
|
|
|
90
90
|
## States
|
|
91
91
|
|
|
@@ -106,7 +106,7 @@ Running `npx coworking-agents` a second time reuses the office that is already r
|
|
|
106
106
|
|
|
107
107
|
Click a person for the side panel: repository, branch, worktree, model, context size, turns, recent actions, subagents, most-used tools and certificates.
|
|
108
108
|
|
|
109
|
-

|
|
110
110
|
|
|
111
111
|
## Help toasts
|
|
112
112
|
|
|
@@ -126,6 +126,11 @@ Agents away from their desks (kitchen, meeting room, nap corner) carry a name ta
|
|
|
126
126
|
- **Git on the door:** each room sign shows uncommitted files (โ), commits to push (โ) and to pull (โ), read with `git status` in the background (no fetch).
|
|
127
127
|
- **History and daily report:** the last 24 hours of arrivals, departures, requests and finished work; a daily report with active time, tools and files that copies as Markdown for a standup; your reply times.
|
|
128
128
|
- **Estimated cost:** what the tokens would cost at official API list prices (table in `src/prices.js`, dated; override in `~/.config/coworking-agents/prices.json`), priced per reply with Claude's cache-write split. Unknown models are never guessed. On a subscription this is not what you pay.
|
|
129
|
+
- **The queue** (`Q`): everyone waiting on you, longest wait first, with a button to answer each one.
|
|
130
|
+
- **Quick replies:** one click fills the reply box with "Go ahead", "Run the tests", "Commit and push" and more; you still choose to send.
|
|
131
|
+
- **Welcome back:** after 15+ minutes away, a card tells you what finished, who arrived or left and who is waiting.
|
|
132
|
+
- **Daily goal and cost alert:** set a goal of agent hours and a limit for the estimated cost of the day (daily report tab); you get told when either is reached.
|
|
133
|
+
- **New session:** โฐ โ New session opens a Terminal tab in one of your folders and starts Claude Code or Codex (macOS; only an existing folder and those two commands are accepted).
|
|
129
134
|
- **Focus mode:** sounds and system notifications pause for 25 or 50 minutes.
|
|
130
135
|
- **Replay:** scrub or play back the last hour.
|
|
131
136
|
|
|
@@ -135,28 +140,39 @@ You're in the office too, with a **YOU** tag. **WASD** or the arrow keys walk (h
|
|
|
135
140
|
|
|
136
141
|
## The game room and the trophy room
|
|
137
142
|
|
|
138
|
-

|
|
139
144
|
|
|
140
145
|
Under the team rooms there's a game room with four doors: arcades, foosball, a pool table, an aquarium, a water cooler and bean bags, where idle agents hang out. Its props open the reports: the **newspaper rack** (the weekly paper), the **vending machine** (the shop), the **easel** (customise), the **guestbook** (history) and the **cork board** (today's date, files edited today, the daily report).
|
|
141
146
|
|
|
142
147
|
A marble portal leads to the **trophy room**, its own screen: legends with wings and a flame in lit niches between marble columns, a stained-glass arch over the crown of **Legend of Legends**, every other trophy under its own glass dome or in the side cabinets, locked ones as silhouettes. Each tier has its own award (bronze cup, silver star cup, gold ruby cup, diamond crystal, winged legend). Click one for its card.
|
|
143
148
|
|
|
144
|
-

|
|
150
|
+
|
|
151
|
+
## The elevator, the rooftop and the server room
|
|
152
|
+
|
|
153
|
+
Click the elevator (or press Enter next to it) to ride between floors, up to the **rooftop**, where agents whose turn ended relax on lounge chairs under the real sky with the city lights below, and down to the **server room**, where every open session is a rack whose LEDs blink with its real state and whose bar shows its output today. Click someone on the rooftop or a rack to open that session.
|
|
154
|
+
|
|
155
|
+

|
|
145
156
|
|
|
146
157
|
## Achievements, coins and the shop
|
|
147
158
|
|
|
148
|
-
- **
|
|
159
|
+
- **86 achievements**, each with five tiers from bronze to legend, measured from your history (hours, actions, streaks, nights, Friday evenings, weeks and months with work, tokens, cache, models, cost), the live office (parallel sessions, subagents, giant contexts, commits and pushes, repos and branches, quick answers, clean desks) and what you do in the office (walking, talking, the radio, the shop, customising). A few are secret.
|
|
149
160
|
- **Legend of Legends** when every achievement is at legend.
|
|
150
|
-
- **Coins:** each tier pays 10 to 50 coins (the super achievement 1000). Spend them in the **shop** on decor that appears in the office: corridor plants, a rug, a COFFEE neon, floor lamps, a disco ball, a golden statue of the agent of the month, a piano, a fountain. โฐ โ **Decorate** lets you drag what you bought anywhere in the office (saved in your progress file).
|
|
161
|
+
- **Coins:** each tier pays 10 to 50 coins (the super achievement 1000). Spend them in the **shop** on decor that appears in the office: corridor plants, a rug, a COFFEE neon, floor lamps, a disco ball, a golden statue of the agent of the month, a piano, a fountain, a turtle terrarium, a bunny, a bigger aquarium, and clothes for your character. โฐ โ **Decorate** lets you drag what you bought anywhere in the office (saved in your progress file).
|
|
151
162
|
- **Agent of the month:** a framed portrait on the wall of the conversation with the most active time this month; the top 5 are in the achievements tab.
|
|
152
163
|
- **The weekly paper**, *The Office Times*, tells your week in headlines, all from measured data.
|
|
164
|
+
- **Weekly missions:** three new challenges every week (hours, actions, days worked, commits, pushes, quick replies, pets, fast answers, long tasks), 100 coins each.
|
|
165
|
+
- **Your outfit:** caps, headphones, shades, a party hat, shirts and a crown from the shop; your character wears them (pick in Customise).
|
|
166
|
+
- **Bug Catcher:** click an arcade in the game room: catch falling bugs with your laptop, golden ones and combos score more, three misses end the game; the best score is kept.
|
|
167
|
+
- **Surprises:** now and then a cake, a pigeon at the window or a golden ticket shows up; click it for coins.
|
|
168
|
+
- **Share:** the paper tab makes a weekly card (hours, actions, trophies, a slice of the office) and a visitor photo of the office without names, branches or the office name. It also shows this week's AI scoreboard (hours, actions, tokens per AI).
|
|
153
169
|
- **Progress file:** achievements, coins, purchases, avatars and settings live in `~/.config/coworking-agents/progress.json` (mode `0600`). No account needed: the app runs on your machine, so the progress is yours.
|
|
154
170
|
|
|
155
171
|
## Make it yours
|
|
156
172
|
|
|
157
|
-

|
|
158
174
|
|
|
159
|
-
โฐ โ **Customise** has 13 options: the office name (on a plaque by the elevator and in the top bar), your own look, floor (wood, dark wood, carpet, concrete, tiles, checker, marble, grass), walls (10 colours), room carpets (5 palettes), desks (5), the window view (city, beach, mountains, forest, space), the sky (automatic, always day, sunset, always night), lighting (natural, warm, cool, neon), the mascot (cat, dog, robot, fox, penguin, bunny), plants (normal or jungle), the radio station and the accent colour. The die next to an agent's face in its panel gives that agent a new look.
|
|
175
|
+
โฐ โ **Customise** shows a live preview of the office on top and has 13 options: the office name (on a plaque by the elevator and in the top bar), your own look, floor (wood, dark wood, carpet, concrete, tiles, checker, marble, grass), walls (10 colours), room carpets (5 palettes), desks (5), the window view (city, beach, mountains, forest, space), the sky (automatic, always day, sunset, always night), lighting (natural, warm, cool, neon), the mascot (cat, dog, robot, fox, penguin, bunny), plants (normal or jungle), the radio station and the accent colour. The die next to an agent's face in its panel gives that agent a new look.
|
|
160
176
|
|
|
161
177
|
## A living office
|
|
162
178
|
|
|
@@ -166,10 +182,11 @@ A marble portal leads to the **trophy room**, its own screen: legends with wings
|
|
|
166
182
|
- **Holidays:** Christmas, New Year, Halloween, Festa Junina, Carnaval and the office's birthday decorate the room and put hats on everyone (`E` turns decor off).
|
|
167
183
|
- **Pets:** the mascot visits whoever has waited on you the longest and likes being petted; a dog joins at 8 achievements at gold, a parrot at 16.
|
|
168
184
|
- **Certificates** for the skills and MCP servers each session used hang on its partition.
|
|
169
|
-
- **Office photo:** โฐ โ Office photo saves the scene as a PNG
|
|
185
|
+
- **Office photo:** โฐ โ Office photo saves the scene as a PNG, and the last four hang as framed photos on the game room wall.
|
|
186
|
+
- **Weather indoors:** umbrellas by the elevator when it rains outside (real weather on), coats on the rack in winter. And something happens if you click the `</>` neon three times.
|
|
170
187
|
- **Languages:** English and Brazilian Portuguese.
|
|
171
188
|
|
|
172
|
-
<p align="center"><img src="
|
|
189
|
+
<p align="center"><img src="docs/mobile.png" alt="The office on a phone" width="300"></p>
|
|
173
190
|
|
|
174
191
|
## Shortcuts
|
|
175
192
|
|
|
@@ -178,7 +195,7 @@ A marble portal leads to the **trophy room**, its own screen: legends with wings
|
|
|
178
195
|
| `1`โ`9` | open the n-th agent |
|
|
179
196
|
| `N` | next agent that needs you (opens its talk modal) |
|
|
180
197
|
| `/` | quick search by name, title, repository or state |
|
|
181
|
-
| `R` / `T` / `G` / `C` | usage report / daily report (today) / history / achievements |
|
|
198
|
+
| `Q` / `R` / `T` / `G` / `C` | queue / usage report / daily report (today) / history / achievements |
|
|
182
199
|
| WASD or arrows, Shift, Enter | walk, run, interact with what's near you |
|
|
183
200
|
| `F` | focus mode (25 min, again 50, again off) |
|
|
184
201
|
| `H` | replay the last hour |
|
|
Binary file
|
|
Binary file
|
package/docs/camera.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/logo.png
ADDED
|
Binary file
|
package/docs/mobile.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/panel.png
ADDED
|
Binary file
|
package/docs/paper.png
ADDED
|
Binary file
|
package/docs/servers.png
ADDED
|
Binary file
|
package/docs/talk.png
ADDED
|
Binary file
|
package/docs/top-bar.png
ADDED
|
Binary file
|
|
Binary file
|
package/docs/usage.png
ADDED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coworking-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A pixel-art coworking office for your AI coding agents (Claude Code, Codex, โฆ): see who's working, who needs you, and who's editing the same checkout.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"coworking-agents": "bin/coworking-agents.js"
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"commands",
|
|
16
16
|
"skills",
|
|
17
17
|
"AGENTS.md",
|
|
18
|
-
"ROADMAP.md"
|
|
18
|
+
"ROADMAP.md",
|
|
19
|
+
"docs/*.png"
|
|
19
20
|
],
|
|
20
21
|
"engines": {
|
|
21
22
|
"node": ">=18"
|
package/public/art.js
CHANGED
|
@@ -759,7 +759,7 @@ function aiIconEl(id, color) {
|
|
|
759
759
|
|
|
760
760
|
window.Art = {
|
|
761
761
|
aiIcon, aiIconEl, portrait, drawArcade, drawDiagramBoard, drawNeon, drawPoster, drawServerRack, drawDuck, drawSideMonitor, drawStickies, drawBeanSack, drawPizza, drawCupStack,
|
|
762
|
-
PAL, SKIN, HAIR, SHIRT, SCREEN_GLOW, hash, shade, look, setLookSeeds, setHat, setCtx, r, sprite, blit,
|
|
762
|
+
PAL, SKIN, HAIR, SHIRT, SCREEN_GLOW, hash, shade, look, setLookSeeds, setHat, drawHat, setCtx, r, sprite, blit,
|
|
763
763
|
drawSeatedBack, drawFront, drawStanding, drawSleeping, drawChairBack, drawChairBase, drawCat, bubble, drawScreen,
|
|
764
764
|
drawPlant, drawCertificate, drawLamp, drawWhiteboard, drawCork, drawClock, skyFor, drawWindow, drawSofa, drawCoffeeMachine,
|
|
765
765
|
drawCooler, drawRug, drawFloor, applyLight, LEGS_SIT,
|
package/public/index.html
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
<button id="btn-replay" class="icon-btn"><svg viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><path fill="currentColor" d="M6 1h4v1h2v1h1v2h1v6h-1v2h-1v1h-2v1H6v-1H4v-1H3v-2H2V9h2v2h1v1h1v1h4v-1h1v-1h1V5h-1V4h-1V3H6v1H5v1H4v1h2v2H1V3h2v1h1V3h1V2h1zM7 5h2v3h2v2H7z"/></svg><span class="sr"></span></button>
|
|
31
31
|
<button id="btn-search" class="icon-btn"><svg viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><path fill="currentColor" d="M5 1h4v1h1v1h1v4h-1v1H9v1H5V8H4V7H3V3h1V2h1zm0 2v4h4V3zM10 9h1v1h1v1h1v1h1v1h-1v1h-1v-1h-1v-1h-1v-1H9v-1h1z"/></svg><span class="sr"></span></button>
|
|
32
32
|
<button id="btn-photo" class="icon-btn"><svg viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><path fill="currentColor" d="M5 2h6v2h3v10H2V4h3zm3 4H7v1H6v3h1v1h2v-1h1V7H9V6z"/></svg><span class="sr"></span></button>
|
|
33
|
+
<button id="btn-launch" class="icon-btn"><svg viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><path fill="currentColor" d="M1 2h14v12H1zm2 2v8h10V4zm1 1h2v1h1v1H6v1H4V7h1V6H4zm4 3h3v1H8z"/></svg><span class="sr"></span></button>
|
|
33
34
|
<button id="btn-deco" class="icon-btn"><svg viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><path fill="currentColor" d="M7 1h2v3h2l-3 3-3-3h2zM1 7h3V5l3 3-3 3V9H1zm11-2 3 3-3 3V9H9V7h3zM5 12l3-3 3 3H9v3H7v-3z"/></svg><span class="sr"></span></button>
|
|
34
35
|
<button id="btn-office" class="icon-btn"><svg viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><path fill="currentColor" d="M7 1h2v2h2v1h2v2h-1v2h1v2h-2v2h-1v1H9v2H7v-2H5v-1H4v-2H3V8h1V6H3V4h2V3h2zm0 5v1H6v2h1v1h2V9h1V7H9V6z"/></svg><span class="sr"></span></button>
|
|
35
36
|
<button id="btn-lang" class="icon-btn lang"></button>
|
|
@@ -75,6 +76,9 @@
|
|
|
75
76
|
<script src="js/personal.js"></script>
|
|
76
77
|
<script src="js/rooms.js"></script>
|
|
77
78
|
<script src="js/player.js"></script>
|
|
79
|
+
<script src="js/work.js"></script>
|
|
80
|
+
<script src="js/views.js"></script>
|
|
81
|
+
<script src="js/fun2.js"></script>
|
|
78
82
|
<script src="js/main.js"></script>
|
|
79
83
|
</body>
|
|
80
84
|
</html>
|
|
@@ -42,7 +42,7 @@ function achFacts() {
|
|
|
42
42
|
skillsUsed: Math.max(0, ...cred.map(c => (c.topSkills || []).length)),
|
|
43
43
|
commits: achCount('commits'), pushes: achCount('pushes'), repos: achCount('repos'), branches: achCount('branches'), cleanDesk: achCount('cleanDesk'), talks: achCount('talks'),
|
|
44
44
|
sprints: achCount('sprints'), neon: achCount('neon'), tabPaper: achCount('tab_paper'), tabUsage: achCount('tab_usage'), tabToday: achCount('tab_today'), tabFeed: achCount('tab_feed'), tabShop: achCount('tab_shop'),
|
|
45
|
-
spent: prog.spent, moves: achCount('moves'), themesTried: achCount('themesTried'), midnight: achCount('midnight'),
|
|
45
|
+
spent: prog.spent, moves: achCount('moves'), missionsDone: achCount('missionsDone'), arcadeBest: achCount('arcadeBest'), surprises: achCount('surprises'), outfits: achCount('outfits'), shares: achCount('shares'), terraceVisits: achCount('terraceVisits'), serverVisits: achCount('serverVisits'), lifts: achCount('lifts'), quickReplies: achCount('quickReplies'), goals: achCount('goals'), launches: achCount('launches'), themesTried: achCount('themesTried'), midnight: achCount('midnight'),
|
|
46
46
|
dayHours: Math.max(0, ...vals.map(d => d.activeMs / 36e5)),
|
|
47
47
|
dayTools: Math.max(0, ...vals.map(d => d.tools)),
|
|
48
48
|
dayOut: Math.max(0, ...vals.map(d => d.output || 0)),
|
|
@@ -136,6 +136,17 @@ const ACH = [
|
|
|
136
136
|
{ id: 'historian', g: 'you', icon: '๐', fact: 'tabFeed', steps: [1, 10, 30, 100, 300] },
|
|
137
137
|
{ id: 'windowshop', g: 'you', icon: '๐ช', fact: 'tabShop', steps: [1, 10, 30, 100, 300] },
|
|
138
138
|
{ id: 'spender', g: 'you', icon: '๐ธ', fact: 'spent', steps: [200, 1000, 3000, 6000, 10000] },
|
|
139
|
+
{ id: 'quick', g: 'you', icon: 'โก', fact: 'quickReplies', steps: [1, 10, 50, 200, 500] },
|
|
140
|
+
{ id: 'goalgetter', g: 'you', icon: '๐ฏ', fact: 'goals', steps: [1, 5, 20, 60, 150] },
|
|
141
|
+
{ id: 'founder', g: 'you', icon: '๐งโ๐ป', fact: 'launches', steps: [1, 5, 20, 60, 150] },
|
|
142
|
+
{ id: 'rooftop', g: 'you', icon: '๐', fact: 'terraceVisits', steps: [1, 10, 30, 100, 300] },
|
|
143
|
+
{ id: 'sysadmin', g: 'you', icon: '๐ง', fact: 'serverVisits', steps: [1, 10, 30, 100, 300] },
|
|
144
|
+
{ id: 'liftboy', g: 'you', icon: '๐', fact: 'lifts', steps: [1, 10, 50, 150, 500] },
|
|
145
|
+
{ id: 'missionary', g: 'you', icon: '๐ฏ', fact: 'missionsDone', steps: [1, 5, 15, 40, 100] },
|
|
146
|
+
{ id: 'gamer', g: 'you', icon: '๐น', fact: 'arcadeBest', steps: [10, 25, 50, 80, 120] },
|
|
147
|
+
{ id: 'lucky', g: 'you', icon: '๐', fact: 'surprises', steps: [1, 10, 30, 100, 300] },
|
|
148
|
+
{ id: 'fashion', g: 'you', icon: '๐ฉ', fact: 'outfits', steps: [1, 5, 20, 50, 100] },
|
|
149
|
+
{ id: 'sharer', g: 'you', icon: '๐ค', fact: 'shares', steps: [1, 5, 20, 50, 100] },
|
|
139
150
|
{ id: 'arranger', g: 'you', icon: 'โฅ', fact: 'moves', steps: [1, 10, 30, 100, 300] },
|
|
140
151
|
{ id: 'themer', g: 'you', icon: '๐', fact: 'themesTried', steps: [2, 5, 10, 20, 40] },
|
|
141
152
|
{ id: 'dj', g: 'you', icon: '๐ป', fact: 'radio', steps: [1, 5, 20, 50, 100] },
|
|
@@ -151,7 +162,7 @@ const ACH = [
|
|
|
151
162
|
const TIER_COINS = [0, 10, 20, 30, 40, 50];
|
|
152
163
|
const superDone = st => st.every(a => a.tier >= 5);
|
|
153
164
|
function coinsEarned(st = achState()) { return st.reduce((n, a) => n + TIER_COINS.slice(1, a.tier + 1).reduce((x, y) => x + y, 0), 0) + (superDone(st) ? 1000 : 0); }
|
|
154
|
-
const coins = () => Math.max(0, coinsEarned() - prog.spent);
|
|
165
|
+
const coins = () => Math.max(0, coinsEarned() + (Number(prog.record.missionCoins) || 0) + (Number(prog.record.eventCoins) || 0) - prog.spent);
|
|
155
166
|
|
|
156
167
|
function achState() {
|
|
157
168
|
const f = achFacts();
|
|
@@ -239,7 +250,7 @@ function renderAchievements() {
|
|
|
239
250
|
const legends = st.filter(a => a.tier >= 5).length, done = superDone(st);
|
|
240
251
|
const superCard = `<div class="ach super ${done ? 'on' : ''}"><span class="ach-ico">๐</span><div><b>${esc(A.superName)}</b><small>${esc(done ? A.tiers[5] : A.locked)}</small><p>${esc(done ? A.superDone : A.superDesc)}</p><div class="ach-bar"><i style="width:${(legends / st.length * 100).toFixed(1)}%"></i></div><div class="ach-foot"><span></span><span>${legends} / ${st.length}</span></div></div></div>`;
|
|
241
252
|
const wallet = `<div class="wallet"><span class="coin"></span><b>${coins().toLocaleString(lang === 'pt' ? 'pt-BR' : 'en')}</b> ${esc(A.coins)}<span class="note">${esc(A.coinsHow)}</span></div>`;
|
|
242
|
-
body.innerHTML = tabsHtml() + `${wallet}${superCard}<h3>${esc(A.aotm)} ยท ${esc(monthName)}</h3><p class="sub">${esc(A.aotmSub)}</p>${podium}
|
|
253
|
+
body.innerHTML = tabsHtml() + `${wallet}${missionsHtml()}${superCard}<h3>${esc(A.aotm)} ยท ${esc(monthName)}</h3><p class="sub">${esc(A.aotmSub)}</p>${podium}
|
|
243
254
|
<h3>${esc(A.title)} ยท ${got}/${all}</h3><p class="sub">${esc(A.sub)}</p>
|
|
244
255
|
${['history', 'live', 'you'].map(g => `<h4 class="ach-g">${esc(A.groups[g])}</h4><div class="achs">${st.filter(a => a.g === g).sort((x, y) => (x.secret && !x.tier) - (y.secret && !y.tier) || y.tier / y.max - x.tier / x.max || y.progress - x.progress).map(card).join('')}</div>`).join('')}`;
|
|
245
256
|
body.querySelectorAll('[data-face]').forEach(el => el.appendChild(Art.portrait(el.dataset.face)));
|
package/public/js/collection.js
CHANGED
|
@@ -32,7 +32,7 @@ function renderPaper() {
|
|
|
32
32
|
const st = achState(), legends = st.filter(a => a.tier >= 5).length;
|
|
33
33
|
const story = (h, p) => `<article><h4>${esc(h)}</h4><p>${esc(p)}</p></article>`;
|
|
34
34
|
const today = new Date().toLocaleDateString(loc, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
|
|
35
|
-
body.innerHTML = tabsHtml() + `<div class="paper">
|
|
35
|
+
body.innerHTML = tabsHtml() + `<div class="rhead"><span></span><span><button class="btn small" data-share-card>๐ค ${esc(T.share.card)}</button> <button class="btn small" data-visit-photo>๐ท ${esc(T.share.visit)}</button></span></div><div class="paper">
|
|
36
36
|
<header><small>${esc(today)}</small><h2>${esc(P.name)}</h2><small>${esc(P.edition(keys.length))}</small></header>
|
|
37
37
|
<div class="lead"><h3>${esc(P.headline(hrs(total)))}</h3><p>${esc(P.leadText(fmtK(tools), fmtK(out), keys.length))}</p></div>
|
|
38
38
|
<div class="cols">
|
|
@@ -40,6 +40,7 @@ function renderPaper() {
|
|
|
40
40
|
${top ? story(P.star((live && live.name) || top.title || top.id.slice(0, 8)), P.starText(hrs(top.activeMs), fmtK(top.tools))) : ''}
|
|
41
41
|
${night ? story(P.owls, P.owlsText(night)) : story(P.sleep, P.sleepText)}
|
|
42
42
|
${both ? story(P.duo, P.duoText(both)) : ''}
|
|
43
|
+
${scoreboardHtml()}
|
|
43
44
|
${story(P.trophies, P.trophiesText(st.reduce((n, a) => n + a.tier, 0), legends, coins()))}
|
|
44
45
|
${(() => { const k = new Date().toISOString().slice(0, 10), d = replies[k]; return d && d.n ? story(P.replies, P.repliesText(fmtDur(d.total / d.n), fmtDur(d.best))) : ''; })()}
|
|
45
46
|
</div>
|
|
@@ -48,7 +49,7 @@ function renderPaper() {
|
|
|
48
49
|
|
|
49
50
|
// ---- pets unlocked by achievements: a dog at 8 achievements at gold or better, a parrot at 16 ----
|
|
50
51
|
const goldCount = () => achState().filter(a => a.tier >= 3).length;
|
|
51
|
-
const dog = { x: 0, y: 0, tx: 0, ty: 0, until: 0, ready: false };
|
|
52
|
+
const dog = { x: 0, y: 0, tx: 0, ty: 0, until: 0, ready: false }, bunny = { x: 0, y: 0, tx: 0, ty: 0, until: 0, ready: false };
|
|
52
53
|
function drawPets(t, movers) {
|
|
53
54
|
const g = goldCount();
|
|
54
55
|
if (g >= 8 && prog.mascot !== 'dog') {
|
|
@@ -60,6 +61,13 @@ function drawPets(t, movers) {
|
|
|
60
61
|
movers.push({ y: dog.y, draw: () => drawDog(dog.x, dog.y, t, dog.flip, moving) });
|
|
61
62
|
}
|
|
62
63
|
if (g >= 16) drawParrot(RX + RW - 30, TOP - 26, t);
|
|
64
|
+
if (owns('bunnypet') && prog.mascot !== 'bunny') {
|
|
65
|
+
if (!bunny.ready) Object.assign(bunny, { x: RX + 60, y: TOP + 100, tx: RX + 60, ty: TOP + 100, ready: true });
|
|
66
|
+
const dx = bunny.tx - bunny.x, dy = bunny.ty - bunny.y, d = Math.hypot(dx, dy);
|
|
67
|
+
if (d > 1) { const hop = ((t / 200) | 0) % 2; bunny.x += dx / d * (hop ? 1.4 : 0); bunny.y += dy / d * (hop ? 1.4 : 0); bunny.flip = dx < 0; }
|
|
68
|
+
else if (Date.now() > bunny.until) { bunny.tx = RX + 10 + Math.random() * (RW - 30); bunny.ty = wing.ping - 20 + Math.random() * 16; bunny.until = Date.now() + 3000 + Math.random() * 5000; }
|
|
69
|
+
movers.push({ y: bunny.y, draw: () => drawCritter('bunny', bunny.x, bunny.y, d > 1 ? 'walk' : 'sit', t, bunny.flip) });
|
|
70
|
+
}
|
|
63
71
|
}
|
|
64
72
|
function drawDog(x, y, t, flip, moving) {
|
|
65
73
|
const f = moving ? ((t / 120) | 0) % 2 : 0, c = '#c98f5a', d = '#8a5a3a';
|
|
@@ -87,6 +95,11 @@ const SHOP = [
|
|
|
87
95
|
{ id: 'statue', price: 800, icon: '๐ฝ' },
|
|
88
96
|
{ id: 'piano', price: 1200, icon: '๐น' },
|
|
89
97
|
{ id: 'fountain', price: 2000, icon: 'โฒ' },
|
|
98
|
+
{ id: 'turtle', price: 400, icon: '๐ข' },
|
|
99
|
+
{ id: 'bunnypet', price: 600, icon: '๐' },
|
|
100
|
+
{ id: 'bigtank', price: 900, icon: '๐ ' },
|
|
101
|
+
{ id: 'cap', price: 150, icon: '๐งข' }, { id: 'headphones', price: 300, icon: '๐ง' }, { id: 'shades', price: 250, icon: '๐ถ' }, { id: 'party', price: 200, icon: '๐ฅณ' },
|
|
102
|
+
{ id: 'shirt_pink', price: 300, icon: '๐' }, { id: 'shirt_black', price: 300, icon: '๐' }, { id: 'shirt_gold', price: 400, icon: '๐' }, { id: 'crown', price: 1500, icon: '๐' },
|
|
90
103
|
];
|
|
91
104
|
const owns = id => prog.owned.includes(id);
|
|
92
105
|
function buy(id) {
|
|
@@ -103,7 +116,7 @@ function renderShop() {
|
|
|
103
116
|
${has ? `<small class="ok">${esc(S2.owned)}</small> <button class="btn small" data-move-item>${esc(S2.move)}</button>` : `<button class="btn small" data-buy="${it.id}" ${can ? '' : 'disabled'}><span class="coin"></span> ${it.price}</button>`}</div></div>`; }).join('')}</div>
|
|
104
117
|
${prog.owned.length ? `<p><button class="btn small" data-move-item>โฅ ${esc(S2.decoTitle)}</button></p>` : ''}`;
|
|
105
118
|
}
|
|
106
|
-
reportEl.addEventListener('click', e => { const b = e.target.closest('[data-buy]'); if (b) buy(b.dataset.buy); if (e.target.closest('[data-move-item]')) setDecorating(true); });
|
|
119
|
+
reportEl.addEventListener('click', e => { if (e.target.closest('[data-share-card]')) return shareCard(); if (e.target.closest('[data-visit-photo]')) return visitPhoto(); const b = e.target.closest('[data-buy]'); if (b) buy(b.dataset.buy); if (e.target.closest('[data-move-item]')) setDecorating(true); });
|
|
107
120
|
|
|
108
121
|
// bought items, drawn in fixed places (floor items before the people, so people walk in front)
|
|
109
122
|
// ---- bought items: each one has a default spot and can be moved in decorate mode ----
|
|
@@ -118,6 +131,7 @@ const ITEMS = {
|
|
|
118
131
|
fountain: { w: 26, h: 20, at: () => ({ x: CX - 12, y: H - 72 }) },
|
|
119
132
|
neon: { w: 30, h: 8, at: () => ({ x: Math.min(W - 34, RX + 150 > W - 40 ? RX + 2 : RX + 150), y: 2 }) },
|
|
120
133
|
disco: { w: 8, h: 14, at: () => ({ x: CX - 3, y: TOP - 2 }) },
|
|
134
|
+
turtle: { w: 24, h: 16, at: () => ({ x: RX + 110, y: wing.copa + 30 }) },
|
|
121
135
|
};
|
|
122
136
|
function itemPos(id) {
|
|
123
137
|
const it = ITEMS[id], p = prog.placed && prog.placed[id];
|
|
@@ -125,6 +139,9 @@ function itemPos(id) {
|
|
|
125
139
|
return { x: Math.max(0, Math.min(W - it.w, pos.x)), y: Math.max(0, Math.min(H - it.h, pos.y)), w: it.w, h: it.h };
|
|
126
140
|
}
|
|
127
141
|
const ITEM_DRAW = {
|
|
142
|
+
turtle: (x, y, t) => { r(x - 1, y - 1, 26, 18, PAL.ink); r(x, y, 24, 16, '#bfe3f0'); r(x, y + 11, 24, 5, '#c9a86b'); r(x + 3, y + 8, 1, 4, '#3a8f46'); r(x + 19, y + 7, 1, 5, '#3a8f46');
|
|
143
|
+
const tx = x + 6 + Math.round((Math.sin(t / 3000) + 1) * 4); r(tx, y + 8, 8, 4, '#3a6a2a'); r(tx + 1, y + 7, 6, 1, '#5a8a3a'); r(tx + 8, y + 9, 2, 2, '#7aa04a'); r(tx + 1, y + 12, 1, 1, '#7aa04a'); r(tx + 6, y + 12, 1, 1, '#7aa04a');
|
|
144
|
+
ctx.fillStyle = 'rgba(255,255,255,.18)'; ctx.fillRect(x + 2, y + 1, 2, 9); },
|
|
128
145
|
rug: (x, y) => drawPersianRug(x, y + 3, 76, 44),
|
|
129
146
|
plants: (x, y) => { Art.drawPlant(x, y + 2, true); Art.drawPlant(x + 13, y, true); Art.drawPlant(x + 26, y + 3, false); },
|
|
130
147
|
lamps: (x, y, t, glows) => { for (const lx of [x, x + 16]) { r(lx + 3, y + 4, 2, 14, PAL.ink); r(lx, y, 8, 5, '#ffd84d'); glows.push({ x: lx + 4, y: y + 2, r: 22, c: '#ffd84d' }); } },
|
|
@@ -199,7 +216,7 @@ const partyBounce = (id, t) => partying() ? -Math.abs(Math.round(Math.sin(t / 11
|
|
|
199
216
|
// ---- group photo: the office canvas as a PNG ----
|
|
200
217
|
function groupPhoto() {
|
|
201
218
|
const cv = document.getElementById('cv');
|
|
202
|
-
achBump('photos');
|
|
219
|
+
achBump('photos'); savePhotoThumb();
|
|
203
220
|
cv.toBlob(b => {
|
|
204
221
|
if (!b) return;
|
|
205
222
|
const a = document.createElement('a'), d = new Date();
|