Re-theme the app: token-based CSS and a full face lift

CSS refactor: every color, font, radius, and shadow is now defined once
in theme.css; all other styles derive translucent variants with
color-mix(), so re-theming means editing one file. Removed the
duplicate core.css/style.css import chains, dead styles, and the
Bootstrap/Tailwind orphan classes that were silently unstyled
(the main reason the splash page looked broken).

New "Lantern & Brine" look: brass and parchment on a lantern-lit
ink-navy night, Pirata One wordmark with Alegreya SC/Sans body fonts
(self-hosted via fontsource — they were previously referenced but
never loaded), parchment-faced playing cards, and a rebuilt splash
page. Inline color styles in components were replaced with semantic
classes (prompt-box, notice-banner, status chips, etc.), the rulebook's
embedded palette was updated to match, and the leftover Vite favicon
and "frontend" page title were replaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 06:50:00 -07:00
parent ba6bf35579
commit 0744893e7b
36 changed files with 1442 additions and 1185 deletions

View File

@@ -10,8 +10,8 @@
## Cosmetic ## 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. - [x] **Refactor the CSS.** All colors/fonts/radii/shadows now live as tokens in `frontend/src/assets/css/theme.css`; everything else derives translucent variants via `color-mix()`. Re-theming the app = editing that one file (plus the mirrored token block at the top of `src/pirats/rules.html`).
- [ ] **Re-styling**. The entire app could use a face lift, but in particular the splash page is ugly. - [x] **Re-styling**. New "Lantern & Brine" theme: brass/parchment on lantern-lit ink-navy, Pirata One wordmark, Alegreya SC/Sans (self-hosted via fontsource), parchment-faced playing cards, rebuilt splash page.
## Worth Double-Checking / Minor ## Worth Double-Checking / Minor

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title> <title>Rats with Gats</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -8,6 +8,9 @@
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@fontsource/alegreya-sans": "^5.2.8",
"@fontsource/alegreya-sc": "^5.2.8",
"@fontsource/pirata-one": "^5.2.8",
"svelte-spa-router": "^5.1.0" "svelte-spa-router": "^5.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -50,6 +53,33 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@fontsource/alegreya-sans": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/alegreya-sans/-/alegreya-sans-5.2.8.tgz",
"integrity": "sha512-kXUTR1S6dsWa7rBxaDJ3P05HodmYmxfY20aQtQ4SYVmrEYrWtaw5A1n+h+wcCF0nAMwf+CQ4M435dNuWD1YfMg==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/alegreya-sc": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/alegreya-sc/-/alegreya-sc-5.2.8.tgz",
"integrity": "sha512-28ZMhxJ/bx7f9S2oTTPeXvKchAu5VxPbEG95RI+EI9l3/1YyomEXzUqX0lCdCQb5fDXhdmVwHSD07P0BL4ueQA==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/pirata-one": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/pirata-one/-/pirata-one-5.2.8.tgz",
"integrity": "sha512-hl3Zqt6mgP+jstHnv2qYc2xQdYM1DSYrpSU9bpS1fYExoGFgJJNbXTPBQ9XivNk9RCKkibhou3iqoOZQOXM48w==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",

View File

@@ -14,6 +14,9 @@
"vite": "^8.0.12" "vite": "^8.0.12"
}, },
"dependencies": { "dependencies": {
"@fontsource/alegreya-sans": "^5.2.8",
"@fontsource/alegreya-sc": "^5.2.8",
"@fontsource/pirata-one": "^5.2.8",
"svelte-spa-router": "^5.1.0" "svelte-spa-router": "^5.1.0"
} }
} }

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 240 B

View File

@@ -1,11 +1,17 @@
@import './assets/css/style.css'; /* ==========================================================================
@import './assets/css/core.css'; RATS WITH GATS — STYLESHEET INDEX
theme.css holds every color/font/shape token (edit it to re-theme).
base.css holds the reset, typography, and utility classes.
The rest are component- and phase-specific styles built on those tokens.
========================================================================== */
@import './assets/css/theme.css';
@import './assets/css/base.css';
@import './assets/css/components.css'; @import './assets/css/components.css';
@import './assets/css/card.css';
@import './assets/css/welcome.css'; @import './assets/css/welcome.css';
@import './assets/css/lobby.css'; @import './assets/css/lobby.css';
@import './assets/css/character.css'; @import './assets/css/character.css';
@import './assets/css/scene-setup.css'; @import './assets/css/scene-setup.css';
@import './assets/css/scene-play.css'; @import './assets/css/scene-play.css';
@import './assets/css/card.css';
@import './assets/css/upkeep.css'; @import './assets/css/upkeep.css';
@import './assets/css/admin.css'; @import './assets/css/admin.css';

View File

@@ -1,22 +1,46 @@
/* --- Admin Panel --- */ /* --- Admin panel --- */
.admin-players-list { .admin-page {
display: flex; max-width: 56rem;
flex-direction: column; margin: 0 auto;
gap: 1rem; padding: 2rem 1rem;
}
.admin-page h1 {
font-family: var(--font-display);
font-weight: 400;
font-size: 2.2rem;
color: var(--accent);
margin-bottom: 1.5rem;
letter-spacing: 1px;
}
.admin-key-chip {
background: var(--well);
border: 1px solid var(--edge);
border-radius: 4px;
padding: 0.15rem 0.5rem;
font-family: ui-monospace, monospace;
font-size: 0.875rem;
}
.admin-table {
width: 100%;
text-align: left;
background: var(--well);
border: 1px solid var(--edge);
border-radius: var(--radius-sm);
border-collapse: collapse;
margin-top: 1rem; margin-top: 1rem;
} }
.admin-player-row { .admin-table th, .admin-table td {
display: flex; padding: 0.75rem;
justify-content: space-between; border-bottom: 1px solid var(--edge-soft);
align-items: center;
padding: 1rem;
gap: 1rem;
flex-wrap: wrap;
} }
.player-info-basic { .admin-table th {
font-size: 1.05rem; font-family: var(--font-heading);
color: var(--accent);
} }
.link-copy-action { .link-copy-action {
@@ -26,3 +50,45 @@
flex: 1; flex: 1;
max-width: 500px; max-width: 500px;
} }
/* --- Game over --- */
.gameover-container {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.gameover-card {
text-align: center;
}
.gameover-card h1 {
font-family: var(--font-display);
font-weight: 400;
font-size: 2.6rem;
color: var(--accent);
letter-spacing: 1px;
margin-bottom: 0.75rem;
}
.gameover-card .sheet-group {
text-align: left;
margin-top: 1.5rem;
}
.crew-epitaph-row {
background: var(--well);
padding: 0.75rem;
border-radius: var(--radius-sm);
margin-bottom: 0.5rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.crew-epitaph-row .objectives-summary {
font-size: 0.85rem;
color: var(--text-muted);
}

View File

@@ -0,0 +1,208 @@
/* --- Reset & page setup --- */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background:
radial-gradient(ellipse at 50% -20%, color-mix(in srgb, var(--accent) 7%, transparent) 0%, transparent 55%),
radial-gradient(circle at 50% 60%, var(--bg) 0%, var(--bg-deep) 100%);
background-attachment: fixed;
color: var(--text);
font-family: var(--font-body);
font-size: 16px;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
h1, h2, h3, h4, h5 {
font-family: var(--font-heading);
font-weight: 700;
line-height: 1.25;
}
a {
color: var(--deep);
}
::selection {
background: color-mix(in srgb, var(--accent) 35%, transparent);
}
input[type="checkbox"] {
accent-color: var(--accent);
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-deep);
}
::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--accent) 50%, var(--bg-deep));
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}
/* --- App layout --- */
.app-main {
flex: 1;
padding: 2rem;
max-width: 1400px;
width: 100%;
margin: 0 auto;
}
/* Top padding clears the fixed corner menu */
.dashboard-container {
padding: 3.5rem 2rem 4rem;
max-width: 1400px;
width: 100%;
margin: 0 auto;
}
@media (max-width: 600px) {
.dashboard-container {
padding: 3.5rem 1rem 4rem;
}
}
.view-header {
margin-bottom: 1.5rem;
}
.view-header h2,
.lobby-view h2,
.scene-setup-view h2,
.between-scenes-view h2 {
font-size: 2.1rem;
color: var(--accent);
margin-bottom: 0.5rem;
letter-spacing: 0.5px;
}
.description {
color: var(--text-muted);
max-width: 720px;
margin: 0 auto 1.5rem;
}
/* --- Loader / spinner widgets --- */
.loader-container {
padding: 4rem;
}
.spinner {
width: 50px;
height: 50px;
border: 3px solid var(--edge-soft);
border-radius: 50%;
border-top-color: var(--accent);
animation: spin 1s ease-in-out infinite;
margin: 0 auto 1.5rem;
}
.spinner-small {
width: 20px;
height: 20px;
border: 2px solid var(--edge-soft);
border-radius: 50%;
border-top-color: var(--accent);
animation: spin 1s ease-in-out infinite;
display: inline-block;
flex-shrink: 0;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* --- Text utilities --- */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-sm { font-size: 0.875rem; }
.italic { font-style: italic; }
.font-bold { font-weight: 700; }
.font-mono { font-family: ui-monospace, monospace; }
.underline { text-decoration: underline; }
.text-muted { color: var(--text-muted); }
.text-accent, .gold-text { color: var(--accent); }
.text-danger { color: var(--danger); }
.text-success, .success-text { color: var(--success); font-weight: 600; }
.text-warning { color: var(--warning); }
.text-deep, .deep-text { color: var(--deep); }
.info-text {
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
}
.section-desc {
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 1rem;
}
.empty-text {
color: var(--text-muted);
font-style: italic;
font-size: 0.9rem;
}
.footnote, .footnote-desc {
font-size: 0.8rem;
color: var(--text-muted);
}
.footnote-desc {
margin-top: 0.15rem;
margin-left: 1.5rem;
}
/* --- Spacing & layout utilities --- */
.mt-2 { margin-top: 0.5rem; }
.mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-8 { margin-top: 2rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.p-3 { padding: 0.75rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
.mx-auto { margin-left: auto; margin-right: auto; }
.margin-top { margin-top: 1.5rem; }
.margin-top-small { margin-top: 0.5rem; }
.flex { display: flex; }
.flex-grow { flex: 1; }
.items-center { align-items: center; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: 0.5rem; }
.gap-4 { gap: 1rem; }
.w-full { width: 100%; }
.max-w-4xl { max-width: 56rem; }
.max-w-5xl { max-width: 64rem; }
.space-y-2 > * + * { margin-top: 0.5rem; }
.space-y-6 > * + * { margin-top: 1.5rem; }
.center-block { margin: 1rem auto; max-width: 500px; }
.inline-form { display: flex; gap: 0.5rem; width: 100%; }
.inline-group { display: flex; gap: 0.5rem; width: 100%; }
/* --- Ghost World --- */
.ghost-world {
filter: grayscale(95%) brightness(0.85) sepia(15%) contrast(1.05);
transition: filter 1.2s ease-in-out;
}

View File

@@ -1,22 +1,44 @@
/* Card Widget (Mini) */ /* ============================================================
Playing-card widgets (Card.svelte: mini / medium / large)
Light parchment faces on the dark table for contrast.
============================================================ */
/* Shared face */
.card-mini, .card-medium, .card-large {
background: linear-gradient(150deg, var(--card-face) 0%, var(--card-face-shade) 100%);
border: 1px solid color-mix(in srgb, var(--card-ink-black) 35%, var(--card-face-shade));
color: var(--card-ink-black);
}
.card-mini.suit-h, .card-mini.suit-d,
.card-medium.suit-h .val, .card-medium.suit-h .suit, .card-medium.suit-h .card-center,
.card-medium.suit-d .val, .card-medium.suit-d .suit, .card-medium.suit-d .card-center,
.card-large.suit-h .val, .card-large.suit-h .suit, .card-large.suit-h .card-center,
.card-large.suit-d .val, .card-large.suit-d .suit, .card-large.suit-d .card-center {
color: var(--card-ink-red);
}
.card-medium.joker-card, .card-large.joker-card {
background: linear-gradient(150deg, color-mix(in srgb, var(--accent) 30%, var(--card-face)) 0%, var(--card-face-shade) 100%);
border-color: var(--accent-dark);
}
/* --- Mini (played-card columns) --- */
.card-mini { .card-mini {
width: 32px; width: 32px;
height: 48px; height: 48px;
border-radius: 4px; border-radius: 4px;
border: 1px solid var(--text-muted);
background: var(--bg-dark);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
padding: 2px 4px; padding: 2px 4px;
font-size: 0.7rem; font-size: 0.7rem;
font-weight: 900; font-weight: 900;
position: relative;
} }
.card-mini.base-card { .card-mini.base-card {
border-color: var(--gold); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
color: var(--gold);
background: rgba(212,175,55,0.1);
} }
.card-mini.rotated { .card-mini.rotated {
@@ -25,13 +47,11 @@
} }
.card-mini.success { .card-mini.success {
border-color: var(--neon-emerald); box-shadow: 0 0 0 2px var(--success);
color: var(--neon-emerald);
} }
.card-mini.failure { .card-mini.failure {
border-color: var(--red-suit); box-shadow: 0 0 0 2px var(--danger);
color: var(--red-suit);
} }
.card-mini .val { .card-mini .val {
@@ -48,17 +68,15 @@
position: absolute; position: absolute;
bottom: -1px; bottom: -1px;
left: 1px; left: 1px;
color: var(--text-muted); color: color-mix(in srgb, var(--card-ink-black) 60%, var(--card-face));
} }
/* Card Widget (Medium) */ /* --- Medium (obstacle display) --- */
.card-medium { .card-medium {
width: 75px; width: 75px;
height: 112px; height: 112px;
border-radius: 6px; border-radius: var(--radius-sm);
border: 1.5px solid var(--glass-border); box-shadow: 0 3px 10px rgba(0, 0, 0, 0.4);
background: linear-gradient(135deg, var(--bg-ocean-light) 0%, var(--bg-dark) 100%);
box-shadow: 0 3px 10px rgba(0,0,0,0.4);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@@ -83,29 +101,6 @@
font-size: 0.75rem; font-size: 0.75rem;
} }
.card-medium.suit-c, .card-medium.suit-s {
border-color: rgba(226, 232, 240, 0.15);
}
.card-medium.suit-h, .card-medium.suit-d {
border-color: rgba(255, 42, 95, 0.15);
}
.card-medium.joker-card {
border-color: var(--gold);
background: linear-gradient(135deg, #1d1b10 0%, var(--bg-dark) 100%);
}
.card-medium.suit-c .val, .card-medium.suit-c .suit,
.card-medium.suit-s .val, .card-medium.suit-s .suit {
color: var(--black-suit);
}
.card-medium.suit-h .val, .card-medium.suit-h .suit,
.card-medium.suit-d .val, .card-medium.suit-d .suit {
color: var(--red-suit);
}
.card-medium .card-center { .card-medium .card-center {
font-size: 1.8rem; font-size: 1.8rem;
text-align: center; text-align: center;
@@ -113,17 +108,13 @@
margin: auto 0; margin: auto 0;
} }
.card-medium.suit-c .card-center, .card-medium.suit-s .card-center { color: var(--black-suit); } /* --- Large (the player's hand) --- */
.card-medium.suit-h .card-center, .card-medium.suit-d .card-center { color: var(--red-suit); }
/* Card Widget (Large) */
.card-large { .card-large {
width: 110px; width: 110px;
height: 165px; height: 165px;
border-radius: 8px; border-radius: var(--radius-md);
border: 2px solid var(--glass-border); border-width: 2px;
background: linear-gradient(135deg, var(--bg-ocean-light) 0%, var(--bg-dark) 100%); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@@ -134,29 +125,7 @@
.card-large:hover { .card-large:hover {
transform: translateY(-8px) scale(1.05); transform: translateY(-8px) scale(1.05);
border-color: var(--gold); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.55), 0 0 14px color-mix(in srgb, var(--accent) 30%, transparent);
box-shadow: 0 10px 25px rgba(212, 175, 55, 0.2);
}
.card-large.suit-c, .card-large.suit-s {
border-color: rgba(226, 232, 240, 0.15);
}
.card-large.suit-c:hover, .card-large.suit-s:hover {
border-color: var(--black-suit);
box-shadow: 0 10px 25px var(--black-glow);
}
.card-large.suit-h, .card-large.suit-d {
border-color: rgba(255, 42, 95, 0.15);
}
.card-large.suit-h:hover, .card-large.suit-d:hover {
border-color: var(--red-suit);
box-shadow: 0 10px 25px var(--red-glow);
}
.card-large.joker-card {
border-color: var(--gold);
background: linear-gradient(135deg, #1d1b10 0%, var(--bg-dark) 100%);
} }
.card-large .card-corner { .card-large .card-corner {
@@ -175,16 +144,6 @@
font-size: 1rem; font-size: 1rem;
} }
.card-large.suit-c .val, .card-large.suit-c .suit,
.card-large.suit-s .val, .card-large.suit-s .suit {
color: var(--black-suit);
}
.card-large.suit-h .val, .card-large.suit-h .suit,
.card-large.suit-d .val, .card-large.suit-d .suit {
color: var(--red-suit);
}
.card-large .card-center { .card-large .card-center {
font-size: 2.8rem; font-size: 2.8rem;
text-align: center; text-align: center;
@@ -192,9 +151,6 @@
margin: auto 0; margin: auto 0;
} }
.card-large.suit-c .card-center, .card-large.suit-s .card-center { color: var(--black-suit); }
.card-large.suit-h .card-center, .card-large.suit-d .card-center { color: var(--red-suit); }
.card-large .card-tech-overlay { .card-large .card-tech-overlay {
position: absolute; position: absolute;
bottom: 0.5rem; bottom: 0.5rem;
@@ -203,10 +159,10 @@
text-align: center; text-align: center;
} }
.tech-tag { .card-large .tech-tag {
background: rgba(0, 0, 0, 0.7); background: color-mix(in srgb, var(--surface) 92%, transparent);
border: 1px solid var(--gold); border: 1px solid var(--accent);
color: var(--gold); color: var(--accent-bright);
font-size: 0.65rem; font-size: 0.65rem;
padding: 0.15rem 0.3rem; padding: 0.15rem 0.3rem;
border-radius: 4px; border-radius: 4px;
@@ -220,7 +176,7 @@
gap: 2px; gap: 2px;
} }
/* Drag and Drop Interface Styles */ /* --- Drag & drop --- */
.card-large[draggable="true"] { .card-large[draggable="true"] {
cursor: grab; cursor: grab;
} }
@@ -236,9 +192,9 @@
} }
.obstacle-item.drag-over { .obstacle-item.drag-over {
border-color: var(--gold) !important; border-color: var(--accent) !important;
background: rgba(212, 175, 55, 0.1) !important; background: color-mix(in srgb, var(--accent) 9%, transparent) !important;
box-shadow: 0 0 20px rgba(212, 175, 55, 0.2) !important; box-shadow: 0 0 20px color-mix(in srgb, var(--accent) 20%, transparent) !important;
transform: translateY(-2px) scale(1.01); transform: translateY(-2px) scale(1.01);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); transition: var(--transition-smooth);
} }

View File

@@ -1,4 +1,4 @@
/* --- Character Creation & Sheet Grid --- */ /* --- Character creation & sheet --- */
.creation-grid { .creation-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@@ -10,20 +10,18 @@
.creation-grid { .creation-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
}
.section-desc { .creation-grid > .card {
font-size: 0.9rem; grid-column: span 1 !important;
color: var(--text-muted); }
margin-bottom: 1rem;
} }
.record-line { .record-line {
background: rgba(0,0,0,0.2); background: var(--well);
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-radius: 6px; border-radius: var(--radius-sm);
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
border-left: 3px solid var(--gold); border-left: 3px solid var(--accent);
} }
.delegation-box { .delegation-box {
@@ -33,14 +31,15 @@
} }
.delegation-box h4 { .delegation-box h4 {
font-family: var(--font-body);
font-size: 0.95rem; font-size: 0.95rem;
color: var(--text-primary); color: var(--text);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.answer-box { .answer-box {
font-style: italic; font-style: italic;
color: var(--neon-cyan); color: var(--accent-bright);
font-size: 1.05rem; font-size: 1.05rem;
} }
@@ -50,14 +49,6 @@
font-style: normal; font-style: normal;
} }
.waiting-box {
font-size: 0.9rem;
color: var(--gold);
display: flex;
align-items: center;
gap: 0.5rem;
}
.inbox-list:has(.inbox-item) .inbox-empty-message { .inbox-list:has(.inbox-item) .inbox-empty-message {
display: none; display: none;
} }
@@ -65,8 +56,8 @@
.inbox-item { .inbox-item {
padding: 1rem; padding: 1rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
border-left: 3px solid var(--neon-cyan); border-left: 3px solid var(--deep);
background: rgba(0, 242, 254, 0.02); background: color-mix(in srgb, var(--deep) 4%, transparent);
} }
.inbox-item label { .inbox-item label {
@@ -76,26 +67,22 @@
} }
.submitted-techniques .tech-chip { .submitted-techniques .tech-chip {
background: rgba(212,175,55,0.05); background: color-mix(in srgb, var(--accent) 6%, transparent);
border: 1px solid var(--gold); border: 1px solid var(--accent);
color: var(--gold); color: var(--accent);
padding: 0.5rem; padding: 0.6rem 1rem;
border-radius: 6px; font-size: 0.95rem;
border-radius: var(--radius-sm);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-family: var(--font-heading); font-family: var(--font-heading);
} }
.tech-chip {
padding: 0.6rem 1rem;
font-size: 0.95rem;
}
.tech-assignment-pill { .tech-assignment-pill {
background: rgba(0, 242, 254, 0.05); background: color-mix(in srgb, var(--deep) 6%, transparent);
border: 1px solid var(--neon-cyan); border: 1px solid var(--deep);
color: var(--text-primary); color: var(--text);
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
border-radius: 6px; border-radius: var(--radius-sm);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
text-align: left; text-align: left;
display: flex; display: flex;
@@ -104,8 +91,8 @@
} }
.card-rank { .card-rank {
background: var(--neon-cyan); background: var(--accent);
color: var(--bg-dark); color: var(--text-inverse);
font-weight: 900; font-weight: 900;
width: 24px; width: 24px;
height: 24px; height: 24px;
@@ -114,33 +101,30 @@
justify-content: center; justify-content: center;
border-radius: 50%; border-radius: 50%;
font-size: 0.85rem; font-size: 0.85rem;
flex-shrink: 0;
} }
/* --- Character Sheet layout --- */ /* Crew creation progress rows */
.status-row {
background: var(--well);
border-left: 4px solid var(--edge);
border-radius: var(--radius-sm);
padding: 0.75rem;
display: flex;
justify-content: space-between;
align-items: center;
list-style: none;
}
.status-row.is-ready {
border-left-color: var(--success);
}
/* --- Character sheet layout --- */
.character-sheet-details { .character-sheet-details {
text-align: left; text-align: left;
} }
.sheet-group {
background: rgba(0,0,0,0.15);
padding: 1rem;
border-radius: 8px;
border-left: 2px solid var(--gold-dark);
}
.sheet-group h4 {
color: var(--gold);
font-size: 0.95rem;
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.footnote {
font-size: 0.8rem;
color: var(--text-muted);
}
.techniques-list-sheet { .techniques-list-sheet {
list-style: none; list-style: none;
} }
@@ -150,37 +134,23 @@
font-size: 0.95rem; font-size: 0.95rem;
} }
.footnote-desc { /* --- Face-card technique assignment (drag & drop) --- */
font-size: 0.8rem;
color: var(--text-muted);
margin-top: 0.15rem;
margin-left: 1.5rem;
}
.margin-top-small {
margin-top: 0.5rem;
}
/* --- Visual & Tactile Technique Assignment --- */
.face-tech-drag-form { .face-tech-drag-form {
margin-top: 1.5rem; margin-top: 1.5rem;
} }
.available-techniques-container { .available-techniques-container {
background: rgba(255, 255, 255, 0.02); background: var(--well);
border: 1px dashed var(--glass-border); border: 1px dashed var(--edge);
border-radius: 12px; border-radius: var(--radius-lg);
padding: 1.25rem; padding: 1.25rem;
margin-bottom: 2rem; margin-bottom: 2rem;
box-shadow: inset 0 0 15px rgba(0,0,0,0.2);
} }
.available-techniques-container h4 { .available-techniques-container h4 {
font-family: var(--font-heading); color: var(--accent);
color: var(--gold);
font-size: 1.1rem; font-size: 1.1rem;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
@@ -200,18 +170,17 @@
} }
.draggable-tech-chip { .draggable-tech-chip {
background: linear-gradient(135deg, rgba(22, 36, 59, 0.9) 0%, rgba(13, 23, 38, 0.9) 100%); background: var(--surface-raised);
border: 1px solid var(--gold); border: 1px solid var(--accent);
color: var(--text-primary); color: var(--text);
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
border-radius: 8px; border-radius: var(--radius-sm);
cursor: grab; cursor: grab;
user-select: none; user-select: none;
font-family: var(--font-body);
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 500; font-weight: 500;
transition: var(--transition-smooth); transition: var(--transition-smooth);
box-shadow: 0 4px 10px rgba(0,0,0,0.3), 0 0 5px rgba(212, 175, 55, 0.1); box-shadow: var(--shadow-soft);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
@@ -219,8 +188,7 @@
.draggable-tech-chip:hover { .draggable-tech-chip:hover {
transform: translateY(-2px); transform: translateY(-2px);
border-color: var(--neon-cyan); border-color: var(--accent-bright);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4), 0 0 10px rgba(0, 242, 254, 0.3);
} }
.draggable-tech-chip:active { .draggable-tech-chip:active {
@@ -232,26 +200,20 @@
border-style: dashed; border-style: dashed;
} }
.draggable-tech-chip.selected {
border-color: var(--neon-cyan);
box-shadow: 0 0 15px rgba(0, 242, 254, 0.5);
background: rgba(0, 242, 254, 0.1);
}
.draggable-tech-chip.assigned-hidden { .draggable-tech-chip.assigned-hidden {
opacity: 0.2; opacity: 0.2;
pointer-events: none; pointer-events: none;
cursor: not-allowed; cursor: not-allowed;
border-color: var(--text-muted); border-color: var(--edge);
} }
.drag-icon { .drag-icon {
color: var(--gold); color: var(--accent);
font-size: 0.8rem; font-size: 0.8rem;
opacity: 0.7; opacity: 0.7;
} }
/* Slots Grid */ /* Face-card slots */
.face-cards-slots-grid { .face-cards-slots-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@@ -276,9 +238,9 @@
max-width: 220px; max-width: 220px;
aspect-ratio: 2 / 3; aspect-ratio: 2 / 3;
min-height: 280px; min-height: 280px;
background: linear-gradient(135deg, rgba(13, 23, 38, 0.8) 0%, rgba(7, 11, 18, 0.9) 100%); background: var(--well);
border: 2px dashed rgba(212, 175, 55, 0.3); border: 2px dashed var(--edge-accent);
border-radius: 12px; border-radius: var(--radius-lg);
padding: 1rem; padding: 1rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -286,27 +248,25 @@
transition: var(--transition-smooth); transition: var(--transition-smooth);
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4); box-shadow: var(--shadow-soft);
} }
.face-card-slot:hover { .face-card-slot:hover {
border-color: rgba(212, 175, 55, 0.7); border-color: var(--accent);
box-shadow: 0 12px 30px rgba(212, 175, 55, 0.15);
transform: translateY(-4px); transform: translateY(-4px);
} }
.face-card-slot.drag-over { .face-card-slot.drag-over {
border-color: var(--neon-cyan); border-color: var(--accent-bright);
border-style: solid; border-style: solid;
background: rgba(0, 242, 254, 0.05); background: color-mix(in srgb, var(--accent) 7%, transparent);
box-shadow: 0 0 20px rgba(0, 242, 254, 0.2);
transform: scale(1.02); transform: scale(1.02);
} }
.face-card-slot.has-assignment { .face-card-slot.has-assignment {
border-style: solid; border-style: solid;
border-color: var(--gold); border-color: var(--accent);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5), 0 0 15px rgba(212, 175, 55, 0.15); background: color-mix(in srgb, var(--accent) 5%, var(--well));
} }
.card-bg-letter { .card-bg-letter {
@@ -314,10 +274,9 @@
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
font-family: var(--font-heading); font-family: var(--font-display);
font-size: 8rem; font-size: 9rem;
font-weight: 900; color: color-mix(in srgb, var(--text) 4%, transparent);
color: rgba(255, 255, 255, 0.025);
line-height: 1; line-height: 1;
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
@@ -325,7 +284,7 @@
} }
.face-card-slot.has-assignment .card-bg-letter { .face-card-slot.has-assignment .card-bg-letter {
color: rgba(212, 175, 55, 0.04); color: color-mix(in srgb, var(--accent) 8%, transparent);
} }
.card-slot-header, .card-slot-footer { .card-slot-header, .card-slot-footer {
@@ -342,7 +301,7 @@
.face-card-slot.has-assignment .card-slot-header, .face-card-slot.has-assignment .card-slot-header,
.face-card-slot.has-assignment .card-slot-footer { .face-card-slot.has-assignment .card-slot-footer {
color: var(--gold); color: var(--accent);
} }
.card-slot-body { .card-slot-body {
@@ -360,29 +319,29 @@
font-family: var(--font-heading); font-family: var(--font-heading);
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary); color: var(--text);
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
pointer-events: none; pointer-events: none;
} }
.face-card-slot.has-assignment .card-title { .face-card-slot.has-assignment .card-title {
color: var(--gold); color: var(--accent);
} }
.drop-zone-placeholder { .drop-zone-placeholder {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text-muted); color: var(--text-muted);
border: 1px dashed rgba(255, 255, 255, 0.1); border: 1px dashed var(--edge);
background: rgba(0, 0, 0, 0.2); background: var(--well);
border-radius: 6px; border-radius: var(--radius-sm);
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
transition: var(--transition-smooth); transition: var(--transition-smooth);
pointer-events: none; pointer-events: none;
} }
.face-card-slot:hover .drop-zone-placeholder { .face-card-slot:hover .drop-zone-placeholder {
color: var(--text-primary); color: var(--text);
border-color: rgba(255, 255, 255, 0.2); border-color: var(--edge-accent);
} }
.assigned-tech-content { .assigned-tech-content {
@@ -399,14 +358,14 @@
} }
.assigned-tech-chip { .assigned-tech-chip {
background: rgba(212, 175, 55, 0.08); background: color-mix(in srgb, var(--accent) 9%, transparent);
border: 1px solid var(--gold); border: 1px solid var(--accent);
color: var(--text-primary); color: var(--text);
padding: 0.5rem; padding: 0.5rem;
border-radius: 8px; border-radius: var(--radius-sm);
font-size: 0.85rem; font-size: 0.85rem;
position: relative; position: relative;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); box-shadow: var(--shadow-soft);
word-break: break-word; word-break: break-word;
animation: fadeIn 0.3s ease; animation: fadeIn 0.3s ease;
} }
@@ -418,22 +377,21 @@
width: 18px; width: 18px;
height: 18px; height: 18px;
border-radius: 50%; border-radius: 50%;
background: #c0392b; background: var(--danger);
border: 1px solid #e74c3c; border: 1px solid color-mix(in srgb, var(--danger) 70%, var(--text));
color: white; color: var(--text-inverse);
font-size: 10px; font-size: 10px;
font-weight: 900; font-weight: 900;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
box-shadow: 0 2px 5px rgba(0,0,0,0.5); box-shadow: var(--shadow-soft);
transition: var(--transition-smooth); transition: var(--transition-smooth);
} }
.remove-tech-btn:hover { .remove-tech-btn:hover {
background: #e74c3c; transform: scale(1.15);
transform: scale(1.1);
} }
@keyframes fadeIn { @keyframes fadeIn {

View File

@@ -1,39 +1,108 @@
/* --- UI Panels & Glassmorphism --- */ /* --- Panels --- */
.glass-panel { .glass-panel {
background: var(--glass-bg); background: color-mix(in srgb, var(--surface) 88%, transparent);
backdrop-filter: blur(12px); border: 1px solid var(--edge);
-webkit-backdrop-filter: blur(12px); border-radius: var(--radius-lg);
border: 1px solid var(--glass-border);
border-radius: 12px;
padding: 2rem; padding: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); box-shadow: var(--shadow-soft);
transition: var(--transition-smooth); transition: var(--transition-smooth);
} }
.glass-panel:hover { .glass-panel:hover {
border-color: rgba(212, 175, 55, 0.35); border-color: var(--edge-accent);
box-shadow: 0 8px 32px rgba(212, 175, 55, 0.05);
} }
.card { .card {
background: var(--bg-ocean-light); background: var(--surface);
border: 1px solid var(--glass-border); border: 1px solid var(--edge);
border-radius: 12px; border-radius: var(--radius-lg);
padding: 1.5rem; padding: 1.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.3); box-shadow: var(--shadow-soft);
transition: var(--transition-smooth);
} }
.card h3 { .card h3 {
font-family: var(--font-heading); color: var(--accent);
color: var(--gold); font-size: 1.35rem;
margin-bottom: 1rem; margin-bottom: 1rem;
border-bottom: 1px solid rgba(212, 175, 55, 0.2); border-bottom: 1px solid var(--edge-accent);
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
/* --- Forms & Controls --- */ /* Sunken sub-section inside a panel (character sheet groups, ledgers...) */
.sheet-group {
background: var(--well);
padding: 1rem;
border-radius: var(--radius-sm);
border-left: 2px solid var(--accent-dark);
}
.sheet-group h4 {
color: var(--accent);
font-size: 1.05rem;
margin-bottom: 0.5rem;
letter-spacing: 0.5px;
}
/* Notice banners (first-scene framing, over-limit warnings...) */
.notice-banner {
border: 1px dashed var(--edge);
border-radius: var(--radius-md);
padding: 1rem 1.5rem;
margin: 0 auto 1.5rem;
max-width: 700px;
text-align: left;
}
.notice-banner h4 {
margin-bottom: 0.5rem;
}
.notice-banner.accent {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 9%, transparent);
}
.notice-banner.accent h4 { color: var(--accent); }
.notice-banner.danger {
border-color: var(--danger);
background: color-mix(in srgb, var(--danger) 9%, transparent);
}
.notice-banner.danger h4 { color: var(--danger); }
/* Prompt boxes (character-sheet callouts: death, ghost, name...) */
.prompt-box {
border: 1px dashed var(--edge);
border-left: 2px solid var(--edge);
padding: 1rem;
border-radius: var(--radius-md);
margin-bottom: 1rem;
background: var(--well);
}
.prompt-box h4 {
margin-bottom: 0.5rem;
}
.prompt-box.accent {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.prompt-box.accent h4 { color: var(--accent); }
.prompt-box.danger {
border-color: var(--danger);
background: color-mix(in srgb, var(--danger) 9%, transparent);
}
.prompt-box.danger h4 { color: var(--danger); }
.prompt-box.ghostly {
border-color: var(--ghost);
background: color-mix(in srgb, var(--ghost) 10%, transparent);
}
.prompt-box.ghostly h4 { color: var(--ghost); }
/* --- Forms & controls --- */
.form-group { .form-group {
margin-bottom: 1.25rem; margin-bottom: 1.25rem;
text-align: left; text-align: left;
@@ -47,40 +116,35 @@
font-weight: 500; font-weight: 500;
} }
.input-field { .input-field, .form-control, .select-field, .copy-input {
width: 100%; width: 100%;
padding: 0.75rem 1rem; padding: 0.7rem 1rem;
background: rgba(7, 11, 18, 0.8); background: var(--well);
border: 1px solid var(--glass-border); border: 1px solid var(--edge);
border-radius: 8px; border-radius: var(--radius-sm);
color: var(--text-primary); color: var(--text);
font-family: var(--font-body); font-family: var(--font-body);
font-size: 1rem; font-size: 1rem;
transition: var(--transition-smooth); transition: var(--transition-smooth);
} }
.input-field:focus { .input-field::placeholder, .form-control::placeholder {
color: color-mix(in srgb, var(--text-muted) 70%, transparent);
}
.input-field:focus, .form-control:focus, .select-field:focus {
outline: none; outline: none;
border-color: var(--neon-cyan); border-color: var(--accent);
box-shadow: 0 0 10px rgba(0, 242, 254, 0.2); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
} }
.select-field { .select-field {
width: 100%;
padding: 0.75rem 1rem;
background: rgba(7, 11, 18, 0.8);
border: 1px solid var(--glass-border);
border-radius: 8px;
color: var(--text-primary);
font-family: var(--font-body);
font-size: 1rem;
transition: var(--transition-smooth);
cursor: pointer; cursor: pointer;
} }
.select-field:focus { .input-large {
outline: none; padding: 0.9rem 1.1rem;
border-color: var(--neon-cyan); font-size: 1.1rem;
} }
.input-row { .input-row {
@@ -92,96 +156,112 @@
flex: 1; flex: 1;
} }
.select-small { padding: 0.5rem; font-size: 0.9rem; flex: 1; }
.select-xsmall { padding: 0.4rem 0.5rem; font-size: 0.85rem; width: 140px; }
.checkbox-label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
font-size: 0.95rem;
}
.objectives-checklist {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
/* --- Buttons --- */ /* --- Buttons --- */
.btn { .btn {
display: inline-block; display: inline-block;
padding: 0.75rem 1.5rem; padding: 0.7rem 1.5rem;
font-family: var(--font-body); font-family: var(--font-heading);
font-weight: 700; font-weight: 700;
font-size: 1rem; font-size: 1rem;
text-transform: uppercase;
letter-spacing: 1px; letter-spacing: 1px;
border: none; border: 1px solid transparent;
border-radius: 8px; border-radius: var(--radius-sm);
cursor: pointer; cursor: pointer;
transition: var(--transition-smooth); transition: var(--transition-smooth);
text-decoration: none; text-decoration: none;
} }
.btn-primary { .btn-primary {
background: linear-gradient(135deg, var(--gold) 0%, var(--gold-dark) 100%); background: linear-gradient(160deg, var(--accent-bright) 0%, var(--accent) 45%, var(--accent-dark) 100%);
color: var(--text-dark); border-color: var(--accent-dark);
box-shadow: 0 4px 15px var(--gold-glow); color: var(--text-inverse);
box-shadow: var(--shadow-soft);
} }
.btn-primary:hover:not(:disabled) { .btn-primary:hover:not(:disabled) {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(212, 175, 55, 0.6); box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 35%, transparent);
} }
.btn-secondary { .btn-secondary {
background: rgba(255,255,255,0.08); background: color-mix(in srgb, var(--text) 8%, transparent);
border: 1px solid rgba(255,255,255,0.15); border-color: var(--edge);
color: var(--text-primary); color: var(--text);
} }
.btn-secondary:hover:not(:disabled) { .btn-secondary:hover:not(:disabled) {
background: rgba(255,255,255,0.15); background: color-mix(in srgb, var(--text) 15%, transparent);
transform: translateY(-2px); transform: translateY(-2px);
} }
.btn-deep { .btn-deep {
background: linear-gradient(135deg, #152238 0%, #0d1726 100%); background: color-mix(in srgb, var(--deep) 10%, var(--surface));
border: 1px solid var(--neon-cyan); border-color: var(--deep);
color: var(--neon-cyan); color: var(--deep);
box-shadow: 0 4px 15px rgba(0, 242, 254, 0.1);
} }
.btn-deep:hover:not(:disabled) { .btn-deep:hover:not(:disabled) {
background: var(--neon-cyan); background: var(--deep);
color: var(--text-dark); color: var(--text-inverse);
box-shadow: 0 4px 20px rgba(0, 242, 254, 0.4);
transform: translateY(-2px); transform: translateY(-2px);
} }
.btn-danger { .btn-danger {
background: linear-gradient(135deg, #7a1c1c 0%, #c0392b 100%); background: color-mix(in srgb, var(--danger) 18%, var(--surface));
color: var(--text-primary); border-color: var(--danger);
box-shadow: 0 4px 15px rgba(192, 57, 43, 0.2); color: color-mix(in srgb, var(--danger) 70%, var(--text));
} }
.btn-danger:hover:not(:disabled) { .btn-danger:hover:not(:disabled) {
background: var(--danger);
color: var(--text-inverse);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(192, 57, 43, 0.5); }
.btn-success {
background: color-mix(in srgb, var(--success) 18%, var(--surface));
border-color: var(--success);
color: color-mix(in srgb, var(--success) 70%, var(--text));
}
.btn-success:hover:not(:disabled) {
background: var(--success);
color: var(--text-inverse);
} }
.btn-gold { .btn-gold {
background: transparent; background: transparent;
border: 2px solid var(--gold); border: 2px solid var(--accent);
color: var(--gold); color: var(--accent);
box-shadow: 0 0 10px rgba(212,175,55,0.1);
} }
.btn-gold:hover:not(:disabled) { .btn-gold:hover:not(:disabled) {
background: var(--gold); background: var(--accent);
color: var(--bg-dark); color: var(--text-inverse);
box-shadow: 0 0 20px var(--gold-glow); box-shadow: 0 0 20px color-mix(in srgb, var(--accent) 40%, transparent);
transform: translateY(-2px); transform: translateY(-2px);
} }
.btn-full { .btn-full, .btn-block { width: 100%; }
width: 100%; .btn-small { padding: 0.4rem 0.8rem; font-size: 0.85rem; }
} .btn-large { padding: 0.9rem 2rem; font-size: 1.15rem; }
.btn-small {
padding: 0.4rem 0.8rem;
font-size: 0.8rem;
}
.btn-large {
padding: 1rem 2rem;
font-size: 1.1rem;
}
.btn:disabled { .btn:disabled {
opacity: 0.5; opacity: 0.5;
@@ -191,111 +271,183 @@
} }
.glow-effect:hover { .glow-effect:hover {
filter: brightness(1.2); filter: brightness(1.15);
} }
/* --- Event Log --- */ /* --- Alerts --- */
.event-log-fab { .alert {
position: fixed; padding: 0.75rem 1.25rem;
bottom: 20px; margin-bottom: 1.5rem;
right: 20px; border-radius: var(--radius-sm);
width: 60px; text-align: center;
height: 60px; font-weight: 500;
border-radius: 50%; }
background: var(--glass-bg);
backdrop-filter: blur(8px); .alert-danger {
-webkit-backdrop-filter: blur(8px); background: color-mix(in srgb, var(--danger) 14%, transparent);
border: 1px solid var(--gold); border: 1px solid var(--danger);
color: var(--gold); color: color-mix(in srgb, var(--danger) 75%, var(--text));
font-size: 1.5rem; }
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5), 0 0 10px rgba(212, 175, 55, 0.2);
/* --- Player chips --- */
.player-chips {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: center;
margin-top: 1rem;
}
.player-chip {
background: color-mix(in srgb, var(--text) 6%, transparent);
border: 1px solid var(--edge);
padding: 0.5rem 1.25rem;
border-radius: 30px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; gap: 0.5rem;
cursor: pointer;
z-index: 1000;
transition: var(--transition-smooth); transition: var(--transition-smooth);
} }
.event-log-fab:hover { .player-chip.current-user {
transform: scale(1.1); background: color-mix(in srgb, var(--accent) 14%, transparent);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6), 0 0 15px rgba(212, 175, 55, 0.4); border-color: var(--accent);
} }
.event-log-panel { .player-chip.voted-chip {
position: fixed; border-color: var(--success);
bottom: 90px; background-color: color-mix(in srgb, var(--success) 6%, transparent);
right: 20px;
width: 350px;
max-width: calc(100vw - 40px);
height: 400px;
max-height: calc(100vh - 120px);
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
display: flex;
flex-direction: column;
z-index: 999;
transition: transform 0.3s ease, opacity 0.3s ease;
} }
.event-log-panel.hidden { .player-chip.not-voted-chip {
transform: translateY(20px) scale(0.95); border-color: var(--edge);
opacity: 0;
pointer-events: none;
} }
.event-log-header { .list-chips {
display: flex; justify-content: flex-start;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
} }
.event-log-header h3 { .list-chips .player-chip {
margin: 0; padding: 0.4rem 1rem;
font-size: 0.9rem;
}
.deep-chip {
border-color: var(--deep);
background: color-mix(in srgb, var(--deep) 5%, transparent);
}
.pirat-chip {
border-color: var(--pirat);
background: color-mix(in srgb, var(--pirat) 5%, transparent);
}
.player-chip.is-ghost {
border-color: var(--ghost);
color: var(--ghost);
background: color-mix(in srgb, var(--ghost) 10%, transparent);
}
.player-chip.is-dead {
border-color: var(--danger);
color: var(--danger);
background: color-mix(in srgb, var(--danger) 10%, transparent);
}
.player-chip.is-captain {
border-color: var(--accent);
color: var(--accent);
background: color-mix(in srgb, var(--accent) 10%, transparent);
}
/* --- Badges --- */
.creator-badge {
background: var(--accent);
color: var(--text-inverse);
font-size: 0.7rem;
font-weight: 700;
padding: 0.1rem 0.4rem;
border-radius: 4px;
text-transform: uppercase;
}
.deep-badge, .pirat-badge, .ghost-badge {
padding: 0.1rem 0.45rem;
font-size: 0.75rem;
font-weight: 700;
border-radius: 4px;
margin-left: 0.35rem;
white-space: nowrap;
}
.deep-badge {
background: color-mix(in srgb, var(--deep) 14%, transparent);
border: 1px solid var(--deep);
color: var(--deep);
}
.pirat-badge {
background: color-mix(in srgb, var(--pirat) 14%, transparent);
border: 1px solid var(--pirat);
color: var(--pirat);
}
.ghost-badge {
background: color-mix(in srgb, var(--ghost) 14%, transparent);
border: 1px solid var(--ghost);
color: var(--ghost);
}
.captain-badge {
background: color-mix(in srgb, var(--accent) 14%, transparent);
border: 1px solid var(--accent);
color: var(--accent);
padding: 0.4rem 1rem;
border-radius: 20px;
font-size: 1rem;
font-weight: 700;
font-family: var(--font-heading); font-family: var(--font-heading);
font-size: 1.1rem; display: inline-flex;
color: var(--gold); align-items: center;
}
.event-log-container {
flex: 1;
overflow-y: auto;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
} }
.event-list { .captain-badge.vacant {
list-style: none; background: color-mix(in srgb, var(--text) 4%, transparent);
padding: 0; border: 1px dashed var(--edge);
margin: 0;
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.event-item {
font-size: 0.9rem;
background: rgba(0, 0, 0, 0.3);
padding: 0.75rem;
border-radius: 8px;
border-left: 3px solid var(--neon-cyan);
}
.event-time {
display: block;
font-size: 0.75rem;
color: var(--text-muted); color: var(--text-muted);
margin-bottom: 0.25rem;
} }
.event-msg { /* --- Waiting indicator --- */
color: var(--text-primary); .waiting-box, .waiting-indicator {
font-size: 0.95rem;
color: var(--accent);
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
}
/* --- Dev mode --- */
.dev-toggle {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 0.5rem;
}
.dev-banner {
background: var(--well);
border: 1px solid var(--accent);
border-radius: var(--radius-md);
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
margin: 0 auto 1.5rem;
max-width: 56rem;
} }

View File

@@ -1,237 +0,0 @@
/* --- Variables & Tokens --- */
:root {
--bg-dark: #070b12;
--bg-ocean: #0d1726;
--bg-ocean-light: #16243b;
--gold: #d4af37;
--gold-glow: rgba(212, 175, 55, 0.4);
--gold-dark: #aa841c;
--neon-cyan: #00f2fe;
--neon-emerald: #00ff87;
--red-suit: #ff2a5f;
--red-glow: rgba(255, 42, 95, 0.3);
--black-suit: #e2e8f0;
--black-glow: rgba(226, 232, 240, 0.2);
--glass-bg: rgba(13, 23, 38, 0.7);
--glass-bg-hover: rgba(22, 36, 59, 0.85);
--glass-border: rgba(212, 175, 55, 0.2);
--glass-border-focus: rgba(0, 242, 254, 0.4);
--text-primary: #f1f5f9;
--text-muted: #94a3b8;
--text-dark: #0f172a;
--font-heading: 'Cinzel', serif;
--font-body: 'Outfit', sans-serif;
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* --- Base Reset & Setup --- */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: radial-gradient(circle at 50% 50%, var(--bg-ocean) 0%, var(--bg-dark) 100%);
color: var(--text-primary);
font-family: var(--font-body);
font-size: 16px;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-dark);
}
::-webkit-scrollbar-thumb {
background: var(--gold-dark);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--gold);
}
/* --- Layout Components --- */
.app-header {
background: rgba(7, 11, 18, 0.95);
border-bottom: 2px solid var(--gold);
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
}
.logo-container {
display: flex;
align-items: center;
gap: 0.75rem;
}
.logo-container h1 {
font-family: var(--font-heading);
font-size: 1.8rem;
font-weight: 900;
letter-spacing: 2px;
background: linear-gradient(135deg, var(--gold) 30%, #fff 70%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 10px var(--gold-glow);
}
.math-magic {
font-family: var(--font-heading);
font-size: 2.2rem;
color: var(--neon-cyan);
text-shadow: 0 0 10px var(--neon-cyan), 0 0 20px var(--neon-cyan);
animation: pulse-glow 3s infinite ease-in-out;
}
.header-status {
display: flex;
align-items: center;
gap: 1rem;
}
.player-pill {
background: rgba(0, 242, 254, 0.1);
border: 1px solid var(--neon-cyan);
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 700;
display: flex;
align-items: center;
gap: 0.5rem;
}
.player-pill .bullet {
width: 8px;
height: 8px;
background-color: var(--neon-emerald);
border-radius: 50%;
box-shadow: 0 0 8px var(--neon-emerald);
}
.phase-pill {
background: rgba(212, 175, 55, 0.1);
border: 1px solid var(--gold);
color: var(--gold);
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 700;
letter-spacing: 1px;
}
.app-main {
flex: 1;
padding: 2rem;
max-width: 1400px;
width: 100%;
margin: 0 auto;
}
.app-footer {
background: var(--bg-dark);
padding: 1.5rem;
text-align: center;
border-top: 1px solid rgba(255,255,255,0.05);
font-size: 0.85rem;
color: var(--text-muted);
}
/* --- Loader / Spinner widgets --- */
.loader-container {
padding: 4rem;
}
.spinner {
width: 50px;
height: 50px;
border: 3px solid rgba(0, 242, 254, 0.1);
border-radius: 50%;
border-top-color: var(--neon-cyan);
animation: spin 1s ease-in-out infinite;
margin: 0 auto 1.5rem;
}
.spinner-small {
width: 20px;
height: 20px;
border: 2px solid rgba(255,255,255,0.1);
border-radius: 50%;
border-top-color: var(--gold);
animation: spin 1s ease-in-out infinite;
display: inline-block;
}
/* --- Alert styles --- */
.alert {
padding: 0.75rem 1.25rem;
margin-bottom: 1.5rem;
border-radius: 8px;
text-align: center;
font-weight: 500;
}
.alert-danger {
background: rgba(192, 57, 43, 0.2);
border: 1px solid #c0392b;
color: #e74c3c;
}
/* --- Animation keyframes --- */
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes pulse-glow {
0% {
text-shadow: 0 0 10px var(--neon-cyan), 0 0 20px var(--neon-cyan);
transform: scale(1);
}
50% {
text-shadow: 0 0 15px var(--neon-cyan), 0 0 30px var(--neon-cyan), 0 0 40px var(--neon-cyan);
transform: scale(1.05);
}
100% {
text-shadow: 0 0 10px var(--neon-cyan), 0 0 20px var(--neon-cyan);
transform: scale(1);
}
}
/* --- Utilities --- */
.text-center { text-align: center; }
.text-left { text-align: left; }
.margin-top { margin-top: 1.5rem; }
.gold-text { color: var(--gold); }
.center-block { margin: 1rem auto; max-width: 500px; }
.inline-form { display: flex; gap: 0.5rem; width: 100%; }
.inline-group { display: flex; gap: 0.5rem; width: 100%; }
.select-small { padding: 0.5rem; font-size: 0.9rem; flex: 1; }
.select-xsmall { padding: 0.4rem; font-size: 0.85rem; }
.btn-small { padding: 0.4rem 0.8rem; font-size: 0.85rem; }
/* --- Ghost World Styles --- */
.ghost-world {
filter: grayscale(95%) brightness(0.85) sepia(15%) contrast(1.05);
transition: filter 1.2s ease-in-out;
}

View File

@@ -1,64 +1,13 @@
/* --- Lobby / Hold View --- */ /* --- Lobby / Ship's Hold --- */
.lobby-view { .lobby-view {
max-width: 700px; max-width: 700px;
margin: 2rem auto; margin: 2rem auto;
} }
.lobby-view h2 {
font-family: var(--font-heading);
font-size: 2.2rem;
color: var(--gold);
margin-bottom: 0.5rem;
}
.lobby-status-box { .lobby-status-box {
margin: 2rem 0; margin: 2rem 0;
} }
.player-chips {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: center;
margin-top: 1rem;
}
.player-chip {
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.1);
padding: 0.5rem 1.25rem;
border-radius: 30px;
display: flex;
align-items: center;
gap: 0.5rem;
transition: var(--transition-smooth);
}
.player-chip.current-user {
background: rgba(0, 242, 254, 0.15);
border-color: var(--neon-cyan);
box-shadow: 0 0 10px rgba(0,242,254,0.15);
}
.player-chip.voted-chip {
border-color: var(--neon-emerald);
background-color: rgba(0,255,135,0.05);
}
.player-chip.not-voted-chip {
border-color: rgba(255,255,255,0.1);
}
.creator-badge {
background: var(--gold);
color: var(--text-dark);
font-size: 0.7rem;
font-weight: 700;
padding: 0.1rem 0.4rem;
border-radius: 4px;
text-transform: uppercase;
}
.links-box { .links-box {
text-align: left; text-align: left;
margin-bottom: 2rem; margin-bottom: 2rem;
@@ -69,7 +18,7 @@
} }
.link-item h4 { .link-item h4 {
color: var(--text-primary); color: var(--text);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
@@ -80,27 +29,17 @@
.copy-input { .copy-input {
flex: 1; flex: 1;
background: #05080e; background: var(--bg-deep);
border: 1px solid var(--glass-border);
color: var(--text-primary);
padding: 0.5rem 1rem;
border-radius: 8px;
font-family: var(--font-body);
font-size: 0.95rem; font-size: 0.95rem;
padding: 0.5rem 1rem;
} }
.copy-input.admin-input { .copy-input.admin-input {
border-color: var(--gold-dark); border-color: var(--edge-accent);
color: var(--gold); color: var(--accent);
} }
.admin-link-item { .admin-link-item {
border-top: 1px dashed rgba(212, 175, 55, 0.3); border-top: 1px dashed var(--edge-accent);
padding-top: 1.5rem; padding-top: 1.5rem;
} }
.info-text {
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
}

View File

@@ -1,4 +1,4 @@
/* --- Scene Play Board Layout --- */ /* --- Scene board layout --- */
.scene-view-layout { .scene-view-layout {
display: grid; display: grid;
grid-template-columns: 1.4fr 1fr; grid-template-columns: 1.4fr 1fr;
@@ -15,7 +15,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-bottom: 1px solid rgba(212, 175, 55, 0.2); border-bottom: 1px solid var(--edge-accent);
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@@ -28,11 +28,49 @@
.deck-counter { .deck-counter {
font-size: 0.85rem; font-size: 0.85rem;
color: var(--gold); color: var(--accent);
font-weight: 700; font-weight: 700;
} }
/* --- Active Obstacle Item Display --- */ /* Scene status banner (captain + roster) */
.scene-status-banner {
margin-bottom: 1.5rem;
padding: 1rem;
background: var(--well);
border: 1px solid var(--edge);
border-radius: var(--radius-md);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.roster-chips {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
align-items: center;
}
.roster-chips .roster-label {
font-size: 0.85rem;
color: var(--text-muted);
font-family: var(--font-heading);
margin-right: 0.25rem;
}
.roster-chips .player-chip {
font-size: 0.8rem;
padding: 0.25rem 0.6rem;
border-radius: 12px;
margin: 0;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
/* --- Obstacles --- */
.obstacles-container { .obstacles-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -40,10 +78,10 @@
} }
.obstacle-item { .obstacle-item {
border: 1px solid var(--glass-border); border: 1px solid var(--edge);
border-radius: 10px; border-radius: var(--radius-md);
padding: 1.25rem; padding: 1.25rem;
background: rgba(7, 11, 18, 0.4); background: var(--well);
display: grid; display: grid;
grid-template-columns: 0.8fr 2fr 1fr 1fr; grid-template-columns: 0.8fr 2fr 1fr 1fr;
gap: 1rem; gap: 1rem;
@@ -51,6 +89,10 @@
overflow: hidden; overflow: hidden;
} }
.obstacle-item.in-challenge {
box-shadow: 0 0 0 2px var(--danger);
}
@media (max-width: 768px) { @media (max-width: 768px) {
.obstacle-item { .obstacle-item {
grid-template-columns: 0.8fr 2fr 1.5fr; grid-template-columns: 0.8fr 2fr 1.5fr;
@@ -63,20 +105,14 @@
} }
} }
/* Color theme mappings for suits */ /* Suit accent stripes */
.suit-c { border-left: 4px solid var(--black-suit); } .suit-c, .suit-s { border-left: 4px solid var(--suit-black); }
.suit-s { border-left: 4px solid var(--black-suit); } .suit-h, .suit-d { border-left: 4px solid var(--suit-red); }
.suit-h { border-left: 4px solid var(--red-suit); }
.suit-d { border-left: 4px solid var(--red-suit); }
.obstacle-meta { .obstacle-card-wrapper {
display: flex; display: flex;
flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: rgba(0,0,0,0.3);
border-radius: 6px;
padding: 0.5rem;
} }
.suit-badge { .suit-badge {
@@ -84,8 +120,8 @@
font-size: 1.1rem; font-size: 1.1rem;
} }
.suit-c .suit-badge, .suit-s .suit-badge { color: var(--black-suit); } .suit-c .suit-badge, .suit-s .suit-badge { color: var(--suit-black); }
.suit-h .suit-badge, .suit-d .suit-badge { color: var(--red-suit); } .suit-h .suit-badge, .suit-d .suit-badge { color: var(--suit-red); }
.card-code { .card-code {
font-size: 0.8rem; font-size: 0.8rem;
@@ -94,10 +130,14 @@
} }
.obstacle-details h4 { .obstacle-details h4 {
font-family: var(--font-heading); color: var(--text);
color: var(--text-primary);
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
font-size: 1.1rem; font-size: 1.15rem;
}
.obstacle-details .in-challenge-tag {
color: var(--danger);
font-size: 0.8rem;
} }
.obstacle-details .desc { .obstacle-details .desc {
@@ -110,9 +150,9 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: rgba(212, 175, 55, 0.04); background: color-mix(in srgb, var(--accent) 5%, transparent);
border: 1px dashed var(--glass-border); border: 1px dashed var(--edge-accent);
border-radius: 6px; border-radius: var(--radius-sm);
padding: 0.5rem; padding: 0.5rem;
} }
@@ -121,14 +161,15 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: rgba(0, 255, 135, 0.04); background: color-mix(in srgb, var(--success) 5%, transparent);
border: 1px dashed rgba(0, 255, 135, 0.25); border: 1px dashed color-mix(in srgb, var(--success) 30%, transparent);
border-radius: 6px; border-radius: var(--radius-sm);
padding: 0.5rem; padding: 0.5rem;
} }
.obstacle-successes-display .val-number { .obstacle-successes-display .val-number {
color: var(--neon-emerald); color: var(--success);
font-size: 1.5rem;
} }
.val-label { .val-label {
@@ -141,22 +182,16 @@
font-size: 1.8rem; font-size: 1.8rem;
font-family: var(--font-heading); font-family: var(--font-heading);
font-weight: 900; font-weight: 900;
color: var(--gold); color: var(--accent);
} }
.played-column { .played-column {
grid-column: span 4; grid-column: 1 / -1;
border-top: 1px solid rgba(255,255,255,0.05); border-top: 1px solid var(--edge-soft);
padding-top: 0.75rem; padding-top: 0.75rem;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
@media (max-width: 600px) {
.played-column {
grid-column: span 1;
}
}
.played-column h5 { .played-column h5 {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text-muted); color: var(--text-muted);
@@ -170,82 +205,64 @@
gap: 0.75rem; gap: 0.75rem;
} }
/* --- Challenges Section --- */ .clear-obstacle-row {
.challenges-container { grid-column: 1 / -1;
display: flex;
flex-direction: column;
gap: 1.25rem;
} }
/* --- Challenges --- */
.challenge-item { .challenge-item {
background: rgba(255,255,255,0.02); background: color-mix(in srgb, var(--danger) 5%, transparent);
border: 1px solid var(--glass-border); border: 1px solid var(--danger);
border-radius: 8px; border-radius: var(--radius-md);
padding: 1.25rem; padding: 1rem 1.25rem;
margin-bottom: 1rem;
text-align: left; text-align: left;
} }
.challenge-item h4 { .challenge-item h4 {
font-family: var(--font-heading); color: var(--danger);
color: var(--neon-cyan); font-size: 1.15rem;
font-size: 1.2rem; margin: 0;
margin-bottom: 0.25rem;
} }
.challenge-item .desc { .challenge-head {
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 1rem;
}
.applied-obstacles h5 {
font-size: 0.85rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
}
.applied-obs-row {
padding: 0.75rem 1rem;
margin-bottom: 0.5rem;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.75rem;
}
.obs-info {
display: flex;
align-items: center;
gap: 0.5rem; gap: 0.5rem;
} }
.obs-info .symbol { .challenge-actions {
font-size: 1.2rem;
}
.play-card-form {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
flex-wrap: wrap;
align-items: center; align-items: center;
} }
.select-xsmall { .tax-callout {
padding: 0.25rem 0.5rem; margin-top: 0.75rem;
font-size: 0.8rem; padding: 0.75rem;
width: 140px; border: 1px dashed var(--accent);
background: var(--bg-dark); border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--accent) 6%, transparent);
} }
/* --- Deep Control panel --- */ .tax-callout p {
.deep-text { margin: 0 0 0.5rem 0;
color: var(--neon-cyan) !important; }
/* --- Deep control panel --- */
.deep-panel-card {
max-height: 80vh;
overflow-y: auto;
border-color: color-mix(in srgb, var(--deep) 40%, transparent);
} }
.deep-divider { .deep-divider {
border: 0; border: 0;
height: 1px; height: 1px;
background: linear-gradient(to right, rgba(0, 242, 254, 0), rgba(0, 242, 254, 0.4), rgba(0, 242, 254, 0)); background: linear-gradient(to right, transparent, color-mix(in srgb, var(--deep) 40%, transparent), transparent);
margin: 2rem 0; margin: 2rem 0;
} }
@@ -253,22 +270,28 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
background: rgba(0,0,0,0.2); background: var(--well);
padding: 1rem; padding: 1rem;
border-radius: 8px; border-radius: var(--radius-sm);
max-height: 150px; max-height: 150px;
overflow-y: auto; overflow-y: auto;
border: 1px solid var(--glass-border); border: 1px solid var(--edge);
} }
.checkbox-label { .objective-player-block {
display: flex; background: var(--well);
align-items: center; padding: 0.5rem 0.75rem;
gap: 0.5rem; border-radius: var(--radius-sm);
cursor: pointer; margin-bottom: 0.5rem;
font-size: 0.95rem;
} }
.scene-upkeep-controls {
margin-top: 1rem;
border-top: 1px solid var(--edge-soft);
padding-top: 1rem;
}
/* --- Hand --- */
.hand-flex { .hand-flex {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@@ -1,4 +1,4 @@
/* --- Scene Setup / Role Choose --- */ /* --- Scene setup / role choice --- */
.setup-grid { .setup-grid {
display: grid; display: grid;
grid-template-columns: 1.2fr 1fr; grid-template-columns: 1.2fr 1fr;
@@ -22,62 +22,60 @@
.role-btn { .role-btn {
padding: 1.5rem; padding: 1.5rem;
font-size: 1.2rem; font-size: 1.2rem;
background: rgba(255,255,255,0.03); background: var(--well);
border: 2px solid rgba(255,255,255,0.1); border: 2px solid var(--edge);
color: var(--text-muted); color: var(--text-muted);
} }
.role-btn.pirat-btn:hover, .role-btn.pirat-btn.active { .role-btn.pirat-btn:hover:not(:disabled), .role-btn.pirat-btn.active {
border-color: var(--neon-emerald); border-color: var(--pirat);
color: var(--text-primary); color: var(--text);
background-color: rgba(0,255,135,0.06); background-color: color-mix(in srgb, var(--pirat) 8%, transparent);
box-shadow: 0 0 15px rgba(0,255,135,0.15);
} }
.role-btn.deep-btn:hover, .role-btn.deep-btn.active { .role-btn.deep-btn:hover:not(:disabled), .role-btn.deep-btn.active {
border-color: var(--neon-cyan); border-color: var(--deep);
color: var(--text-primary); color: var(--text);
background-color: rgba(0,242,254,0.06); background-color: color-mix(in srgb, var(--deep) 8%, transparent);
box-shadow: 0 0 15px rgba(0,242,254,0.15);
} }
.large-badge { .role-badge {
padding: 0.5rem 2rem; padding: 0.15rem 0.75rem;
font-size: 1.5rem; border-radius: var(--radius-sm);
border-radius: 8px;
font-family: var(--font-heading); font-family: var(--font-heading);
margin-top: 0.5rem;
}
.role-badge.deep {
background: rgba(0, 242, 254, 0.15);
border: 2px solid var(--neon-cyan);
color: var(--neon-cyan);
box-shadow: 0 0 15px rgba(0,242,254,0.1);
}
.role-badge.pirat {
background: rgba(0, 255, 135, 0.15);
border: 2px solid var(--neon-emerald);
color: var(--neon-emerald);
box-shadow: 0 0 15px rgba(0,255,135,0.1);
}
.list-chips {
justify-content: flex-start;
}
.list-chips .player-chip {
padding: 0.4rem 1rem;
font-size: 0.9rem; font-size: 0.9rem;
} }
.deep-chip { .role-badge.deep {
border-color: var(--neon-cyan); background: color-mix(in srgb, var(--deep) 14%, transparent);
background: rgba(0, 242, 254, 0.03); border: 1px solid var(--deep);
color: var(--deep);
} }
.pirat-chip { .role-badge.pirat {
border-color: var(--neon-emerald); background: color-mix(in srgb, var(--pirat) 14%, transparent);
background: rgba(0, 255, 135, 0.03); border: 1px solid var(--pirat);
color: var(--pirat);
}
.role-badge.unassigned {
background: var(--well);
border: 1px dashed var(--edge);
color: var(--text-muted);
}
.roster-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.roster-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
background: var(--well);
border-radius: var(--radius-sm);
padding: 0.6rem 0.9rem;
} }

View File

@@ -1,18 +0,0 @@
/* ==========================================================================
RATS WITH GATS DESIGN SYSTEM & STYLESHEET
A weathered math-magic pirate aesthetic: deep-ocean dark mode, glassmorphism,
neon math runes, gold accents, and animated card widgets.
This file imports modular stylesheets divided by page phase and components.
========================================================================== */
@import url("core.css");
@import url("components.css");
@import url("card.css");
@import url("welcome.css");
@import url("lobby.css");
@import url("character.css");
@import url("scene-setup.css");
@import url("scene-play.css");
@import url("upkeep.css");
@import url("admin.css");

View File

@@ -0,0 +1,81 @@
/* ============================================================
RATS WITH GATS — THEME TOKENS
"Lantern & Brine": a lantern-lit pirate ship at night.
This file is the ONLY place colors, fonts, radii, and shadows
are defined. Every other stylesheet derives translucent
variants with color-mix(), so re-theming the app means
editing this file and nothing else.
(Font files are bundled via @fontsource imports in main.js.)
============================================================ */
:root {
/* --- Raw palette ---------------------------------------- */
--ink-deep: #0a0d13; /* darkest: page edges, sunken wells */
--ink: #131822; /* page background */
--hull: #1b212e; /* panel surface */
--hull-light: #262e3f; /* raised surface */
--parchment: #efe7d3; /* primary text */
--driftwood: #a89e8a; /* muted text */
--brass: #d9a23c; /* primary accent */
--brass-bright: #f3c365;
--brass-dark: #93701f;
--tide: #41c9bd; /* the Deep */
--kelp: #6cc983; /* Pi-Rats, success */
--coral: #e8606e; /* danger, red suits */
--bone: #dfe6ef; /* black suits */
--ember: #f0954c; /* warnings, jokers */
--mystic: #a08cd8; /* votes, objectives, misc arcana */
--ghost: #8fa3b8; /* dead & ghostly things */
/* Playing-card faces (light parchment cards on a dark table) */
--card-face: #f2e9d2;
--card-face-shade: #ddcfa9;
--card-ink-red: #b03546;
--card-ink-black: #2a3040;
/* --- Semantic assignments -------------------------------- */
--bg: var(--ink);
--bg-deep: var(--ink-deep);
--surface: var(--hull);
--surface-raised: var(--hull-light);
--well: color-mix(in srgb, var(--ink-deep) 55%, transparent);
--text: var(--parchment);
--text-muted: var(--driftwood);
--text-inverse: #1c1508;
--accent: var(--brass);
--accent-bright: var(--brass-bright);
--accent-dark: var(--brass-dark);
--deep: var(--tide);
--pirat: var(--kelp);
--success: var(--kelp);
--danger: var(--coral);
--warning: var(--ember);
--suit-red: var(--coral);
--suit-black: var(--bone);
/* Borders */
--edge: color-mix(in srgb, var(--parchment) 14%, transparent);
--edge-soft: color-mix(in srgb, var(--parchment) 8%, transparent);
--edge-accent: color-mix(in srgb, var(--accent) 35%, transparent);
/* --- Type ------------------------------------------------ */
--font-display: 'Pirata One', 'Alegreya SC', serif;
--font-heading: 'Alegreya SC', serif;
--font-body: 'Alegreya Sans', sans-serif;
/* --- Shape & motion -------------------------------------- */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--shadow-soft: 0 4px 14px rgba(0, 0, 0, 0.35);
--shadow-deep: 0 10px 30px rgba(0, 0, 0, 0.5);
--transition-smooth: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

View File

@@ -1,4 +1,4 @@
/* --- Between Scenes Upkeep --- */ /* --- Between-scenes upkeep --- */
.between-grid { .between-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@@ -19,12 +19,7 @@
.voted-confirmation-box { .voted-confirmation-box {
padding: 1.5rem; padding: 1.5rem;
text-align: center; text-align: center;
border: 1px solid var(--neon-emerald); border: 1px solid var(--success);
}
.success-text {
color: var(--neon-emerald);
font-weight: 700;
} }
.ranks-ledger { .ranks-ledger {
@@ -33,30 +28,66 @@
.ranks-ledger li { .ranks-ledger li {
padding: 0.5rem; padding: 0.5rem;
border-bottom: 1px solid rgba(255,255,255,0.05); border-bottom: 1px solid var(--edge-soft);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.deep-badge { /* --- Death fate panel --- */
background: rgba(0, 242, 254, 0.15); .death-fate-card {
border: 1px solid var(--neon-cyan); border: 2px solid var(--danger);
color: var(--neon-cyan); background: color-mix(in srgb, var(--danger) 5%, var(--surface));
padding: 0.1rem 0.4rem; padding: 2.5rem;
font-size: 0.75rem; margin: 2rem auto;
border-radius: 4px; max-width: 650px;
text-align: center;
} }
.pirat-badge { .death-fate-card > h2 {
background: rgba(0, 255, 135, 0.15); color: var(--danger);
border: 1px solid var(--neon-emerald); font-size: 2rem;
color: var(--neon-emerald); margin-bottom: 1rem;
padding: 0.1rem 0.4rem;
font-size: 0.75rem;
border-radius: 4px;
} }
/* --- Deep Upkeep Hand Refresh Drag & Drop --- */ .death-fate-card .description {
font-size: 1.15rem;
color: var(--text);
margin-bottom: 2rem;
}
.fate-choices {
display: flex;
gap: 1.5rem;
justify-content: center;
flex-wrap: wrap;
}
.creation-form-wrapper {
text-align: left;
background: var(--well);
padding: 1.5rem;
border-radius: var(--radius-md);
border: 1px solid var(--edge-soft);
}
.creation-form-wrapper h3 {
color: var(--accent);
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--edge-accent);
padding-bottom: 0.5rem;
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.form-actions .btn {
flex: 1;
}
/* --- Deep hand refresh drag & drop --- */
.upkeep-drag-container { .upkeep-drag-container {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@@ -76,9 +107,9 @@
flex-direction: column; flex-direction: column;
} }
.upkeep-box h3 { .upkeep-box h4 {
margin-bottom: 1rem; margin-bottom: 1rem;
font-family: var(--font-heading); font-size: 1.1rem;
} }
.upkeep-flex { .upkeep-flex {
@@ -87,14 +118,16 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 1rem; gap: 1rem;
padding: 1.5rem; padding: 1.5rem;
background: rgba(0, 0, 0, 0.3); background: var(--well);
border: 1px dashed var(--glass-border); border: 1px dashed var(--edge);
border-radius: 8px; border-radius: var(--radius-md);
align-content: flex-start; align-content: flex-start;
min-height: 300px; min-height: 300px;
transition: var(--transition-smooth); transition: var(--transition-smooth);
} }
.upkeep-box[ondragover]:hover .upkeep-flex { .end-story-box {
border-color: var(--neon-cyan); margin-top: 1.5rem;
border-top: 1px solid var(--edge-soft);
padding-top: 1rem;
} }

View File

@@ -1,50 +1,89 @@
/* --- Welcome / Main Menu Screen --- */ /* --- Welcome / splash & join screens --- */
.welcome-container { .welcome-container {
max-width: 900px; max-width: 560px;
margin: 4rem auto; margin: 0 auto;
padding: 4rem 1.5rem 3rem;
text-align: center;
} }
.welcome-hero h2 { .welcome-hero {
margin-bottom: 2.5rem;
}
.hero-overline {
font-family: var(--font-heading); font-family: var(--font-heading);
font-size: 2.5rem; font-size: 0.95rem;
color: var(--text-primary); letter-spacing: 4px;
margin-bottom: 1rem; text-transform: uppercase;
}
.welcome-hero .subtitle {
font-size: 1.2rem;
color: var(--text-muted); color: var(--text-muted);
margin-bottom: 3rem; margin-bottom: 0.5rem;
} }
.welcome-actions { .wordmark {
display: grid; font-family: var(--font-display);
grid-template-columns: 1fr 1fr; font-weight: 400;
gap: 2rem; font-size: clamp(3.2rem, 10vw, 5rem);
} line-height: 1;
letter-spacing: 2px;
@media (max-width: 768px) { background: linear-gradient(175deg, var(--accent-bright) 20%, var(--accent) 55%, var(--accent-dark) 100%);
.welcome-actions { -webkit-background-clip: text;
grid-template-columns: 1fr; background-clip: text;
} -webkit-text-fill-color: transparent;
} filter: drop-shadow(0 3px 12px color-mix(in srgb, var(--accent) 30%, transparent));
.action-card {
padding: 2.5rem 1.5rem;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 300px;
}
.action-card h3 {
font-family: var(--font-heading);
color: var(--gold);
font-size: 1.5rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
.action-card p { .wordmark .amp {
color: var(--text-muted); font-size: 0.45em;
margin-bottom: 2rem; vertical-align: 0.45em;
letter-spacing: 1px;
}
.welcome-hero .subtitle {
font-size: 1.15rem;
color: var(--text-muted);
max-width: 420px;
margin: 0 auto;
}
/* Anchor divider: ——— ⚓ ——— */
.hero-flourish {
display: flex;
align-items: center;
gap: 1rem;
color: var(--accent);
font-size: 1.2rem;
margin: 1.75rem auto 0;
max-width: 320px;
}
.hero-flourish::before,
.hero-flourish::after {
content: '';
flex: 1;
height: 1px;
background: linear-gradient(to right, transparent, var(--edge-accent), transparent);
}
.creation-card {
text-align: left;
}
.creation-card h2 {
color: var(--accent);
font-size: 1.5rem;
margin-bottom: 1.25rem;
text-align: center;
}
.join-hint {
font-size: 0.85rem;
color: var(--text-muted);
text-align: center;
margin-top: 1.25rem;
margin-bottom: 0;
}
.welcome-links {
margin-top: 1.75rem;
} }

View File

@@ -171,15 +171,15 @@
<div class="view-header text-center"> <div class="view-header text-center">
<h2>Character Sheet Creation</h2> <h2>Character Sheet Creation</h2>
<p>Fill in your Rat Records, delegate questions to your crew, and prepare your Math Magic Techniques.</p> <p>Fill in your Rat Records, delegate questions to your crew, and prepare your Math Magic Techniques.</p>
<div class="flex justify-center items-center gap-2 text-sm text-gray-500 mt-2"> <div class="dev-toggle">
<input type="checkbox" id="devModeToggle" bind:checked={devMode} class="accent-gold"/> <input type="checkbox" id="devModeToggle" bind:checked={devMode}/>
<label for="devModeToggle">Dev Mode</label> <label for="devModeToggle">Dev Mode</label>
</div> </div>
</div> </div>
{#if devMode} {#if devMode}
<div class="bg-dark-900 border border-gold p-4 rounded-lg flex justify-between items-center mb-6 max-w-4xl mx-auto"> <div class="dev-banner">
<span class="text-gold font-bold">🛠 Dev Mode Tools</span> <span class="text-accent font-bold">🛠 Dev Mode Tools</span>
<button <button
class="btn btn-primary" class="btn btn-primary"
on:click={autoFillAll} on:click={autoFillAll}
@@ -262,7 +262,7 @@
<div class="author-label"> {getPlayerName(state.player.other_hate_from_player_id)}</div> <div class="author-label"> {getPlayerName(state.player.other_hate_from_player_id)}</div>
</div> </div>
{:else} {:else}
<p class="mb-4 text-sm text-gray-400">Assigning crewmates to write your relationships...</p> <p class="mb-4 text-sm text-muted">Assigning crewmates to write your relationships...</p>
{/if} {/if}
</div> </div>
</div> </div>
@@ -295,7 +295,7 @@
{/if} {/if}
{/each} {/each}
{#if state.players.filter(p => (p.other_like_from_player_id === state.player.id && !p.other_like) || (p.other_hate_from_player_id === state.player.id && !p.other_hate)).length === 0} {#if state.players.filter(p => (p.other_like_from_player_id === state.player.id && !p.other_like) || (p.other_hate_from_player_id === state.player.id && !p.other_hate)).length === 0}
<p class="inbox-empty-message text-gray-400 italic">No pending tasks! Wait for other players to assign you tasks.</p> <p class="inbox-empty-message text-muted italic">No pending tasks! Wait for other players to assign you tasks.</p>
{/if} {/if}
</div> </div>
</div> </div>
@@ -346,7 +346,7 @@
{:else if !techniquesComplete} {:else if !techniquesComplete}
{#if swappedTechniques.length === 3} {#if swappedTechniques.length === 3}
<div class="max-w-4xl mx-auto space-y-6"> <div class="max-w-4xl mx-auto space-y-6">
<p class="text-gray-300">Drag and drop your assigned techniques onto your Face Cards:</p> <p>Drag and drop your assigned techniques onto your Face Cards:</p>
{#if faceError} {#if faceError}
<div class="alert alert-danger">{faceError}</div> <div class="alert alert-danger">{faceError}</div>
@@ -488,7 +488,7 @@
</div> </div>
</div> </div>
{:else} {:else}
<div class="text-center italic text-gray-400"> <div class="text-center italic text-muted">
Waiting for all players to submit techniques so they can be shuffled... Waiting for all players to submit techniques so they can be shuffled...
</div> </div>
{/if} {/if}
@@ -512,15 +512,15 @@
<div class="crew-status-list"> <div class="crew-status-list">
<ul class="space-y-2 mt-4"> <ul class="space-y-2 mt-4">
{#each state.players as p} {#each state.players as p}
<li class="p-3 bg-dark-800 rounded flex justify-between items-center border-l-4 {p.tech_jack ? 'border-green-500' : 'border-gray-500'}"> <li class="status-row {p.tech_jack ? 'is-ready' : ''}">
<span>{displayName(p)}</span> <span>{displayName(p)}</span>
<span class="text-sm"> <span class="text-sm">
{#if p.tech_jack} {#if p.tech_jack}
<span class="text-green-400">Ready</span> <span class="text-success">Ready</span>
{:else if p.created_techniques && JSON.parse(p.created_techniques).length === 3} {:else if p.created_techniques && JSON.parse(p.created_techniques).length === 3}
<span class="text-yellow-400">Assigning Face Cards</span> <span class="text-warning">Assigning Face Cards</span>
{:else} {:else}
<span class="text-gray-400">Writing Sheet</span> <span class="text-muted">Writing Sheet</span>
{/if} {/if}
</span> </span>
</li> </li>

View File

@@ -48,29 +48,29 @@
.menu-toggle { .menu-toggle {
padding: 6px 14px; padding: 6px 14px;
border-radius: 16px; border-radius: 16px;
background: rgba(10, 15, 25, 0.9); background: color-mix(in srgb, var(--bg-deep) 92%, transparent);
border: 1px solid var(--glass-border-focus); border: 1px solid var(--edge-accent);
color: var(--neon-cyan); color: var(--accent);
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 700; font-weight: 700;
font-family: var(--font-heading); font-family: var(--font-heading);
cursor: pointer; cursor: pointer;
} }
.menu-toggle:hover, .menu-toggle.open { .menu-toggle:hover, .menu-toggle.open {
background: rgba(0, 242, 254, 0.1); background: color-mix(in srgb, var(--accent) 12%, var(--bg-deep));
} }
.menu-dropdown { .menu-dropdown {
margin-top: 6px; margin-top: 6px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: rgba(10, 15, 25, 0.95); background: color-mix(in srgb, var(--bg-deep) 96%, transparent);
border: 1px solid var(--glass-border-focus); border: 1px solid var(--edge-accent);
border-radius: 12px; border-radius: var(--radius-md);
overflow: hidden; overflow: hidden;
} }
.menu-dropdown a { .menu-dropdown a {
padding: 8px 16px; padding: 8px 16px;
color: var(--neon-cyan); color: var(--accent);
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 700; font-weight: 700;
text-decoration: none; text-decoration: none;
@@ -79,9 +79,9 @@
white-space: nowrap; white-space: nowrap;
} }
.menu-dropdown a:hover { .menu-dropdown a:hover {
background: rgba(0, 242, 254, 0.1); background: color-mix(in srgb, var(--accent) 12%, transparent);
} }
.menu-dropdown a + a { .menu-dropdown a + a {
border-top: 1px solid rgba(0, 242, 254, 0.15); border-top: 1px solid var(--edge-soft);
} }
</style> </style>

View File

@@ -150,10 +150,10 @@
bottom: 20px; bottom: 20px;
right: 20px; right: 20px;
width: 380px; width: 380px;
background: rgba(10, 15, 25, 0.95); background: color-mix(in srgb, var(--bg-deep) 95%, transparent);
border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.1)); border: 1px solid var(--edge);
border-radius: 8px; border-radius: var(--radius-md);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); box-shadow: var(--shadow-deep);
z-index: 1000; z-index: 1000;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -164,19 +164,19 @@
width: auto; width: auto;
} }
.toggle-log-btn { .toggle-log-btn {
background: var(--dark-bg, #1a1a2e); background: transparent;
color: white; color: var(--text);
border: none; border: none;
padding: 10px; padding: 10px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
font-weight: bold; font-weight: bold;
border-radius: 8px; border-radius: var(--radius-md);
border-bottom: 1px solid rgba(255, 255, 255, 0.1); border-bottom: 1px solid var(--edge-soft);
font-family: var(--font-heading); font-family: var(--font-heading);
} }
.toggle-log-btn:hover { .toggle-log-btn:hover {
background: rgba(255, 255, 255, 0.1); background: color-mix(in srgb, var(--text) 10%, transparent);
} }
.log-content { .log-content {
overflow-y: auto; overflow-y: auto;
@@ -193,21 +193,21 @@
} }
.log-top-note { .log-top-note {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-muted, #94a3b8); color: var(--text-muted);
font-style: italic; font-style: italic;
} }
.load-older-btn { .load-older-btn {
background: rgba(255, 255, 255, 0.06); background: color-mix(in srgb, var(--text) 6%, transparent);
border: 1px solid rgba(255, 255, 255, 0.12); border: 1px solid var(--edge);
color: var(--text-muted, #94a3b8); color: var(--text-muted);
font-size: 0.75rem; font-size: 0.75rem;
padding: 4px 12px; padding: 4px 12px;
border-radius: 12px; border-radius: 12px;
cursor: pointer; cursor: pointer;
} }
.load-older-btn:hover { .load-older-btn:hover {
background: rgba(255, 255, 255, 0.12); background: color-mix(in srgb, var(--text) 12%, transparent);
color: var(--text-primary, #f1f5f9); color: var(--text);
} }
.log-entry { .log-entry {
display: grid; display: grid;
@@ -215,12 +215,12 @@
gap: 8px; gap: 8px;
align-items: baseline; align-items: baseline;
padding: 6px 8px; padding: 6px 8px;
background: rgba(255, 255, 255, 0.03); background: color-mix(in srgb, var(--text) 3%, transparent);
border-left: 3px solid rgba(255, 255, 255, 0.15); border-left: 3px solid var(--edge);
border-radius: 0 6px 6px 0; border-radius: 0 6px 6px 0;
font-size: 0.85rem; font-size: 0.85rem;
line-height: 1.35; line-height: 1.35;
color: #c3cede; color: color-mix(in srgb, var(--text) 85%, var(--text-muted));
} }
.log-icon { .log-icon {
font-size: 0.9rem; font-size: 0.9rem;
@@ -230,13 +230,13 @@
} }
.log-time { .log-time {
font-size: 0.7rem; font-size: 0.7rem;
color: var(--text-muted, #94a3b8); color: var(--text-muted);
white-space: nowrap; white-space: nowrap;
opacity: 0.8; opacity: 0.8;
} }
.log-empty { .log-empty {
padding: 10px; padding: 10px;
color: var(--text-muted, #94a3b8); color: var(--text-muted);
font-style: italic; font-style: italic;
} }
.jump-to-bottom { .jump-to-bottom {
@@ -244,37 +244,37 @@
bottom: 12px; bottom: 12px;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
background: var(--gold, #d4af37); background: var(--accent);
color: var(--text-dark, #0f172a); color: var(--text-inverse);
border: none; border: none;
border-radius: 16px; border-radius: 16px;
padding: 5px 14px; padding: 5px 14px;
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); box-shadow: var(--shadow-soft);
} }
.jump-to-bottom:hover { .jump-to-bottom:hover {
filter: brightness(1.1); filter: brightness(1.1);
} }
/* Accent colors by event kind */ /* Accent colors by event kind */
.log-kind-challenge { border-left-color: var(--red-suit, #ff2a5f); } .log-kind-challenge { border-left-color: var(--danger); }
.log-kind-card { border-left-color: var(--neon-cyan, #00f2fe); } .log-kind-card { border-left-color: var(--deep); }
.log-kind-captain, .log-kind-captain,
.log-kind-tax, .log-kind-tax,
.log-kind-rank, .log-kind-rank,
.log-kind-victory { border-left-color: var(--gold, #d4af37); } .log-kind-victory { border-left-color: var(--accent); }
.log-kind-scene, .log-kind-scene,
.log-kind-phase { border-left-color: var(--neon-emerald, #00ff87); } .log-kind-phase { border-left-color: var(--pirat); }
.log-kind-joker, .log-kind-joker,
.log-kind-warning { border-left-color: #ff9f43; } .log-kind-warning { border-left-color: var(--warning); }
.log-kind-obstacle { border-left-color: #4aa3df; } .log-kind-obstacle { border-left-color: var(--deep); }
.log-kind-join, .log-kind-join,
.log-kind-vote, .log-kind-vote,
.log-kind-objective { border-left-color: #9b8cff; } .log-kind-objective { border-left-color: var(--mystic); }
.log-kind-victory { .log-kind-victory {
background: rgba(212, 175, 55, 0.08); background: color-mix(in srgb, var(--accent) 9%, transparent);
color: var(--gold, #d4af37); color: var(--accent);
} }
</style> </style>

View File

@@ -7,16 +7,16 @@
$: crewDone = state.game.completed_crew_1 && state.game.completed_crew_2 && state.game.completed_crew_3; $: crewDone = state.game.completed_crew_1 && state.game.completed_crew_2 && state.game.completed_crew_3;
</script> </script>
<div class="dashboard-container" style="max-width: 800px; margin: 0 auto; padding: 2rem;"> <div class="gameover-container">
<div class="card glass-panel" style="text-align: center; padding: 2rem;"> <div class="card glass-panel gameover-card">
<h1 style="font-family: var(--font-heading); color: var(--gold);">🎉 The Story Has Ended!</h1> <h1>🎉 The Story Has Ended!</h1>
<p class="section-desc" style="font-size: 1.1rem;"> <p class="section-desc" style="font-size: 1.1rem;">
The Pi-Rats of {state.game.crew_name || 'the crew'} party til they pass out in a pile. The Pi-Rats of {state.game.crew_name || 'the crew'} party til they pass out in a pile.
Tell one last tale of how your Pi-Rat is remembered! Tell one last tale of how your Pi-Rat is remembered!
</p> </p>
<div class="sheet-group" style="text-align: left; margin-top: 2rem;"> <div class="sheet-group">
<h3 style="color: var(--gold);">Crew Objectives</h3> <h3 class="text-accent">Crew Objectives</h3>
<div class="objectives-checklist"> <div class="objectives-checklist">
<label class="checkbox-label"><input type="checkbox" checked={state.game.completed_crew_1} disabled> <span>Steal a Ship</span></label> <label class="checkbox-label"><input type="checkbox" checked={state.game.completed_crew_1} disabled> <span>Steal a Ship</span></label>
<label class="checkbox-label"><input type="checkbox" checked={state.game.completed_crew_2} disabled> <span>Choose a Captain</span></label> <label class="checkbox-label"><input type="checkbox" checked={state.game.completed_crew_2} disabled> <span>Choose a Captain</span></label>
@@ -29,16 +29,16 @@
{/if} {/if}
</div> </div>
<div class="sheet-group" style="text-align: left; margin-top: 1.5rem;"> <div class="sheet-group">
<h3 style="color: var(--gold);">The Crew</h3> <h3 class="text-accent">The Crew</h3>
{#each pirats as p} {#each pirats as p}
<div style="background: rgba(0,0,0,0.2); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;"> <div class="crew-epitaph-row">
<span> <span>
{#if p.is_ghost}👻{:else if p.is_dead}💀{:else}🐀{/if} {#if p.is_ghost}👻{:else if p.is_dead}💀{:else}🐀{/if}
<strong>{displayName(p)}</strong> (Rank {p.rank}) <strong>{displayName(p)}</strong> (Rank {p.rank})
{#if p.id === state.game.captain_player_id}<span style="color: var(--gold);"> ⭐ Captain</span>{/if} {#if p.id === state.game.captain_player_id}<span class="text-accent"> ⭐ Captain</span>{/if}
</span> </span>
<span style="font-size: 0.85rem; color: var(--text-muted);"> <span class="objectives-summary">
{p.completed_personal_1 ? '🔫 Gat' : '— no gat'} · {p.completed_personal_1 ? '🔫 Gat' : '— no gat'} ·
{p.completed_personal_2 ? '📛 Named' : '— nameless'} · {p.completed_personal_2 ? '📛 Named' : '— nameless'} ·
{p.completed_personal_3 ? '⚰️ Story complete' : '⛵ Still sailing'} {p.completed_personal_3 ? '⚰️ Story complete' : '⛵ Still sailing'}

View File

@@ -65,7 +65,7 @@
</button> </button>
</div> </div>
<div class="mt-4"> <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> <a href="/#/game/{state.game.id}/admin" class="text-sm text-muted underline">Access Admin Panel directly</a>
</div> </div>
</div> </div>
{/if} {/if}

View File

@@ -27,36 +27,34 @@
<div class="obstacles-container" id="scene-obstacles-container"> <div class="obstacles-container" id="scene-obstacles-container">
<!-- TOP STATUS BAR: Scene Info and Captain --> <!-- TOP STATUS BAR: Scene Info and Captain -->
<div class="scene-status-banner glass-panel" style="margin-bottom: 1.5rem; padding: 1rem; background: rgba(7, 11, 18, 0.6); border: 1px solid rgba(212, 175, 55, 0.2); border-radius: 8px;"> <div class="scene-status-banner">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;"> <!-- Captain Badge -->
<!-- Captain Badge --> <div>
<div> {#if captain}
{#if captain} <span class="captain-badge">
<span class="captain-badge" style="background: rgba(212, 175, 55, 0.15); border: 1px solid var(--gold); color: var(--gold); padding: 0.4rem 1rem; border-radius: 20px; font-size: 1rem; font-weight: 700; font-family: var(--font-heading); display: inline-flex; align-items: center; gap: 0.5rem;"> 🏴‍☠️ Captain: {displayName(captain)} (Rank {captain.rank}){isCaptain ? ' — You!' : ''}
🏴‍☠️ Captain: {displayName(captain)} (Rank {captain.rank}){isCaptain ? ' — You!' : ''} </span>
{:else}
<span class="captain-badge vacant">
🏴‍☠️ Captain: None{state.game.completed_crew_1 ? '' : ' (steal a ship first!)'}
</span>
{/if}
</div>
<!-- Active Scene Roster Mini-list -->
<div class="roster-chips">
<span class="roster-label">Active Roster:</span>
{#each state.players as p}
{#if p.role === 'deep'}
<span class="player-chip deep-chip">
🌊 {displayName(p)} (Deep)
</span> </span>
{:else} {:else if p.role === 'pirat'}
<span class="captain-badge" style="background: rgba(255, 255, 255, 0.05); border: 1px dashed rgba(255, 255, 255, 0.2); color: var(--text-muted); padding: 0.4rem 1rem; border-radius: 20px; font-size: 1rem; display: inline-flex; align-items: center; gap: 0.5rem;"> <span class="player-chip pirat-chip {p.is_ghost ? 'is-ghost' : ''} {p.is_dead ? 'is-dead' : ''} {p.id === state.game.captain_player_id ? 'is-captain' : ''}">
🏴‍☠️ Captain: None{state.game.completed_crew_1 ? '' : ' (steal a ship first!)'} {#if p.is_ghost}👻{:else if p.is_dead}💀{:else}🐀{/if} {displayName(p)} (Rank {p.rank}){p.id === state.game.captain_player_id ? '' : ''}
</span> </span>
{/if} {/if}
</div> {/each}
<!-- Active Scene Roster Mini-list -->
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
<span style="font-size: 0.85rem; color: var(--text-muted); font-family: var(--font-heading); margin-right: 0.25rem;">Active Roster:</span>
{#each state.players as p}
{#if p.role === 'deep'}
<span class="player-chip deep-chip" style="font-size: 0.8rem; padding: 0.25rem 0.6rem; border-radius: 12px; margin: 0; display: inline-flex; align-items: center; gap: 0.25rem;">
🌊 {displayName(p)} (Deep)
</span>
{:else if p.role === 'pirat'}
<span class="player-chip pirat-chip" style="font-size: 0.8rem; padding: 0.25rem 0.6rem; border-radius: 12px; margin: 0; display: inline-flex; align-items: center; gap: 0.25rem; {p.is_ghost ? 'border-color: #7890a8; color: #a0b0c0; background: rgba(120, 150, 180, 0.1);' : ''} {p.is_dead ? 'border-color: var(--red-suit); color: var(--red-suit); background: rgba(255, 42, 95, 0.1);' : ''} {p.id === state.game.captain_player_id ? 'border-color: var(--gold); color: var(--gold); background: rgba(212, 175, 55, 0.1);' : ''}">
{#if p.is_ghost}👻{:else if p.is_dead}💀{:else}🐀{/if} {displayName(p)} (Rank {p.rank}){p.id === state.game.captain_player_id ? ' ⭐' : ''}
</span>
{/if}
{/each}
</div>
</div> </div>
</div> </div>

View File

@@ -52,12 +52,12 @@
<p class="description">Select your role for the upcoming scene. There must be at least one Pi-Rat and one Deep player. If you played the Deep in the last scene, you must play a Pi-Rat this scene!</p> <p class="description">Select your role for the upcoming scene. There must be at least one Pi-Rat and one Deep player. If you played the Deep in the last scene, you must play a Pi-Rat this scene!</p>
{#if state.game.current_scene_number === 1} {#if state.game.current_scene_number === 1}
<div class="glass-panel" style="max-width: 700px; margin: 0 auto 1.5rem; padding: 1rem 1.5rem; border: 1px dashed var(--gold); border-radius: 8px; background: rgba(212, 175, 55, 0.08); text-align: left;"> <div class="notice-banner accent">
<h4 style="color: var(--gold); margin: 0 0 0.5rem 0; font-family: var(--font-heading);">🚢 First Scene Framing</h4> <h4>🚢 First Scene Framing</h4>
<p class="info-text" style="margin: 0; font-size: 0.95rem;"> <p class="info-text" style="margin: 0; font-size: 0.95rem;">
The first scene must establish that the Pi-Rats are <strong>on a ship they can steal</strong> (or have an immediate opportunity to steal one). <strong>Don't start far from water!</strong> The first scene must establish that the Pi-Rats are <strong>on a ship they can steal</strong> (or have an immediate opportunity to steal one). <strong>Don't start far from water!</strong>
{#if state.player.role === 'deep'} {#if state.player.role === 'deep'}
<br/><span style="color: var(--gold);">You're playing the Deep — once the scene starts, describe where the crew is and what ship is ripe for the taking before calling any Challenges.</span> <br/><span class="text-accent">You're playing the Deep — once the scene starts, describe where the crew is and what ship is ripe for the taking before calling any Challenges.</span>
{/if} {/if}
</p> </p>
</div> </div>
@@ -85,16 +85,16 @@
<div class="role-restrictions mt-4"> <div class="role-restrictions mt-4">
{#if !allowedRoles.includes('pirat')} {#if !allowedRoles.includes('pirat')}
{#if state.game.current_scene_number === 1} {#if state.game.current_scene_number === 1}
<p class="text-red-400 text-sm italic">You start at Rank 3, so you must play The Deep for the first scene!</p> <p class="text-danger text-sm italic">You start at Rank 3, so you must play The Deep for the first scene!</p>
{:else} {:else}
<p class="text-red-400 text-sm italic">You are the only eligible player for The Deep this scene!</p> <p class="text-danger text-sm italic">You are the only eligible player for The Deep this scene!</p>
{/if} {/if}
{/if} {/if}
{#if !allowedRoles.includes('deep')} {#if !allowedRoles.includes('deep')}
{#if state.game.current_scene_number === 1} {#if state.game.current_scene_number === 1}
<p class="text-red-400 text-sm italic">You start at Rank 1, so you must play your Pi-Rat for the first scene!</p> <p class="text-danger text-sm italic">You start at Rank 1, so you must play your Pi-Rat for the first scene!</p>
{:else} {:else}
<p class="text-red-400 text-sm italic">You played The Deep last scene, so you must play a Pi-Rat!</p> <p class="text-danger text-sm italic">You played The Deep last scene, so you must play a Pi-Rat!</p>
{/if} {/if}
{/if} {/if}
</div> </div>
@@ -106,7 +106,7 @@
<div class="roster-list" id="scene-roster-list"> <div class="roster-list" id="scene-roster-list">
{#each state.players as p} {#each state.players as p}
<div class="roster-item"> <div class="roster-item">
<span class="player-name">{displayName(p)} <span class="text-sm text-gray-400">(Rank {p.rank})</span></span> <span class="player-name">{displayName(p)} <span class="text-sm text-muted">(Rank {p.rank})</span></span>
{#if p.role === 'pirat'} {#if p.role === 'pirat'}
<span class="role-badge pirat">Pi-Rat</span> <span class="role-badge pirat">Pi-Rat</span>
{:else if p.role === 'deep'} {:else if p.role === 'deep'}
@@ -132,13 +132,13 @@
{starting ? 'Starting Scene...' : 'Confirm Roles & Shuffle Deck'} {starting ? 'Starting Scene...' : 'Confirm Roles & Shuffle Deck'}
</button> </button>
{:else if !allRolesAssigned} {:else if !allRolesAssigned}
<p class="text-gray-400 italic mt-4">Waiting for all players to choose a role...</p> <p class="text-muted italic mt-4">Waiting for all players to choose a role...</p>
{:else if !deepPlayer} {:else if !deepPlayer}
<p class="text-red-400 italic mt-4">Someone must play as The Deep!</p> <p class="text-danger italic mt-4">Someone must play as The Deep!</p>
{:else if !piratPlayer} {:else if !piratPlayer}
<p class="text-red-400 italic mt-4">Someone must play as a Pi-Rat!</p> <p class="text-danger italic mt-4">Someone must play as a Pi-Rat!</p>
{:else} {:else}
<p class="text-gray-400 italic mt-4">Waiting for The Deep or Creator to start the scene...</p> <p class="text-muted italic mt-4">Waiting for The Deep or Creator to start the scene...</p>
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -184,67 +184,67 @@
{#if state.player.is_dead && !state.player.is_ghost} {#if state.player.is_dead && !state.player.is_ghost}
<!-- Death Fate Panel --> <!-- Death Fate Panel -->
<div class="card glass-panel death-fate-card" style="border: 2px solid var(--red-suit); background: rgba(255, 42, 95, 0.05); padding: 2.5rem; margin: 2rem auto; max-width: 650px; border-radius: 12px; box-shadow: 0 0 20px rgba(255,42,95,0.15); text-align: center;"> <div class="glass-panel death-fate-card">
<h2 style="color: var(--red-suit); font-family: var(--font-heading); margin-bottom: 1rem; font-size: 2rem;">💀 Your Pi-Rat Has Died!</h2> <h2>💀 Your Pi-Rat Has Died!</h2>
<p class="description" style="font-size: 1.15rem; color: var(--text-primary); margin-bottom: 2rem; line-height: 1.6;"> <p class="description">
Your story arc is complete! You went out in a blaze of glory (or retired honorably). You must now choose how your journey continues: Your story arc is complete! You went out in a blaze of glory (or retired honorably). You must now choose how your journey continues:
</p> </p>
{#if !isRolling} {#if !isRolling}
<div style="display: flex; gap: 1.5rem; justify-content: center; flex-wrap: wrap;"> <div class="fate-choices">
<button class="btn btn-secondary btn-large glow-effect" on:click={becomeGhost} style="border: 1px solid var(--text-muted); font-size: 1.1rem; padding: 0.8rem 2rem;"> <button class="btn btn-secondary btn-large glow-effect" on:click={becomeGhost}>
👻 Remain as a Ghost 👻 Remain as a Ghost
</button> </button>
<button class="btn btn-primary btn-large glow-effect" on:click={() => isRolling = true} style="background: var(--gold); border: 1px solid var(--gold); color: black; font-size: 1.1rem; padding: 0.8rem 2rem;"> <button class="btn btn-primary btn-large glow-effect" on:click={() => isRolling = true}>
🐀 Roll a New Character 🐀 Roll a New Character
</button> </button>
</div> </div>
<p class="info-text" style="margin-top: 1.5rem; font-style: italic; color: var(--text-muted);"> <p class="info-text italic" style="margin-top: 1.5rem;">
Ghosts look back into the living world, watch over friends, and help their crew. New recruits start fresh at Rank 1 with surprise Secret Techniques and a name based on their smell. Ghosts look back into the living world, watch over friends, and help their crew. New recruits start fresh at Rank 1 with surprise Secret Techniques and a name based on their smell.
</p> </p>
{:else} {:else}
<div class="creation-form-wrapper" style="text-align: left; background: rgba(0,0,0,0.2); padding: 1.5rem; border-radius: 8px; border: 1px solid rgba(255,255,255,0.05);"> <div class="creation-form-wrapper">
<h3 style="color: var(--gold); font-family: var(--font-heading); margin-bottom: 1.5rem; border-bottom: 1px solid rgba(212,175,55,0.2); padding-bottom: 0.5rem;">Create Your New Recruit</h3> <h3>Create Your New Recruit</h3>
{#if rollingError} {#if rollingError}
<div class="alert alert-danger" style="margin-bottom: 1.5rem;">{rollingError}</div> <div class="alert alert-danger" style="margin-bottom: 1.5rem;">{rollingError}</div>
{/if} {/if}
<div class="form-group" style="margin-bottom: 1.25rem;"> <div class="form-group">
<label style="display: block; font-weight: bold; margin-bottom: 0.5rem; font-size: 0.9rem; color: var(--text-primary); font-family: var(--font-heading);">What does your Pi-Rat look like?</label> <label>What does your Pi-Rat look like?</label>
<div class="input-row" style="display: flex; gap: 0.5rem;"> <div class="input-row">
<input type="text" placeholder="e.g. Scruffy snout, wearing a tattered vest" bind:value={reRollDetails.avatar_look} required class="input-field" style="flex: 1; padding: 0.6rem; border-radius: 4px; border: 1px solid var(--glass-border); background: rgba(0,0,0,0.3); color: white;"> <input type="text" placeholder="e.g. Scruffy snout, wearing a tattered vest" bind:value={reRollDetails.avatar_look} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.avatar_look = getSuggestion('look')} style="padding: 0.4rem 0.8rem; font-size: 0.85rem;">Suggest</button> <button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.avatar_look = getSuggestion('look')}>Suggest</button>
</div> </div>
</div> </div>
<div class="form-group" style="margin-bottom: 1.25rem;"> <div class="form-group">
<label style="display: block; font-weight: bold; margin-bottom: 0.5rem; font-size: 0.9rem; color: var(--text-primary); font-family: var(--font-heading);">What does your Pi-Rat smell like? (Determines name)</label> <label>What does your Pi-Rat smell like? (Determines name)</label>
<div class="input-row" style="display: flex; gap: 0.5rem;"> <div class="input-row">
<input type="text" placeholder="e.g. Spiced rum, salty cheese, gunpowder" bind:value={reRollDetails.avatar_smell} required class="input-field" style="flex: 1; padding: 0.6rem; border-radius: 4px; border: 1px solid var(--glass-border); background: rgba(0,0,0,0.3); color: white;"> <input type="text" placeholder="e.g. Spiced rum, salty cheese, gunpowder" bind:value={reRollDetails.avatar_smell} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.avatar_smell = getSuggestion('smell')} style="padding: 0.4rem 0.8rem; font-size: 0.85rem;">Suggest</button> <button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.avatar_smell = getSuggestion('smell')}>Suggest</button>
</div> </div>
</div> </div>
<div class="form-group" style="margin-bottom: 1.25rem;"> <div class="form-group">
<label style="display: block; font-weight: bold; margin-bottom: 0.5rem; font-size: 0.9rem; color: var(--text-primary); font-family: var(--font-heading);">What were your first words after transforming?</label> <label>What were your first words after transforming?</label>
<div class="input-row" style="display: flex; gap: 0.5rem;"> <div class="input-row">
<input type="text" placeholder="e.g. Shiver my decimals!" bind:value={reRollDetails.first_words} required class="input-field" style="flex: 1; padding: 0.6rem; border-radius: 4px; border: 1px solid var(--glass-border); background: rgba(0,0,0,0.3); color: white;"> <input type="text" placeholder="e.g. Shiver my decimals!" bind:value={reRollDetails.first_words} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.first_words = getSuggestion('first_words')} style="padding: 0.4rem 0.8rem; font-size: 0.85rem;">Suggest</button> <button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.first_words = getSuggestion('first_words')}>Suggest</button>
</div> </div>
</div> </div>
<div class="form-group" style="margin-bottom: 1.5rem;"> <div class="form-group">
<label style="display: block; font-weight: bold; margin-bottom: 0.5rem; font-size: 0.9rem; color: var(--text-primary); font-family: var(--font-heading);">Is your Pi-Rat good at Math?</label> <label>Is your Pi-Rat good at Math?</label>
<div class="input-row" style="display: flex; gap: 0.5rem;"> <div class="input-row">
<input type="text" placeholder="e.g. Thinks Pi is a dessert" bind:value={reRollDetails.good_at_math} required class="input-field" style="flex: 1; padding: 0.6rem; border-radius: 4px; border: 1px solid var(--glass-border); background: rgba(0,0,0,0.3); color: white;"> <input type="text" placeholder="e.g. Thinks Pi is a dessert" bind:value={reRollDetails.good_at_math} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.good_at_math = getSuggestion('good_at_math')} style="padding: 0.4rem 0.8rem; font-size: 0.85rem;">Suggest</button> <button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.good_at_math = getSuggestion('good_at_math')}>Suggest</button>
</div> </div>
</div> </div>
<div style="display: flex; gap: 1rem; margin-top: 1.5rem;"> <div class="form-actions">
<button class="btn btn-secondary" on:click={() => isRolling = false} style="flex: 1; padding: 0.75rem;">Cancel</button> <button class="btn btn-secondary" on:click={() => isRolling = false}>Cancel</button>
<button class="btn btn-primary" on:click={submitReRoll} style="flex: 2; padding: 0.75rem; background: var(--gold); border: 1px solid var(--gold); color: black;">Submit New Character</button> <button class="btn btn-primary" on:click={submitReRoll}>Submit New Character</button>
</div> </div>
</div> </div>
{/if} {/if}
@@ -350,8 +350,8 @@
</div> </div>
{#if isOverLimit} {#if isOverLimit}
<div style="border: 1px solid var(--red-suit); border-radius: 4px; padding: 1rem; margin: 1rem 0; background: rgba(255, 42, 95, 0.1);"> <div class="notice-banner danger" style="margin: 1rem 0; max-width: none;">
<p style="color: var(--red-suit); font-weight: bold; margin: 0;">⚠️ Discard Required: You have selected {keepCards.length} cards to keep, which exceeds your maximum hand limit of {maxHandSize}. Please discard at least {keepCards.length - maxHandSize} card(s).</p> <p class="text-danger font-bold" style="margin: 0;">⚠️ Discard Required: You have selected {keepCards.length} cards to keep, which exceeds your maximum hand limit of {maxHandSize}. Please discard at least {keepCards.length - maxHandSize} card(s).</p>
</div> </div>
{/if} {/if}
@@ -374,7 +374,7 @@
{#each state.players as p} {#each state.players as p}
<li> <li>
<strong>{displayName(p)}:</strong> Rank {p.rank} <strong>{displayName(p)}:</strong> Rank {p.rank}
{#if p.role === 'deep'}<span class="deep-badge">Deep</span>{:else if p.is_ghost}<span class="ghost-badge" style="background: rgba(120, 150, 180, 0.15); border: 1px solid #7890a8; color: #a0b0c0; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 700; margin-left: 0.5rem; display: inline-block;">Ghost</span>{:else}<span class="pirat-badge">Pi-Rat</span>{/if} {#if p.role === 'deep'}<span class="deep-badge">Deep</span>{:else if p.is_ghost}<span class="ghost-badge">Ghost</span>{:else}<span class="pirat-badge">Pi-Rat</span>{/if}
</li> </li>
{/each} {/each}
</ul> </ul>
@@ -414,7 +414,7 @@
{/if} {/if}
{#if state.player.is_creator && state.game.phase === 'between_scenes'} {#if state.player.is_creator && state.game.phase === 'between_scenes'}
<div style="margin-top: 1.5rem; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 1rem;"> <div class="end-story-box">
<p class="info-text" style="font-size: 0.85rem;">All good stories end. When the crew agrees the legend is complete:</p> <p class="info-text" style="font-size: 0.85rem;">All good stories end. When the crew agrees the legend is complete:</p>
<button on:click={finishGame} class="btn btn-danger">🏴‍☠️ End the Story</button> <button on:click={finishGame} class="btn btn-danger">🏴‍☠️ End the Story</button>
</div> </div>

View File

@@ -62,9 +62,9 @@
{#each openChallenges as ch} {#each openChallenges as ch}
{@const chPlays = JSON.parse(ch.plays || '[]')} {@const chPlays = JSON.parse(ch.plays || '[]')}
{@const chObstacleIds = JSON.parse(ch.obstacle_ids || '[]')} {@const chObstacleIds = JSON.parse(ch.obstacle_ids || '[]')}
<div class="glass-panel" style="margin-bottom: 1rem; padding: 1rem; border: 1px solid var(--red-suit, #ff2a5f); border-radius: 8px; background: rgba(255, 42, 95, 0.06);"> <div class="challenge-item">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;"> <div class="challenge-head">
<h4 style="margin: 0; font-family: var(--font-heading);"> <h4>
{#if ch.challenge_type === 'pvp'} {#if ch.challenge_type === 'pvp'}
⚔️ Duel: {playerName(ch.challenger_player_id)} vs {playerName(ch.target_player_id)} ⚔️ Duel: {playerName(ch.challenger_player_id)} vs {playerName(ch.target_player_id)}
{:else} {:else}
@@ -72,7 +72,7 @@
{/if} {/if}
</h4> </h4>
{#if state.player.role === 'deep' && ch.challenge_type === 'deep'} {#if state.player.role === 'deep' && ch.challenge_type === 'deep'}
<div style="display: flex; gap: 0.5rem;"> <div class="challenge-actions">
<button class="btn btn-primary" on:click={() => resolveChallenge(ch.id)} disabled={ch.tax_state === 'requested'}>Resolve</button> <button class="btn btn-primary" on:click={() => resolveChallenge(ch.id)} disabled={ch.tax_state === 'requested'}>Resolve</button>
<button class="btn btn-secondary" on:click={() => cancelChallenge(ch.id)}>Withdraw</button> <button class="btn btn-secondary" on:click={() => cancelChallenge(ch.id)}>Withdraw</button>
</div> </div>
@@ -101,10 +101,10 @@
<!-- Tax: pending request aimed at me --> <!-- Tax: pending request aimed at me -->
{#if ch.tax_state === 'requested'} {#if ch.tax_state === 'requested'}
{#if myTaxRequest && myTaxRequest.id === ch.id} {#if myTaxRequest && myTaxRequest.id === ch.id}
<div style="margin-top: 0.75rem; padding: 0.75rem; border: 1px dashed var(--gold); border-radius: 6px;"> <div class="tax-callout">
<p style="margin: 0 0 0.5rem 0;"><strong>{playerName(ch.target_player_id)}</strong> calls a <strong>{ch.tax_type === 'gat' ? 'Gat' : 'Name'} Tax</strong> on you: take this Challenge for them!</p> <p><strong>{playerName(ch.target_player_id)}</strong> calls a <strong>{ch.tax_type === 'gat' ? 'Gat' : 'Name'} Tax</strong> on you: take this Challenge for them!</p>
<p class="info-text" style="font-size: 0.85rem;">Accept: you get one random card from their hand and attempt the Challenge. Refuse: you hand over your {ch.tax_type === 'gat' ? 'Gat' : 'Name'} — they keep it if they succeed!</p> <p class="info-text" style="font-size: 0.85rem;">Accept: you get one random card from their hand and attempt the Challenge. Refuse: you hand over your {ch.tax_type === 'gat' ? 'Gat' : 'Name'} — they keep it if they succeed!</p>
<div style="display: flex; gap: 0.5rem;"> <div class="challenge-actions">
<button class="btn btn-primary" on:click={() => respondTax(ch.id, true)}>Accept</button> <button class="btn btn-primary" on:click={() => respondTax(ch.id, true)}>Accept</button>
<button class="btn btn-danger" on:click={() => respondTax(ch.id, false)}>Refuse</button> <button class="btn btn-danger" on:click={() => respondTax(ch.id, false)}>Refuse</button>
</div> </div>
@@ -116,8 +116,8 @@
<!-- Tax: option for the challenged player --> <!-- Tax: option for the challenged player -->
{#if ch.challenge_type === 'deep' && ch.acting_player_id === state.player.id && !ch.tax_state && !state.player.tax_banned && taxType(state.player) && eligibleTaxTargets().length > 0} {#if ch.challenge_type === 'deep' && ch.acting_player_id === state.player.id && !ch.tax_state && !state.player.tax_banned && taxType(state.player) && eligibleTaxTargets().length > 0}
<div style="margin-top: 0.75rem; display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;"> <div class="challenge-actions" style="margin-top: 0.75rem;">
<span class="info-text" style="font-size: 0.9rem;">No {taxType(state.player)}? Make it someone else's problem:</span> <span class="info-text" style="font-size: 0.9rem; margin: 0;">No {taxType(state.player)}? Make it someone else's problem:</span>
<select class="select-field" bind:value={taxTargetId} style="max-width: 200px;"> <select class="select-field" bind:value={taxTargetId} style="max-width: 200px;">
<option value="">Pick a crewmate...</option> <option value="">Pick a crewmate...</option>
{#each eligibleTaxTargets() as p} {#each eligibleTaxTargets() as p}
@@ -132,7 +132,7 @@
{#if myPvpDefense && myPvpDefense.id === ch.id} {#if myPvpDefense && myPvpDefense.id === ch.id}
<div style="margin-top: 0.75rem;"> <div style="margin-top: 0.75rem;">
<p style="margin: 0 0 0.5rem 0;"><strong>Defend yourself!</strong> Pick a card:</p> <p style="margin: 0 0 0.5rem 0;"><strong>Defend yourself!</strong> Pick a card:</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;"> <div class="challenge-actions">
{#each hand.filter(c => !isJoker(c)) as card} {#each hand.filter(c => !isJoker(c)) as card}
<button class="btn btn-secondary" on:click={() => pvpDefend(ch.id, card)}>{getCardDisplay(card)}</button> <button class="btn btn-secondary" on:click={() => pvpDefend(ch.id, card)}>{getCardDisplay(card)}</button>
{/each} {/each}

View File

@@ -55,32 +55,32 @@
{/if} {/if}
{#if state.player.is_dead} {#if state.player.is_dead}
<div class="sheet-group prompt-box" style="background: rgba(255, 42, 95, 0.1); border: 1px dashed var(--red-suit); padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;"> <div class="prompt-box danger text-center">
<h4 style="color: var(--red-suit); margin-top: 0; font-family: var(--font-heading); font-size: 1.1rem; font-weight: 700;">💀 You have Died / Retired!</h4> <h4>💀 You have Died / Retired!</h4>
<p class="info-text" style="margin-bottom: 0; font-size: 0.9rem;">Your story arc is complete. You will choose to become a Ghost or roll a new character during the upkeep phase between scenes.</p> <p class="info-text" style="margin-bottom: 0; font-size: 0.9rem;">Your story arc is complete. You will choose to become a Ghost or roll a new character during the upkeep phase between scenes.</p>
</div> </div>
{/if} {/if}
{#if state.player.is_ghost} {#if state.player.is_ghost}
<div class="sheet-group prompt-box" style="background: rgba(120, 150, 180, 0.1); border: 1px dashed #7890a8; padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;"> <div class="prompt-box ghostly text-center">
<h4 style="color: #a0b0c0; margin-top: 0; font-family: var(--font-heading); font-size: 1.1rem; font-weight: 700;">👻 Playing as a Ghost</h4> <h4>👻 Playing as a Ghost</h4>
<p class="info-text" style="margin-bottom: 0; font-size: 0.9rem;">You are in the Ghost World (grayscale view). You cannot interact well with the living but you can still help them resolve challenges!</p> <p class="info-text" style="margin-bottom: 0; font-size: 0.9rem;">You are in the Ghost World (grayscale view). You cannot interact well with the living but you can still help them resolve challenges!</p>
</div> </div>
{/if} {/if}
{#if state.player.needs_name} {#if state.player.needs_name}
<div class="sheet-group prompt-box" style="background: rgba(212, 175, 55, 0.1); border: 1px dashed var(--gold); padding: 1rem; border-radius: 8px; margin-bottom: 1rem;"> <div class="prompt-box accent">
<h4 style="color: var(--gold); margin-top: 0;">🏴‍☠️ You Earned a Name!</h4> <h4>🏴‍☠️ You Earned a Name!</h4>
<p class="info-text" style="margin-bottom: 0.5rem;">You completed your second objective and ranked up! What is your new Pirate Name?</p> <p class="info-text" style="margin-bottom: 0.5rem;">You completed your second objective and ranked up! What is your new Pirate Name?</p>
<div style="display: flex; gap: 0.5rem;"> <div class="input-row">
<input type="text" bind:value={newNameInput} class="form-control" placeholder="Enter new name..."> <input type="text" bind:value={newNameInput} class="input-field" placeholder="Enter new name...">
<button class="btn btn-gold" on:click={submitNewName} disabled={!newNameInput.trim()}>Claim Name</button> <button class="btn btn-gold" on:click={submitNewName} disabled={!newNameInput.trim()}>Claim Name</button>
</div> </div>
</div> </div>
{/if} {/if}
{#if state.player.tax_banned} {#if state.player.tax_banned}
<div class="sheet-group prompt-box" style="background: rgba(255, 42, 95, 0.08); border: 1px dashed var(--red-suit); padding: 0.75rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;"> <div class="prompt-box danger text-center">
<p class="info-text" style="margin: 0; font-size: 0.9rem;">🚫 You failed a refused Tax — no more Gat/Name Taxes for you this scene.</p> <p class="info-text" style="margin: 0; font-size: 0.9rem;">🚫 You failed a refused Tax — no more Gat/Name Taxes for you this scene.</p>
</div> </div>
{/if} {/if}
@@ -104,7 +104,7 @@
<!-- Duel another Pi-Rat --> <!-- Duel another Pi-Rat -->
{#if !state.player.is_dead && duelTargets.length > 0} {#if !state.player.is_dead && duelTargets.length > 0}
<div class="sheet-group margin-top"> <div class="sheet-group margin-top">
<h4 style="color: var(--gold);">⚔️ Duel a Pi-Rat</h4> <h4>⚔️ Duel a Pi-Rat</h4>
<p class="info-text" style="font-size: 0.85rem;">Challenge a crewmate (e.g. for the Captaincy)! Your card becomes a temporary Obstacle they must beat. Both cards are discarded afterwards.</p> <p class="info-text" style="font-size: 0.85rem;">Challenge a crewmate (e.g. for the Captaincy)! Your card becomes a temporary Obstacle they must beat. Both cards are discarded afterwards.</p>
<select class="select-field" bind:value={pvpOpponentId} style="width: 100%; margin-bottom: 0.5rem;"> <select class="select-field" bind:value={pvpOpponentId} style="width: 100%; margin-bottom: 0.5rem;">
<option value="">Challenge whom?</option> <option value="">Challenge whom?</option>
@@ -123,7 +123,7 @@
{/if} {/if}
<div class="sheet-group margin-top"> <div class="sheet-group margin-top">
<h4 style="color: var(--gold);">Crew Objectives</h4> <h4>Crew Objectives</h4>
<div class="objectives-checklist"> <div class="objectives-checklist">
<label class="checkbox-label"> <label class="checkbox-label">
<input type="checkbox" checked={state.game.completed_crew_1} disabled> <input type="checkbox" checked={state.game.completed_crew_1} disabled>
@@ -141,7 +141,7 @@
</div> </div>
<div class="sheet-group margin-top"> <div class="sheet-group margin-top">
<h4 style="color: var(--gold);">Personal Objectives</h4> <h4>Personal Objectives</h4>
<div class="objectives-checklist"> <div class="objectives-checklist">
<label class="checkbox-label"> <label class="checkbox-label">
<input type="checkbox" checked={state.player.completed_personal_1} disabled> <input type="checkbox" checked={state.player.completed_personal_1} disabled>

View File

@@ -62,7 +62,7 @@
} }
</script> </script>
<div class="card glass-panel deep-panel-card" style="max-height: 80vh; overflow-y: auto;"> <div class="card glass-panel deep-panel-card">
<h3 class="deep-text text-center">🌊 Deep Control Panel</h3> <h3 class="deep-text text-center">🌊 Deep Control Panel</h3>
{#if error} {#if error}
@@ -71,7 +71,7 @@
<!-- Call a Challenge --> <!-- Call a Challenge -->
<div class="sheet-group margin-top"> <div class="sheet-group margin-top">
<h4 style="color: var(--gold);">⚔️ Call a Challenge</h4> <h4>⚔️ Call a Challenge</h4>
<p class="info-text" style="font-size: 0.85rem;">Establish the stakes, pick a Pi-Rat, and apply one or more Obstacles. They play one card per Obstacle — beating one passes; each failure breeds a complication.</p> <p class="info-text" style="font-size: 0.85rem;">Establish the stakes, pick a Pi-Rat, and apply one or more Obstacles. They play one card per Obstacle — beating one passes; each failure breeds a complication.</p>
<select class="select-field" bind:value={challengeTargetId} style="width: 100%; margin-bottom: 0.5rem;"> <select class="select-field" bind:value={challengeTargetId} style="width: 100%; margin-bottom: 0.5rem;">
<option value="">Challenge which Pi-Rat?</option> <option value="">Challenge which Pi-Rat?</option>
@@ -88,7 +88,7 @@
</label> </label>
{/each} {/each}
</div> </div>
<input type="text" class="form-control" placeholder="Stakes (optional): what happens on success/failure?" bind:value={challengeStakes} style="width: 100%; margin-bottom: 0.5rem;"> <input type="text" class="input-field" placeholder="Stakes (optional): what happens on success/failure?" bind:value={challengeStakes} style="width: 100%; margin-bottom: 0.5rem;">
<button class="btn btn-primary btn-full" on:click={createChallenge} <button class="btn btn-primary btn-full" on:click={createChallenge}
disabled={!challengeTargetId || !Object.values(selectedObstacles).some(v => v)}> disabled={!challengeTargetId || !Object.values(selectedObstacles).some(v => v)}>
Call Challenge Call Challenge
@@ -97,7 +97,7 @@
<!-- Captain Assignment --> <!-- Captain Assignment -->
<div class="sheet-group margin-top"> <div class="sheet-group margin-top">
<h4 style="color: var(--gold);">🏴‍☠️ Captaincy</h4> <h4>🏴‍☠️ Captaincy</h4>
<p class="info-text" style="font-size: 0.85rem;">The Captain holds power until they step down, lose a duel, die, or the ship is lost. Reassign after a leadership duel or resignation.</p> <p class="info-text" style="font-size: 0.85rem;">The Captain holds power until they step down, lose a duel, die, or the ship is lost. Reassign after a leadership duel or resignation.</p>
<div style="display: flex; gap: 0.5rem;"> <div style="display: flex; gap: 0.5rem;">
<select class="select-field" bind:value={captainSelectId} style="flex: 1;"> <select class="select-field" bind:value={captainSelectId} style="flex: 1;">
@@ -111,7 +111,7 @@
</div> </div>
<div class="sheet-group margin-top"> <div class="sheet-group margin-top">
<h4 style="color: var(--gold);">Crew Objectives</h4> <h4>Crew Objectives</h4>
<div class="objectives-checklist"> <div class="objectives-checklist">
<label class="checkbox-label"> <label class="checkbox-label">
<input type="checkbox" checked={state.game.completed_crew_1} on:change={() => toggleObjective(state.player.id, 'crew_1')}> <input type="checkbox" checked={state.game.completed_crew_1} on:change={() => toggleObjective(state.player.id, 'crew_1')}>
@@ -129,10 +129,10 @@
</div> </div>
<div class="sheet-group margin-top"> <div class="sheet-group margin-top">
<h4 style="color: var(--gold);">Pi-Rat Personal Objectives</h4> <h4>Pi-Rat Personal Objectives</h4>
<p class="info-text">You control completion. Only mark in sequence (1 -> 2 -> 3).</p> <p class="info-text">You control completion. Only mark in sequence (1 -> 2 -> 3).</p>
{#each scenePirats as p} {#each scenePirats as p}
<div style="background: rgba(0,0,0,0.2); padding: 0.5rem; border-radius: 4px; margin-bottom: 0.5rem;"> <div class="objective-player-block">
<strong>{displayName(p)}</strong> <strong>{displayName(p)}</strong>
<div class="objectives-checklist" style="margin-top: 0.25rem;"> <div class="objectives-checklist" style="margin-top: 0.25rem;">
<label class="checkbox-label"><input type="checkbox" checked={p.completed_personal_1} on:change={() => toggleObjective(p.id, 'personal_1')}> <span>1. Gat</span></label> <label class="checkbox-label"><input type="checkbox" checked={p.completed_personal_1} on:change={() => toggleObjective(p.id, 'personal_1')}> <span>1. Gat</span></label>
@@ -144,7 +144,7 @@
</div> </div>
<!-- End Scene Button --> <!-- End Scene Button -->
<div class="scene-upkeep-controls text-center" style="margin-top: 1rem; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 1rem;"> <div class="scene-upkeep-controls text-center">
<p class="info-text">End the scene once every Pi-Rat has faced a Challenge and had a chance at an Objective, or the Obstacle List is empty.</p> <p class="info-text">End the scene once every Pi-Rat has faced a Challenge and had a chance at an Objective, or the Obstacle List is empty.</p>
<button on:click={endScene} class="btn btn-danger btn-large btn-full glow-effect"> <button on:click={endScene} class="btn btn-danger btn-large btn-full glow-effect">
End Scene & Proceed to Ranking End Scene & Proceed to Ranking

View File

@@ -62,8 +62,7 @@
{@const success_count = column_cards.filter(c => c.success).length} {@const success_count = column_cards.filter(c => c.success).length}
{@const is_completed = success_count >= state.players.length} {@const is_completed = success_count >= state.players.length}
{@const in_challenge = challengedObstacleIds.has(obs.id)} {@const in_challenge = challengedObstacleIds.has(obs.id)}
<div class="obstacle-item suit-{obs.suit.toLowerCase()} {is_completed ? 'completed' : ''}" <div class="obstacle-item suit-{obs.suit.toLowerCase()} {is_completed ? 'completed' : ''} {in_challenge ? 'in-challenge' : ''}"
style={in_challenge ? 'box-shadow: 0 0 0 2px var(--red-suit, #ff2a5f);' : ''}
data-obstacle-id={obs.id} data-obstacle-id={obs.id}
on:dragover={(e) => { if (!is_completed) e.preventDefault(); }} on:dragover={(e) => { if (!is_completed) e.preventDefault(); }}
on:dragenter={(e) => { if (!is_completed) e.currentTarget.classList.add("drag-over"); }} on:dragenter={(e) => { if (!is_completed) e.currentTarget.classList.add("drag-over"); }}
@@ -78,11 +77,11 @@
else playCard(obs.id, cardCode); else playCard(obs.id, cardCode);
} }
}}> }}>
<div class="obstacle-card-wrapper" style="display: flex; justify-content: center; align-items: center;"> <div class="obstacle-card-wrapper">
<Card card={active_card_code} size="medium" title="Active Card: {getCardDisplay(active_card_code)}" /> <Card card={active_card_code} size="medium" title="Active Card: {getCardDisplay(active_card_code)}" />
</div> </div>
<div class="obstacle-details"> <div class="obstacle-details">
<h4>{obs.title} {#if in_challenge}<span style="color: var(--red-suit, #ff2a5f); font-size: 0.8rem;">⚔️ In Challenge</span>{/if}</h4> <h4>{obs.title} {#if in_challenge}<span class="in-challenge-tag">⚔️ In Challenge</span>{/if}</h4>
<p class="desc">{obs.description}</p> <p class="desc">{obs.description}</p>
</div> </div>
@@ -102,7 +101,7 @@
<!-- Successes Tracker --> <!-- Successes Tracker -->
<div class="obstacle-successes-display text-center"> <div class="obstacle-successes-display text-center">
<span class="val-label">Successes</span> <span class="val-label">Successes</span>
<span class="val-number" style="font-size: 1.5rem;"> <span class="val-number">
{success_count} / {state.players.length} {success_count} / {state.players.length}
</span> </span>
</div> </div>
@@ -121,8 +120,8 @@
</div> </div>
{#if is_completed && state.player.role === 'deep'} {#if is_completed && state.player.role === 'deep'}
<div class="text-center" style="margin-top: 1rem;"> <div class="clear-obstacle-row text-center">
<button class="btn btn-success" on:click={() => clearObstacle(obs.id)} style="width: 100%; border: 1px solid rgba(255,255,255,0.2);">Clear Obstacle</button> <button class="btn btn-success btn-full" on:click={() => clearObstacle(obs.id)}>Clear Obstacle</button>
</div> </div>
{/if} {/if}
</div> </div>

View File

@@ -1,4 +1,13 @@
import { mount } from 'svelte' import { mount } from 'svelte'
// Self-hosted fonts (bundled by Vite — no external font CDN at runtime)
import '@fontsource/pirata-one'
import '@fontsource/alegreya-sc/500.css'
import '@fontsource/alegreya-sc/700.css'
import '@fontsource/alegreya-sans/400.css'
import '@fontsource/alegreya-sans/500.css'
import '@fontsource/alegreya-sans/700.css'
import './app.css' import './app.css'
import App from './App.svelte' import App from './App.svelte'

View File

@@ -42,14 +42,14 @@
} }
</script> </script>
<div class="max-w-4xl mx-auto p-4 space-y-6"> <div class="admin-page">
<h1 class="text-3xl font-bold text-gold">Admin Panel</h1> <h1>Admin Panel</h1>
{#if !data} {#if !data}
<div class="card p-6"> <div class="card">
<h2 class="text-xl mb-4">Enter Admin Key</h2> <h2 class="mb-4">Enter Admin Key</h2>
<form on:submit|preventDefault={loadAdminData} class="flex gap-4"> <form on:submit|preventDefault={loadAdminData} class="flex gap-4">
<input type="text" bind:value={adminKey} class="form-control flex-grow" placeholder="Admin Key" required/> <input type="text" bind:value={adminKey} class="input-field flex-grow" placeholder="Admin Key" required/>
<button type="submit" class="btn btn-primary">Login</button> <button type="submit" class="btn btn-primary">Login</button>
</form> </form>
{#if error} {#if error}
@@ -57,42 +57,42 @@
{/if} {/if}
</div> </div>
{:else} {:else}
<div class="card p-6"> <div class="card">
<h2 class="text-xl font-bold text-silver mb-4">Game: {data.game.crew_name}</h2> <h2 class="mb-4">Game: {data.game.crew_name}</h2>
<p><strong>Phase:</strong> {data.game.phase}</p> <p><strong>Phase:</strong> {data.game.phase}</p>
<p><strong>Admin Key:</strong> <span class="bg-dark-900 px-2 py-1 font-mono text-sm">{data.game.admin_key}</span></p> <p><strong>Admin Key:</strong> <span class="admin-key-chip">{data.game.admin_key}</span></p>
<h3 class="text-xl font-bold text-silver mt-8 mb-4">Players</h3> <h3 class="mt-8 mb-4">Players</h3>
<table class="w-full text-left bg-dark-900 rounded border border-gray-700"> <table class="admin-table">
<thead> <thead>
<tr class="border-b border-gray-700"> <tr>
<th class="p-3">Pi-Rat</th> <th>Pi-Rat</th>
<th class="p-3">Player</th> <th>Player</th>
<th class="p-3">Role</th> <th>Role</th>
<th class="p-3">Status</th> <th>Status</th>
<th class="p-3">Rank</th> <th>Rank</th>
<th class="p-3">Re-join Link</th> <th>Re-join Link</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each data.players as p} {#each data.players as p}
<tr class="border-b border-gray-800"> <tr>
<td class="p-3">{p.name}</td> <td>{p.name}</td>
<td class="p-3">{p.player_name || p.name}</td> <td>{p.player_name || p.name}</td>
<td class="p-3"> <td>
{#if p.role === 'deep'} 🌊 Deep {#if p.role === 'deep'} 🌊 Deep
{:else if p.role === 'pirat'} 🐀 Pi-Rat {:else if p.role === 'pirat'} 🐀 Pi-Rat
{:else} None {:else} None
{/if} {/if}
</td> </td>
<td class="p-3"> <td>
{#if p.is_ghost} 👻 Ghost {#if p.is_ghost} 👻 Ghost
{:else if p.is_dead} 💀 Dead {:else if p.is_dead} 💀 Dead
{:else} Alive {:else} Alive
{/if} {/if}
</td> </td>
<td class="p-3">{p.rank}</td> <td>{p.rank}</td>
<td class="p-3"> <td>
<div class="link-copy-action"> <div class="link-copy-action">
<a href={rejoinLink(p.id)} class="btn btn-secondary btn-small">Open</a> <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"> <button on:click={() => copyRejoinLink(p.id)} class="btn btn-secondary btn-small">

View File

@@ -24,13 +24,15 @@
</script> </script>
<div class="welcome-container"> <div class="welcome-container">
<div class="header"> <header class="welcome-hero">
<h1>RATS with GATS</h1> <p class="hero-overline">A Remote Play Companion for</p>
<p class="subtitle">A Remote Play Companion App</p> <h1 class="wordmark">Rats <span class="amp">with</span> Gats</h1>
</div> <p class="subtitle">Stowaway rats, transformed by math magic, out to steal a ship and commit some piracy.</p>
<div class="hero-flourish" aria-hidden="true">🐀 ⚓ 🐀</div>
</header>
<div class="card creation-card"> <div class="glass-panel creation-card">
<h2>Start a New Game</h2> <h2>Muster a New Crew</h2>
<form on:submit|preventDefault={createGame}> <form on:submit|preventDefault={createGame}>
<div class="form-group"> <div class="form-group">
<label for="crewName">Crew Name</label> <label for="crewName">Crew Name</label>
@@ -40,7 +42,7 @@
bind:value={crewName} bind:value={crewName}
required required
placeholder="e.g. The Salty Dogs" placeholder="e.g. The Salty Dogs"
class="form-control input-large" class="input-field input-large"
autocomplete="off" autocomplete="off"
/> />
</div> </div>
@@ -49,13 +51,14 @@
<div class="alert alert-danger">{error}</div> <div class="alert alert-danger">{error}</div>
{/if} {/if}
<button type="submit" class="btn btn-primary btn-large btn-block mt-4" disabled={creating}> <button type="submit" class="btn btn-primary btn-large btn-full" disabled={creating}>
{creating ? 'Creating...' : 'Create Game'} {creating ? 'Creating...' : 'Create Game'}
</button> </button>
</form> </form>
<p class="join-hint">Joining someone else's crew? Ask the game creator for the join link from their lobby.</p>
</div> </div>
<div class="links mt-4"> <div class="welcome-links">
<a href="/rules" class="text-muted">Read the Rules</a> <a href="/rules" target="_blank" rel="noopener" class="btn btn-secondary">📖 Read the Rules</a>
</div> </div>
</div> </div>

View File

@@ -26,11 +26,12 @@
</script> </script>
<div class="welcome-container"> <div class="welcome-container">
<div class="header"> <header class="welcome-hero">
<h1>Join Crew</h1> <p class="hero-overline">Rats with Gats</p>
</div> <h1 class="wordmark">Join the Crew</h1>
</header>
<div class="card creation-card"> <div class="glass-panel creation-card">
<h2>Enter your name</h2> <h2>Enter your name</h2>
<p class="info-text" style="margin-bottom: 1rem;"> <p class="info-text" style="margin-bottom: 1rem;">
This is <strong>your</strong> name as a player, not your Pi-Rat's. Your Pi-Rat will be known This is <strong>your</strong> name as a player, not your Pi-Rat's. Your Pi-Rat will be known
@@ -44,7 +45,7 @@
id="playerName" id="playerName"
bind:value={playerName} bind:value={playerName}
required required
class="form-control input-large" class="input-field input-large"
autocomplete="off" autocomplete="off"
autofocus autofocus
/> />
@@ -54,7 +55,7 @@
<div class="alert alert-danger">{error}</div> <div class="alert alert-danger">{error}</div>
{/if} {/if}
<button type="submit" class="btn btn-primary btn-large btn-block mt-4" disabled={joining}> <button type="submit" class="btn btn-primary btn-large btn-full mt-4" disabled={joining}>
{joining ? 'Joining...' : 'Join Game'} {joining ? 'Joining...' : 'Join Game'}
</button> </button>
</form> </form>

View File

@@ -4,21 +4,26 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rats with Gats — Rulebook</title> <title>Rats with Gats — Rulebook</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Alegreya+SC:wght@500;700&family=Alegreya+Sans:wght@400;500;700&family=Pirata+One&display=swap" rel="stylesheet">
<style> <style>
/* Standalone rulebook page — palette mirrors the SPA's core.css tokens */ /* Standalone rulebook page — palette mirrors the SPA's theme.css tokens
("Lantern & Brine"). If you re-theme the app, update these values too. */
:root { :root {
--bg-dark: #070b12; --bg-dark: #0a0d13;
--bg-ocean: #0d1726; --bg-ocean: #131822;
--gold: #d4af37; --gold: #d9a23c;
--neon-cyan: #00f2fe; --neon-cyan: #41c9bd;
--red-suit: #ff2a5f; --red-suit: #e8606e;
--black-suit: #e2e8f0; --black-suit: #dfe6ef;
--glass-bg: rgba(13, 23, 38, 0.7); --glass-bg: rgba(27, 33, 46, 0.85);
--glass-border: rgba(212, 175, 55, 0.2); --glass-border: rgba(217, 162, 60, 0.25);
--text-primary: #f1f5f9; --text-primary: #efe7d3;
--text-muted: #94a3b8; --text-muted: #a89e8a;
--font-heading: 'Cinzel', serif; --font-display: 'Pirata One', 'Alegreya SC', serif;
--font-body: 'Outfit', sans-serif; --font-heading: 'Alegreya SC', serif;
--font-body: 'Alegreya Sans', sans-serif;
} }
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -57,9 +62,11 @@ a { color: var(--neon-cyan); }
.rules-header { text-align: center; } .rules-header { text-align: center; }
.rules-header h1 { .rules-header h1 {
font-family: var(--font-heading); font-family: var(--font-display);
font-weight: 400;
letter-spacing: 1px;
color: var(--gold); color: var(--gold);
font-size: 2.4rem; font-size: 2.8rem;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
@@ -124,14 +131,14 @@ a { color: var(--neon-cyan); }
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin: 0.75rem 0 1rem; margin: 0.75rem 0 1rem;
background: rgba(7, 11, 18, 0.5); background: rgba(10, 13, 19, 0.5);
border: 1px solid var(--glass-border); border: 1px solid var(--glass-border);
border-radius: 8px; border-radius: 8px;
} }
.rules-body th, .rules-body td { .rules-body th, .rules-body td {
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
border-bottom: 1px solid rgba(212, 175, 55, 0.1); border-bottom: 1px solid rgba(217, 162, 60, 0.12);
text-align: left; text-align: left;
vertical-align: top; vertical-align: top;
} }
@@ -177,7 +184,7 @@ a { color: var(--neon-cyan); }
padding: 6px 14px; padding: 6px 14px;
border-radius: 16px; border-radius: 16px;
border: 1px solid var(--glass-border); border: 1px solid var(--glass-border);
background: rgba(10, 15, 25, 0.9); background: rgba(10, 13, 19, 0.9);
text-decoration: none; text-decoration: none;
font-size: 0.85rem; font-size: 0.85rem;
} }