Compare commits
4 Commits
3a00774b50
...
683c60fff9
| Author | SHA1 | Date | |
|---|---|---|---|
| 683c60fff9 | |||
| 69e7d24e10 | |||
| ed7900656d | |||
| bc867c95a1 |
20
TODO.md
20
TODO.md
@@ -4,20 +4,20 @@
|
||||
|
||||
- [ ] **Allow Deep players to roll back game events.** This may require some updates to how game state is handled. Feel free to propose significant overhauls/refactors to enable this cleanly, I am not attached to the current architecture at all.
|
||||
|
||||
- [ ] **Dead Pi-Rat re-roll flow.** I suspect this is badly broken, as it was implemented in a rush by an older model. In particular, other players will need to contribute likes/hates and techniques, which I believe are not currently implemented. This flow should also be used for new players who join after initial character creation, and should take place as a between-scenes phase after Deep Upkeep.
|
||||
|
||||
## Missing Features
|
||||
|
||||
- [ ] **Smell-based temporary nicknames at game start.** When joining, players type an arbitrary lobby name; only re-rolled recruits get "Recruit {smell}". We should keep the lobby names, but be clear that these refer to the players, not the Pi-Rats, who should be identified by their smell until they earn a Name.
|
||||
- [ ] **Tooltip reminders of card suit themes**. When you mouse over a card, there should be reminders that e.g. hearts are thematically social skills. Check the manual for the other suits.
|
||||
- [ ] **Name string transfer on a refused Name Tax.** Mechanically the Objective (and Rank) transfer and the winner gets a "claim your name" prompt; this should be updated so that the stealer literally takes the other Pi-Rat's name, leaving them demoted to "Recruit {smell}".
|
||||
- [ ] **First-scene framing requirement** ("must establish the Pi-Rats are on a ship they can steal; don't start far from water") is not surfaced anywhere in the scene-setup UI. We could add an interstitial phase that prompts the Deep to explain the scene before we move to the scene UI with the objectives.
|
||||
- [ ] **Dead Pi-Rat re-roll flow.** I suspect this is badly broken, as it was implemented in a rush by an older model. In particular, other players will need to contribute likes/hates and techniques, which I believe are not currently implemented. This flow should also be used for new players who join after initial character creation, and should take place as a between-scenes phase after Deep Upkeep.
|
||||
- [ ] **Event log visibility**. The event log should be visible in all phases, not just during a scene.
|
||||
- [ ] **Admin page link**. The admin/creator player should always have a link to the admin page available to them.
|
||||
- [x] **Incomplete admin page.**: The Admin page should include links for players to re-join the game.
|
||||
- [x] **Less intrusive link copied notification.** When you copy a join link from the lobby page, it pops up an alert() to let you know the link was copied. I'd prefer something more subtle, and more importantly not modal.
|
||||
- [x] **Rules endpoint** Make a nicely formatted HTML version of the rulebook. It should probably also be linked from a help menu somewhere that's always available, rather than only being surfaced on the splash page of the site (which joining players don't even see). Work from "Rats with Gats beta.pdf" rather than "RULEBOOK.md", which is a low fidelity AI summary of the former.
|
||||
|
||||
## Cosmetic
|
||||
|
||||
- [ ] **Refactor the CSS.** Make it easy to re-theme by keeping the definitions of primary/background/highlight/etc colors confined to a few specific styles. If this requires adopting some kind of CSS framework/preprocessing, so be it.
|
||||
- [ ] **Re-styling**. The entire app could use a face lift, but in particular the splash page is ugly.
|
||||
|
||||
## Worth Double-Checking / Minor
|
||||
|
||||
- [ ] **Dev/prod server flows.** Investigate where assets are served from in both development and production workflows. Make sure we're not committing any compiled svelte code. In production, the app should be used through the Nix flake, which should handle the compile step when the flake is built.
|
||||
- [ ] **Refactor the CSS.** Make it easy to re-theme by keeping the definitions of primary/background/highlight/etc colors confined to a few specific styles. If this requires adopting some kind of CSS framework/preprocessing, so be it.
|
||||
- [ ] **More formal database migration procedures.**
|
||||
- [ ] **Rules endpoint** Make a nicely formatted HTML version of the rulebook. It should probably also be linked from a help menu somewhere that's always available, rather than only being surfaced on the splash page of the site (which joining players don't even see)
|
||||
- [ ] **Re-styling**. The entire app could use a face lift, but in particular the splash page is ugly.
|
||||
|
||||
@@ -15,4 +15,26 @@
|
||||
|
||||
<main>
|
||||
<Router {routes} />
|
||||
<a class="rules-corner-link" href="/rules" target="_blank" rel="noopener" title="Open the rulebook in a new tab">📖 Rules</a>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.rules-corner-link {
|
||||
position: fixed;
|
||||
top: 12px;
|
||||
left: 16px;
|
||||
z-index: 1001;
|
||||
padding: 6px 14px;
|
||||
border-radius: 16px;
|
||||
background: rgba(10, 15, 25, 0.9);
|
||||
border: 1px solid var(--glass-border-focus);
|
||||
color: var(--neon-cyan);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
font-family: var(--font-heading);
|
||||
}
|
||||
.rules-corner-link:hover {
|
||||
background: rgba(0, 242, 254, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
export let state;
|
||||
|
||||
let starting = false;
|
||||
let copiedLink = null;
|
||||
let copiedTimer = null;
|
||||
|
||||
function copyLink(which, value) {
|
||||
navigator.clipboard.writeText(value);
|
||||
copiedLink = which;
|
||||
clearTimeout(copiedTimer);
|
||||
copiedTimer = setTimeout(() => { copiedLink = null; }, 2000);
|
||||
}
|
||||
|
||||
async function startGame() {
|
||||
starting = true;
|
||||
@@ -39,7 +48,9 @@
|
||||
<h4>Share Join Link:</h4>
|
||||
<div class="copy-container">
|
||||
<input type="text" readonly value="{window.location.origin}/#/game/{state.game.id}/join" class="copy-input" id="join-link-copy">
|
||||
<button on:click={() => { navigator.clipboard.writeText(document.getElementById('join-link-copy').value); alert('Join link copied!'); }} class="btn btn-secondary btn-small">Copy</button>
|
||||
<button on:click={() => copyLink('join', document.getElementById('join-link-copy').value)} class="btn btn-secondary btn-small">
|
||||
{copiedLink === 'join' ? '✓ Copied' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +60,9 @@
|
||||
<p class="info-text">Save this admin link! You can use it to recover character sheets if another player loses their connection or link.</p>
|
||||
<div class="copy-container">
|
||||
<input type="text" readonly value="{window.location.origin}/#/game/{state.game.id}/admin" class="copy-input admin-input" id="admin-link-copy">
|
||||
<button on:click={() => { navigator.clipboard.writeText(document.getElementById('admin-link-copy').value); alert('Admin link copied!'); }} class="btn btn-gold btn-small">Copy Admin Link</button>
|
||||
<button on:click={() => copyLink('admin', document.getElementById('admin-link-copy').value)} class="btn btn-gold btn-small">
|
||||
{copiedLink === 'admin' ? '✓ Copied' : 'Copy Admin Link'}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<a href="/#/game/{state.game.id}/admin" class="text-sm text-silver underline hover:text-white">Access Admin Panel directly</a>
|
||||
|
||||
@@ -8,6 +8,19 @@
|
||||
|
||||
let data = null;
|
||||
let error = '';
|
||||
let copiedPlayerId = null;
|
||||
let copiedTimer = null;
|
||||
|
||||
function rejoinLink(playerId) {
|
||||
return `${window.location.origin}/#/game/${gameId}/player/${playerId}`;
|
||||
}
|
||||
|
||||
function copyRejoinLink(playerId) {
|
||||
navigator.clipboard.writeText(rejoinLink(playerId));
|
||||
copiedPlayerId = playerId;
|
||||
clearTimeout(copiedTimer);
|
||||
copiedTimer = setTimeout(() => { copiedPlayerId = null; }, 2000);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Try to load admin_key from local storage
|
||||
@@ -58,6 +71,7 @@
|
||||
<th class="p-3">Role</th>
|
||||
<th class="p-3">Status</th>
|
||||
<th class="p-3">Rank</th>
|
||||
<th class="p-3">Re-join Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -78,6 +92,14 @@
|
||||
{/if}
|
||||
</td>
|
||||
<td class="p-3">{p.rank}</td>
|
||||
<td class="p-3">
|
||||
<div class="link-copy-action">
|
||||
<a href={rejoinLink(p.id)} class="btn btn-secondary btn-small">Open</a>
|
||||
<button on:click={() => copyRejoinLink(p.id)} class="btn btn-secondary btn-small">
|
||||
{copiedPlayerId === p.id ? '✓ Copied' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
|
||||
@@ -9,6 +9,10 @@ export default defineConfig({
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/rules': {
|
||||
target: 'http://127.0.0.1:8000',
|
||||
changeOrigin: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pirats = "pirats.main:main"
|
||||
where = ["src"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
pirats = ["static/**/*"]
|
||||
pirats = ["static/**/*", "rules.html"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["src"]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI, Form, Depends, HTTPException, APIRouter
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from sqlmodel import Session
|
||||
import uvicorn
|
||||
@@ -105,6 +106,11 @@ api.include_router(admin_router)
|
||||
# Mount API at /api
|
||||
app.include_router(api, prefix="/api")
|
||||
|
||||
# Static rulebook page (registered before the SPA mount so it takes precedence)
|
||||
@app.get("/rules", include_in_schema=False)
|
||||
def rules_page():
|
||||
return FileResponse(BASE_DIR / "rules.html", media_type="text/html")
|
||||
|
||||
# Mount SPA
|
||||
static_dir = BASE_DIR / "static"
|
||||
if os.path.exists(static_dir):
|
||||
|
||||
1304
src/pirats/rules.html
Normal file
1304
src/pirats/rules.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user