diff --git a/client/index.html b/client/index.html index 7677652..eb107e8 100644 --- a/client/index.html +++ b/client/index.html @@ -1,9 +1,15 @@ - + Shadowdark Character Manager + + +
diff --git a/client/public/textures/parchment-noise-light.png b/client/public/textures/parchment-noise-light.png new file mode 100644 index 0000000..b5af4ec Binary files /dev/null and b/client/public/textures/parchment-noise-light.png differ diff --git a/client/public/textures/parchment-noise.png b/client/public/textures/parchment-noise.png new file mode 100644 index 0000000..80caf73 Binary files /dev/null and b/client/public/textures/parchment-noise.png differ diff --git a/client/public/textures/speckle-light.png b/client/public/textures/speckle-light.png new file mode 100644 index 0000000..6c069bc Binary files /dev/null and b/client/public/textures/speckle-light.png differ diff --git a/client/public/textures/speckle.png b/client/public/textures/speckle.png new file mode 100644 index 0000000..91fb664 Binary files /dev/null and b/client/public/textures/speckle.png differ diff --git a/client/public/textures/wood-grain-light.png b/client/public/textures/wood-grain-light.png new file mode 100644 index 0000000..12d371e Binary files /dev/null and b/client/public/textures/wood-grain-light.png differ diff --git a/client/public/textures/wood-grain.png b/client/public/textures/wood-grain.png new file mode 100644 index 0000000..a983e63 Binary files /dev/null and b/client/public/textures/wood-grain.png differ diff --git a/client/src/App.module.css b/client/src/App.module.css index f7cf62c..3cfe0da 100644 --- a/client/src/App.module.css +++ b/client/src/App.module.css @@ -5,9 +5,21 @@ } body { - font-family: "Segoe UI", system-ui, -apple-system, sans-serif; - background: #1a1a2e; - color: #e0e0e0; + font-family: "Alegreya", Georgia, serif; + background-color: var(--bg-body); + background-image: + radial-gradient( + ellipse at center, + var(--bg-body-vignette) 0%, + transparent 70% + ), + var(--texture-body), var(--texture-speckle); + background-size: + 100% 100%, + 512px 512px, + 128px 128px; + background-repeat: no-repeat, repeat, repeat; + color: var(--text-primary); min-height: 100vh; } @@ -19,14 +31,42 @@ body { .header { text-align: center; - padding: 1rem 0; - border-bottom: 1px solid #333; + padding: 1.25rem 0; margin-bottom: 1.5rem; + position: relative; + z-index: 9999; +} + +.header::before, +.header::after { + content: ""; + display: block; + height: 1px; + background: linear-gradient( + 90deg, + transparent 10%, + rgba(var(--gold-rgb), 0.3) 35%, + rgba(var(--gold-rgb), 0.5) 50%, + rgba(var(--gold-rgb), 0.3) 65%, + transparent 90% + ); +} + +.header::before { + margin-bottom: 1rem; +} + +.header::after { + margin-top: 0.5rem; } .header h1 { - font-size: 1.8rem; - color: #c9a84c; + font-family: "Cinzel", Georgia, serif; + font-size: 2rem; + color: var(--gold); font-variant: small-caps; - letter-spacing: 0.05em; + letter-spacing: 0.15em; + text-shadow: + 0 0 30px rgba(var(--gold-rgb), 0.25), + 0 2px 4px rgba(var(--shadow-rgb), 0.5); } diff --git a/client/src/App.tsx b/client/src/App.tsx index c040e3f..662a9a8 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,6 +1,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import CampaignList from "./pages/CampaignList"; import CampaignView from "./pages/CampaignView"; +import ThemeToggle from "./components/ThemeToggle"; import styles from "./App.module.css"; export default function App() { @@ -9,6 +10,7 @@ export default function App() {

Shadowdark

+
} /> diff --git a/client/src/components/AcDisplay.module.css b/client/src/components/AcDisplay.module.css index c6b5304..7354455 100644 --- a/client/src/components/AcDisplay.module.css +++ b/client/src/components/AcDisplay.module.css @@ -5,28 +5,31 @@ } .label { + font-family: "Cinzel", Georgia, serif; font-size: 0.75rem; - color: #888; + color: var(--text-secondary); text-transform: uppercase; font-weight: 600; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .value { + font-family: "Cinzel", Georgia, serif; font-size: 1.4rem; font-weight: 700; - color: #5dade2; + color: var(--ac); cursor: pointer; min-width: 2rem; text-align: center; } .value.overridden { - color: #c9a84c; + color: var(--gold); } .source { font-size: 0.7rem; - color: #666; + color: var(--text-tertiary); } .override { @@ -37,30 +40,31 @@ .overrideIndicator { font-size: 0.65rem; - color: #c9a84c; + color: var(--gold); cursor: pointer; - background: rgba(201, 168, 76, 0.15); + background: rgba(var(--gold-rgb), 0.15); border: none; border-radius: 3px; padding: 0.1rem 0.3rem; } .overrideIndicator:hover { - background: rgba(201, 168, 76, 0.3); + background: rgba(var(--gold-rgb), 0.3); } .calculatedHint { font-size: 0.65rem; - color: #555; + color: var(--text-faint); } .editInput { width: 3rem; padding: 0.2rem 0.3rem; - background: #0f1a30; - border: 1px solid #c9a84c; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.2); border-radius: 4px; - color: #e0e0e0; + color: var(--text-primary); + font-family: "Cinzel", Georgia, serif; font-size: 1.2rem; font-weight: 700; text-align: center; diff --git a/client/src/components/AttackBlock.module.css b/client/src/components/AttackBlock.module.css index ecea745..3d02d71 100644 --- a/client/src/components/AttackBlock.module.css +++ b/client/src/components/AttackBlock.module.css @@ -3,12 +3,14 @@ } .title { + font-family: "Cinzel", Georgia, serif; font-size: 0.9rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .list { @@ -21,40 +23,43 @@ display: flex; justify-content: space-between; align-items: center; - background: #0f1a30; - border-radius: 6px; + background: var(--bg-inset); + border-radius: 4px; padding: 0.35rem 0.6rem; font-size: 0.85rem; + border-left: 2px solid rgba(var(--gold-rgb), 0.2); } .weaponName { + font-family: "Cinzel", Georgia, serif; font-weight: 700; text-transform: uppercase; - color: #e0e0e0; + color: var(--text-primary); + letter-spacing: 0.03em; } .stats { - color: #888; + color: var(--text-secondary); } .modifier { - color: #c9a84c; + color: var(--gold); font-weight: 600; } .damage { - color: #e0e0e0; + color: var(--text-primary); } .tags { font-size: 0.7rem; - color: #666; + color: var(--text-tertiary); margin-left: 0.3rem; } .talentLine { font-style: italic; - color: #888; + color: var(--text-secondary); font-size: 0.8rem; padding: 0.25rem 0.6rem; } @@ -62,7 +67,7 @@ .rollSpace { width: 2.5rem; text-align: center; - color: #444; + color: var(--text-faint); font-size: 0.75rem; } @@ -73,6 +78,6 @@ .empty { font-size: 0.8rem; - color: #555; + color: var(--text-faint); font-style: italic; } diff --git a/client/src/components/CharacterCard.module.css b/client/src/components/CharacterCard.module.css index ded79fd..6c687c2 100644 --- a/client/src/components/CharacterCard.module.css +++ b/client/src/components/CharacterCard.module.css @@ -1,17 +1,32 @@ .card { - background: #16213e; - border: 1px solid #333; - border-radius: 10px; + background-color: var(--bg-surface); + background-image: var(--texture-surface), var(--texture-speckle); + background-size: + 256px 256px, + 128px 128px; + background-repeat: repeat, repeat; + border: 1px solid rgba(var(--gold-rgb), 0.2); + border-radius: 4px; padding: 1rem; cursor: pointer; + box-shadow: + 0 4px 12px rgba(var(--shadow-rgb), 0.5), + 0 1px 4px rgba(var(--shadow-rgb), 0.3), + inset 0 1px 0 rgba(var(--gold-rgb), 0.06); transition: border-color 0.15s, - transform 0.1s; + transform 0.1s, + box-shadow 0.15s; } .card:hover { - border-color: #c9a84c; + border-color: rgba(var(--gold-rgb), 0.5); transform: translateY(-2px); + box-shadow: + 0 8px 20px rgba(var(--shadow-rgb), 0.6), + 0 2px 6px rgba(var(--shadow-rgb), 0.4), + inset 0 1px 0 rgba(var(--gold-rgb), 0.1), + 0 0 15px rgba(var(--gold-rgb), 0.05); } .cardHeader { @@ -22,19 +37,20 @@ } .name { + font-family: "Cinzel", Georgia, serif; font-size: 1.1rem; font-weight: 700; - color: #e0e0e0; + color: var(--text-primary); } .level { font-size: 0.8rem; - color: #888; + color: var(--text-secondary); } .meta { font-size: 0.8rem; - color: #666; + color: var(--text-tertiary); margin-bottom: 0.75rem; } @@ -53,26 +69,27 @@ } .acLabel { + font-family: "Cinzel", Georgia, serif; font-size: 0.75rem; - color: #888; + color: var(--text-secondary); text-transform: uppercase; font-weight: 600; } .acValue { font-size: 1.1rem; - color: #5dade2; + color: var(--ac); } .gearSummary { font-size: 0.75rem; - color: #666; + color: var(--text-tertiary); text-align: right; margin-top: 0.5rem; } .xp { font-size: 0.75rem; - color: #888; + color: var(--text-secondary); text-align: right; } diff --git a/client/src/components/CharacterDetail.module.css b/client/src/components/CharacterDetail.module.css index 92ddd33..82d0f94 100644 --- a/client/src/components/CharacterDetail.module.css +++ b/client/src/components/CharacterDetail.module.css @@ -1,7 +1,7 @@ .overlay { position: absolute; inset: 0; - background: rgba(0, 0, 0, 0.7); + background: var(--bg-overlay); display: flex; align-items: center; justify-content: center; @@ -10,9 +10,14 @@ } .modal { - background: #1a1a2e; - border: 1px solid #333; - border-radius: 12px; + background-color: var(--bg-modal); + background-image: var(--texture-surface), var(--texture-speckle); + background-size: + 256px 256px, + 128px 128px; + background-repeat: repeat, repeat; + border: 2px solid rgba(var(--gold-rgb), 0.3); + border-radius: 4px; width: 100%; max-width: 900px; max-height: 90vh; @@ -20,7 +25,12 @@ overflow-x: hidden; padding: 1.5rem; scrollbar-width: thin; - scrollbar-color: #333 transparent; + scrollbar-color: rgba(var(--gold-rgb), 0.2) transparent; + box-shadow: + 0 8px 40px rgba(var(--shadow-rgb), 0.7), + 0 2px 8px rgba(var(--shadow-rgb), 0.5), + inset 0 1px 0 rgba(var(--gold-rgb), 0.1), + inset 0 0 60px rgba(var(--shadow-rgb), 0.2); } .modal::-webkit-scrollbar { @@ -32,12 +42,12 @@ } .modal::-webkit-scrollbar-thumb { - background: #333; + background: rgba(var(--gold-rgb), 0.2); border-radius: 3px; } .modal::-webkit-scrollbar-thumb:hover { - background: #555; + background: rgba(var(--gold-rgb), 0.35); } .topBar { @@ -49,34 +59,39 @@ } .editBtn { + font-family: "Cinzel", Georgia, serif; padding: 0.35rem 0.75rem; background: transparent; - border: 1px solid #c9a84c; - border-radius: 5px; - color: #c9a84c; + border: 1px solid rgba(var(--gold-rgb), 0.4); + border-radius: 3px; + color: var(--gold); cursor: pointer; font-size: 0.8rem; font-weight: 600; + letter-spacing: 0.05em; + transition: all 0.15s; } .editBtn:hover { - background: rgba(201, 168, 76, 0.15); + background: rgba(var(--gold-rgb), 0.1); + box-shadow: 0 0 8px rgba(var(--gold-rgb), 0.15); } .editBtn.active { - background: #c9a84c; - color: #1a1a2e; + background: rgba(var(--gold-rgb), 0.9); + color: var(--btn-active-text); + text-shadow: none; } .closeBtn { background: none; border: none; - color: #888; + color: var(--text-secondary); font-size: 1.5rem; cursor: pointer; padding: 0.25rem 0.5rem; } .closeBtn:hover { - color: #e0e0e0; + color: var(--text-primary); } diff --git a/client/src/components/CharacterSheet.module.css b/client/src/components/CharacterSheet.module.css index 8086390..89d8432 100644 --- a/client/src/components/CharacterSheet.module.css +++ b/client/src/components/CharacterSheet.module.css @@ -2,11 +2,20 @@ display: flex; justify-content: space-between; align-items: center; - background: linear-gradient(135deg, #16213e, #0f1a30); - border: 1px solid #333; - border-radius: 8px; + background-color: var(--bg-surface); + background-image: var(--texture-surface), var(--texture-speckle); + background-size: + 256px 256px, + 128px 128px; + background-repeat: repeat, repeat; + border: 1px solid rgba(var(--gold-rgb), 0.3); + border-radius: 4px; padding: 0.75rem 1rem; margin-bottom: 1rem; + box-shadow: + 0 3px 12px rgba(var(--shadow-rgb), 0.4), + inset 0 1px 0 rgba(var(--gold-rgb), 0.1), + inset 0 0 20px rgba(var(--shadow-rgb), 0.15); } .identity { @@ -14,18 +23,20 @@ } .name { + font-family: "Cinzel", Georgia, serif; font-size: 1.4rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); + letter-spacing: 0.03em; } .title { - color: #888; + color: var(--text-secondary); font-size: 0.9rem; } .subtitle { - color: #666; + color: var(--text-tertiary); font-size: 0.8rem; margin-top: 0.15rem; } @@ -46,16 +57,17 @@ } .hp { - color: #4caf50; + color: var(--hp); } .ac { - color: #5dade2; + color: var(--ac); } .vitalLabel { + font-family: "Cinzel", Georgia, serif; font-size: 0.75rem; - color: #888; + color: var(--text-secondary); text-transform: uppercase; font-weight: 600; } @@ -67,37 +79,38 @@ } .hpSlash { - color: #666; + color: var(--text-tertiary); font-size: 0.9rem; } .hpMax { - color: #888; + color: var(--text-secondary); font-weight: 600; } .hpBonus { - color: #4caf50; + color: var(--hp); font-size: 0.65rem; margin-left: 0.15rem; } .xpThreshold { font-size: 0.75rem; - color: #666; + color: var(--text-tertiary); } .xpCurrent { - color: #c9a84c; + color: var(--gold); font-weight: 600; } .nameInput { + font-family: "Cinzel", Georgia, serif; font-size: 1.3rem; font-weight: 700; - color: #c9a84c; - background: #0f1a30; - border: 1px solid #333; + color: var(--gold); + background: var(--bg-input); + border: 1px solid rgba(var(--gold-rgb), 0.2); border-radius: 5px; padding: 0.2rem 0.4rem; width: 10rem; @@ -105,23 +118,24 @@ .nameInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .titleInput { font-size: 0.85rem; - color: #888; - background: #0f1a30; - border: 1px solid #333; + color: var(--text-secondary); + background: var(--bg-input); + border: 1px solid rgba(var(--gold-rgb), 0.2); border-radius: 5px; padding: 0.15rem 0.4rem; width: 8rem; margin-left: 0.3rem; + font-family: "Alegreya", Georgia, serif; } .titleInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .panels { @@ -145,19 +159,19 @@ .deleteSection { margin-top: 1rem; padding-top: 0.75rem; - border-top: 1px solid #333; + border-top: 1px solid rgba(var(--gold-rgb), 0.15); } .deleteBtn { padding: 0.4rem 0.75rem; background: transparent; - border: 1px solid #e74c3c; + border: 1px solid var(--danger); border-radius: 5px; - color: #e74c3c; + color: var(--danger); cursor: pointer; font-size: 0.8rem; } .deleteBtn:hover { - background: rgba(231, 76, 60, 0.1); + background: rgba(var(--danger-rgb), 0.1); } diff --git a/client/src/components/CurrencyRow.module.css b/client/src/components/CurrencyRow.module.css index 0810c9b..d4e8ea3 100644 --- a/client/src/components/CurrencyRow.module.css +++ b/client/src/components/CurrencyRow.module.css @@ -12,44 +12,47 @@ } .coinLabel { + font-family: "Cinzel", Georgia, serif; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .gp { - color: #c9a84c; + color: var(--gold); } .sp { - color: #a0a0a0; + color: var(--silver); } .cp { - color: #b87333; + color: var(--copper); } .coinInput { width: 3.5rem; padding: 0.25rem 0.4rem; - background: #0f1a30; - border: 1px solid #333; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); border-radius: 4px; - color: #e0e0e0; + color: var(--text-primary); font-size: 0.85rem; text-align: center; + font-family: "Alegreya", Georgia, serif; } .coinInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .coinBtn { width: 20px; height: 20px; border-radius: 50%; - border: 1px solid #444; - background: #16213e; - color: #e0e0e0; + border: 1px solid rgba(var(--gold-rgb), 0.25); + background: var(--bg-inset); + color: var(--text-primary); cursor: pointer; font-size: 0.75rem; display: flex; @@ -58,8 +61,8 @@ } .coinBtn:hover { - border-color: #c9a84c; - color: #c9a84c; + border-color: var(--gold); + color: var(--gold); } .coinValue { diff --git a/client/src/components/DiceButton.module.css b/client/src/components/DiceButton.module.css index f939e30..68074d9 100644 --- a/client/src/components/DiceButton.module.css +++ b/client/src/components/DiceButton.module.css @@ -2,9 +2,9 @@ width: 22px; height: 22px; border-radius: 4px; - border: 1px solid #444; - background: #16213e; - color: #888; + border: 1px solid rgba(var(--gold-rgb), 0.25); + background: var(--bg-inset); + color: var(--text-secondary); cursor: pointer; font-size: 0.75rem; display: flex; @@ -13,31 +13,37 @@ transition: border-color 0.15s, color 0.15s, - background 0.15s; + background 0.15s, + box-shadow 0.15s; + box-shadow: inset 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .btn:hover { - border-color: #c9a84c; - color: #c9a84c; - background: rgba(201, 168, 76, 0.1); + border-color: rgba(var(--gold-rgb), 0.6); + color: var(--gold); + background: rgba(var(--gold-rgb), 0.08); + box-shadow: + 0 0 6px rgba(var(--gold-rgb), 0.15), + inset 0 1px 2px rgba(var(--shadow-rgb), 0.2); } .btn:active { - background: rgba(201, 168, 76, 0.25); + background: rgba(var(--gold-rgb), 0.15); } .btn.crit { - border-color: #ffd700; - color: #ffd700; + border-color: var(--crit); + color: var(--crit); animation: critPulse 1s ease-in-out infinite; + box-shadow: 0 0 8px rgba(var(--crit-rgb), 0.3); } @keyframes critPulse { 0%, 100% { - box-shadow: 0 0 4px rgba(255, 215, 0, 0.3); + box-shadow: 0 0 4px rgba(var(--crit-rgb), 0.3); } 50% { - box-shadow: 0 0 8px rgba(255, 215, 0, 0.6); + box-shadow: 0 0 12px rgba(var(--crit-rgb), 0.6); } } diff --git a/client/src/components/GearList.module.css b/client/src/components/GearList.module.css index 0ffdfe8..c04c365 100644 --- a/client/src/components/GearList.module.css +++ b/client/src/components/GearList.module.css @@ -10,11 +10,13 @@ } .title { + font-family: "Cinzel", Georgia, serif; font-size: 0.9rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.1em; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .slotCounter { @@ -23,13 +25,13 @@ } .slotCounter.normal { - color: #4caf50; + color: var(--hp); } .slotCounter.warning { - color: #ff9800; + color: var(--warning); } .slotCounter.over { - color: #e74c3c; + color: var(--danger); } .table { @@ -38,13 +40,15 @@ } .tableHeader { + font-family: "Cinzel", Georgia, serif; font-size: 0.7rem; - color: #666; + color: var(--text-tertiary); text-transform: uppercase; font-weight: 600; text-align: left; padding: 0.25rem 0.5rem; - border-bottom: 1px solid #333; + border-bottom: 1px solid rgba(var(--gold-rgb), 0.2); + letter-spacing: 0.05em; } .right { @@ -55,11 +59,11 @@ } .row { - border-bottom: 1px solid #222; + border-bottom: 1px solid rgba(var(--gold-rgb), 0.06); } .row:hover { - background: rgba(201, 168, 76, 0.05); + background: rgba(var(--gold-rgb), 0.06); } .cell { @@ -75,14 +79,14 @@ .removeBtn { background: none; border: none; - color: #555; + color: var(--text-faint); cursor: pointer; font-size: 0.9rem; padding: 0.1rem 0.3rem; } .removeBtn:hover { - color: #e74c3c; + color: var(--danger); } .addArea { @@ -91,25 +95,34 @@ .addBtn { padding: 0.4rem 0.75rem; - background: #c9a84c; - color: #1a1a2e; + background: var(--btn-gold-bg); + color: var(--btn-active-text); border: none; - border-radius: 6px; + border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.8rem; + box-shadow: + 0 2px 4px rgba(var(--shadow-rgb), 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + text-shadow: 0 1px 1px rgba(var(--shadow-rgb), 0.2); } .addBtn:hover { - background: #d4b65a; + background: linear-gradient( + 180deg, + var(--gold-bright), + var(--gold-hover) 40%, + var(--gold) + ); } .cancelBtn { padding: 0.4rem 0.75rem; - background: #333; - color: #888; - border: none; - border-radius: 6px; + background: var(--bg-inset); + color: var(--text-secondary); + border: 1px solid rgba(var(--gold-rgb), 0.15); + border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.8rem; @@ -124,30 +137,32 @@ .customInput { flex: 1; padding: 0.4rem 0.6rem; - background: #0f1a30; - border: 1px solid #333; - border-radius: 6px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); + border-radius: 4px; + color: var(--text-primary); font-size: 0.85rem; + font-family: "Alegreya", Georgia, serif; } .customInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .customSelect { padding: 0.4rem 0.6rem; - background: #0f1a30; - border: 1px solid #333; - border-radius: 6px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); + border-radius: 4px; + color: var(--text-primary); font-size: 0.85rem; + font-family: "Alegreya", Georgia, serif; } .empty { font-size: 0.8rem; - color: #555; + color: var(--text-faint); font-style: italic; padding: 0.5rem; } diff --git a/client/src/components/GearPanel.module.css b/client/src/components/GearPanel.module.css index 2f78a99..a4c509c 100644 --- a/client/src/components/GearPanel.module.css +++ b/client/src/components/GearPanel.module.css @@ -1,6 +1,16 @@ .panel { - background: #16213e; - border: 1px solid #333; - border-radius: 8px; + background-color: var(--bg-surface); + background-image: var(--texture-surface), var(--texture-speckle); + background-size: + 256px 256px, + 128px 128px; + background-repeat: repeat, repeat; + border: 1px solid rgba(var(--gold-rgb), 0.2); + border-radius: 4px; padding: 0.75rem; + box-shadow: + 0 4px 16px rgba(var(--shadow-rgb), 0.5), + 0 1px 4px rgba(var(--shadow-rgb), 0.4), + inset 0 1px 0 rgba(var(--gold-rgb), 0.06), + inset 0 0 30px rgba(var(--shadow-rgb), 0.15); } diff --git a/client/src/components/HpBar.module.css b/client/src/components/HpBar.module.css index d4618ad..eb9e842 100644 --- a/client/src/components/HpBar.module.css +++ b/client/src/components/HpBar.module.css @@ -5,10 +5,12 @@ } .label { + font-family: "Cinzel", Georgia, serif; font-size: 0.75rem; - color: #888; + color: var(--text-secondary); text-transform: uppercase; font-weight: 600; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .values { @@ -20,32 +22,32 @@ } .current { - color: #4caf50; + color: var(--hp); } .current.hurt { - color: #ff9800; + color: var(--warning); } .current.critical { - color: #e74c3c; + color: var(--danger); } .slash { - color: #666; + color: var(--text-tertiary); } .max { - color: #888; + color: var(--text-secondary); } .btn { width: 24px; height: 24px; border-radius: 50%; - border: 1px solid #444; - background: #16213e; - color: #e0e0e0; + border: 1px solid rgba(var(--gold-rgb), 0.25); + background: var(--bg-inset); + color: var(--text-primary); cursor: pointer; font-size: 0.9rem; display: flex; @@ -55,6 +57,6 @@ } .btn:hover { - border-color: #c9a84c; - color: #c9a84c; + border-color: var(--gold); + color: var(--gold); } diff --git a/client/src/components/HpDisplay.module.css b/client/src/components/HpDisplay.module.css index 5c11c5a..d7e7d93 100644 --- a/client/src/components/HpDisplay.module.css +++ b/client/src/components/HpDisplay.module.css @@ -14,33 +14,35 @@ .current { font-size: 1.4rem; font-weight: 700; - color: #4caf50; + color: var(--hp); } .current.hurt { - color: #ff9800; + color: var(--warning); } .current.critical { - color: #e74c3c; + color: var(--danger); } .slash { - color: #666; + color: var(--text-tertiary); font-size: 0.9rem; } .max { - color: #888; + color: var(--text-secondary); font-weight: 600; font-size: 1rem; } .label { + font-family: "Cinzel", Georgia, serif; font-size: 0.65rem; - color: #888; + color: var(--text-secondary); text-transform: uppercase; font-weight: 600; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .buttons { @@ -51,9 +53,9 @@ .healBtn { padding: 0.15rem 0.5rem; background: rgba(76, 175, 80, 0.15); - border: 1px solid #4caf50; + border: 1px solid var(--hp); border-radius: 4px; - color: #4caf50; + color: var(--hp); cursor: pointer; font-size: 0.65rem; font-weight: 600; @@ -66,10 +68,10 @@ .dmgBtn { padding: 0.15rem 0.5rem; - background: rgba(231, 76, 60, 0.15); - border: 1px solid #e74c3c; + background: rgba(var(--danger-rgb), 0.15); + border: 1px solid var(--danger); border-radius: 4px; - color: #e74c3c; + color: var(--danger); cursor: pointer; font-size: 0.65rem; font-weight: 600; @@ -77,7 +79,7 @@ } .dmgBtn:hover { - background: rgba(231, 76, 60, 0.3); + background: rgba(var(--danger-rgb), 0.3); } .inputRow { @@ -89,10 +91,10 @@ .amountInput { width: 2.5rem; padding: 0.2rem 0.3rem; - background: #0f1a30; - border: 1px solid #444; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.2); border-radius: 4px; - color: #e0e0e0; + color: var(--text-primary); font-size: 0.85rem; text-align: center; font-weight: 600; @@ -103,11 +105,11 @@ } .amountInput.healing { - border-color: #4caf50; + border-color: var(--hp); } .amountInput.damage { - border-color: #e74c3c; + border-color: var(--danger); } .applyBtn { @@ -120,24 +122,24 @@ } .applyBtn.healing { - background: #4caf50; - color: #1a1a2e; + background: var(--hp); + color: var(--btn-active-text); } .applyBtn.damage { - background: #e74c3c; + background: var(--danger); color: #fff; } .cancelBtn { background: none; border: none; - color: #666; + color: var(--text-tertiary); cursor: pointer; font-size: 0.85rem; padding: 0 0.2rem; } .cancelBtn:hover { - color: #e0e0e0; + color: var(--text-primary); } diff --git a/client/src/components/InfoPanel.module.css b/client/src/components/InfoPanel.module.css index 733510c..251a023 100644 --- a/client/src/components/InfoPanel.module.css +++ b/client/src/components/InfoPanel.module.css @@ -1,17 +1,29 @@ .panel { - background: #16213e; - border: 1px solid #333; - border-radius: 8px; + background-color: var(--bg-surface); + background-image: var(--texture-surface), var(--texture-speckle); + background-size: + 256px 256px, + 128px 128px; + background-repeat: repeat, repeat; + border: 1px solid rgba(var(--gold-rgb), 0.2); + border-radius: 4px; padding: 0.75rem; + box-shadow: + 0 4px 16px rgba(var(--shadow-rgb), 0.5), + 0 1px 4px rgba(var(--shadow-rgb), 0.4), + inset 0 1px 0 rgba(var(--gold-rgb), 0.06), + inset 0 0 30px rgba(var(--shadow-rgb), 0.15); } .sectionTitle { + font-family: "Cinzel", Georgia, serif; font-size: 0.8rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.1em; margin-bottom: 0.5rem; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .infoGrid { @@ -28,15 +40,17 @@ } .infoLabel { - color: #666; + font-family: "Cinzel", Georgia, serif; + color: var(--text-tertiary); font-size: 0.7rem; text-transform: uppercase; font-weight: 600; min-width: 5rem; + letter-spacing: 0.03em; } .infoValue { - color: #e0e0e0; + color: var(--text-primary); } .notes { @@ -48,26 +62,28 @@ .editField { padding: 0.3rem 0.5rem; - background: #0f1a30; - border: 1px solid #333; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); border-radius: 5px; - color: #e0e0e0; + color: var(--text-primary); font-size: 0.85rem; width: 100%; + font-family: "Alegreya", Georgia, serif; } .editField:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .editSelect { padding: 0.3rem 0.5rem; - background: #0f1a30; - border: 1px solid #333; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); border-radius: 5px; - color: #e0e0e0; + color: var(--text-primary); font-size: 0.85rem; + font-family: "Alegreya", Georgia, serif; } .editRow { @@ -87,26 +103,28 @@ } .fieldLabel { + font-family: "Cinzel", Georgia, serif; font-size: 0.65rem; - color: #666; + color: var(--text-tertiary); text-transform: uppercase; font-weight: 600; + letter-spacing: 0.03em; } .notesEdit { width: 100%; min-height: 50px; padding: 0.4rem; - background: #0f1a30; - border: 1px solid #333; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); border-radius: 5px; - color: #e0e0e0; + color: var(--text-primary); font-size: 0.85rem; - font-family: inherit; + font-family: "Alegreya", Georgia, serif; resize: vertical; } .notesEdit:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } diff --git a/client/src/components/InlineNumber.module.css b/client/src/components/InlineNumber.module.css index ba5ac66..732e3db 100644 --- a/client/src/components/InlineNumber.module.css +++ b/client/src/components/InlineNumber.module.css @@ -5,16 +5,16 @@ } .value:hover { - border-bottom-color: #c9a84c; + border-bottom-color: var(--gold); } .input { width: 3rem; padding: 0.1rem 0.2rem; - background: #0f1a30; - border: 1px solid #c9a84c; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.3); border-radius: 4px; - color: #e0e0e0; + color: var(--text-primary); font-size: inherit; font-weight: inherit; text-align: center; diff --git a/client/src/components/ItemPicker.module.css b/client/src/components/ItemPicker.module.css index 37cd520..e6c49cb 100644 --- a/client/src/components/ItemPicker.module.css +++ b/client/src/components/ItemPicker.module.css @@ -5,16 +5,17 @@ .searchInput { width: 100%; padding: 0.5rem 0.75rem; - background: #0f1a30; - border: 1px solid #333; - border-radius: 6px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.25); + border-radius: 4px; + color: var(--text-primary); font-size: 0.85rem; + font-family: "Alegreya", Georgia, serif; } .searchInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .dropdown { @@ -24,12 +25,22 @@ right: 0; max-height: 220px; overflow-y: auto; - background: #16213e; - border: 1px solid #444; - border-radius: 6px; + background: + repeating-linear-gradient( + 0deg, + transparent, + transparent 3px, + rgba(180, 155, 100, 0.02) 3px, + rgba(180, 155, 100, 0.02) 4px + ), + linear-gradient(175deg, #221e18 0%, #1e1b16 50%, #201c17 100%); + border: 1px solid rgba(var(--gold-rgb), 0.25); + border-radius: 4px; margin-bottom: 0.25rem; z-index: 200; - box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.5); + box-shadow: + 0 -4px 20px rgba(var(--shadow-rgb), 0.5), + 0 -2px 8px rgba(var(--shadow-rgb), 0.4); } .group { @@ -37,12 +48,14 @@ } .groupLabel { + font-family: "Cinzel", Georgia, serif; font-size: 0.7rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); text-transform: uppercase; padding: 0.25rem 0.75rem; - letter-spacing: 0.05em; + letter-spacing: 0.08em; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .item { @@ -54,15 +67,15 @@ } .item:hover { - background: rgba(201, 168, 76, 0.15); + background: rgba(var(--gold-rgb), 0.12); } .itemName { - color: #e0e0e0; + color: var(--text-primary); } .itemMeta { - color: #666; + color: var(--text-tertiary); font-size: 0.75rem; } @@ -70,12 +83,12 @@ padding: 0.5rem 0.75rem; cursor: pointer; font-size: 0.85rem; - color: #888; + color: var(--text-secondary); font-style: italic; - border-top: 1px solid #333; + border-top: 1px solid rgba(var(--gold-rgb), 0.1); } .customOption:hover { - background: rgba(201, 168, 76, 0.15); - color: #c9a84c; + background: rgba(var(--gold-rgb), 0.12); + color: var(--gold); } diff --git a/client/src/components/RollEntry.module.css b/client/src/components/RollEntry.module.css index dde812c..d014ed5 100644 --- a/client/src/components/RollEntry.module.css +++ b/client/src/components/RollEntry.module.css @@ -1,11 +1,15 @@ .card { - background: #0f1a30; - border: 1px solid #2a2a4a; - border-radius: 6px; + background-color: var(--bg-roll-entry); + background-image: var(--texture-surface); + background-size: 256px 256px; + background-repeat: repeat; + border: 1px solid rgba(var(--gold-rgb), 0.1); + border-radius: 4px; padding: 0.5rem 0.6rem; animation: slideIn 0.3s ease-out; border-left-width: 3px; border-left-style: solid; + box-shadow: 0 2px 6px rgba(var(--shadow-rgb), 0.3); } @keyframes slideIn { @@ -20,16 +24,23 @@ } .card.nat20 { - border-color: #ffd700; - background: linear-gradient(135deg, #1a1a0e, #0f1a30); + border-color: var(--crit); + background: linear-gradient( + 135deg, + rgba(40, 35, 15, 0.9), + rgba(24, 21, 16, 0.9) + ); + box-shadow: + 0 0 12px rgba(var(--crit-rgb), 0.15), + 0 2px 6px rgba(var(--shadow-rgb), 0.3); } .card.nat20 .total { - color: #ffd700; + color: var(--crit); } .card.fresh { - border-color: #c9a84c; + border-color: var(--gold); animation: slideIn 0.3s ease-out, glow 1s ease-out; @@ -37,7 +48,7 @@ @keyframes glow { 0% { - box-shadow: 0 0 8px rgba(201, 168, 76, 0.4); + box-shadow: 0 0 8px rgba(var(--gold-rgb), 0.4); } 100% { box-shadow: none; @@ -52,75 +63,78 @@ } .charName { + font-family: "Cinzel", Georgia, serif; font-weight: 700; font-size: 0.8rem; - color: #c9a84c; + color: var(--gold); + letter-spacing: 0.03em; } .timestamp { font-size: 0.65rem; - color: #555; + color: var(--text-faint); } .label { font-size: 0.75rem; - color: #888; + color: var(--text-secondary); margin-bottom: 0.2rem; } .breakdown { font-size: 0.75rem; - color: #666; + color: var(--text-tertiary); margin-bottom: 0.15rem; } .dieResult { - color: #e0e0e0; + color: var(--text-primary); font-weight: 600; } .dieChosen { - color: #c9a84c; + color: var(--gold); font-weight: 700; } .dieDiscarded { - color: #555; + color: var(--text-faint); text-decoration: line-through; } .modLine { font-size: 0.7rem; - color: #666; + color: var(--text-tertiary); } .total { font-size: 1.3rem; font-weight: 700; - color: #e0e0e0; + color: var(--text-primary); text-align: center; margin-top: 0.2rem; } .advantage { - color: #4caf50; + color: var(--hp); font-size: 0.65rem; font-weight: 600; text-transform: uppercase; } .disadvantage { - color: #e74c3c; + color: var(--danger); font-size: 0.65rem; font-weight: 600; text-transform: uppercase; } .critBanner { + font-family: "Cinzel", Georgia, serif; text-align: center; font-size: 0.75rem; font-weight: 700; - color: #ffd700; + color: var(--crit); text-transform: uppercase; letter-spacing: 0.1em; padding: 0.15rem 0; diff --git a/client/src/components/RollLog.module.css b/client/src/components/RollLog.module.css index 83389ca..a3b20a5 100644 --- a/client/src/components/RollLog.module.css +++ b/client/src/components/RollLog.module.css @@ -1,14 +1,18 @@ .panel { width: 300px; height: 100%; - background: #1a1a2e; - border-left: 1px solid #333; + background-color: var(--bg-roll-log); + background-image: var(--texture-surface); + background-size: 256px 256px; + background-repeat: repeat; + border-left: 2px solid rgba(var(--gold-rgb), 0.2); display: flex; flex-direction: column; transition: width 0.2s; flex-shrink: 0; z-index: 150; position: relative; + box-shadow: -4px 0 16px rgba(var(--shadow-rgb), 0.3); } .panel.collapsed { @@ -21,28 +25,30 @@ justify-content: space-between; align-items: center; padding: 0.5rem 0.75rem; - border-bottom: 1px solid #333; + border-bottom: 1px solid rgba(var(--gold-rgb), 0.15); } .title { + font-family: "Cinzel", Georgia, serif; font-size: 0.85rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.1em; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .collapseBtn { background: none; border: none; - color: #888; + color: var(--text-secondary); cursor: pointer; font-size: 1rem; padding: 0.2rem; } .collapseBtn:hover { - color: #c9a84c; + color: var(--gold); } .collapsedContent { @@ -61,34 +67,35 @@ .collapsedLast { writing-mode: vertical-rl; font-size: 0.7rem; - color: #888; + color: var(--text-secondary); max-height: 100px; overflow: hidden; } .inputArea { padding: 0.5rem 0.75rem; - border-bottom: 1px solid #333; + border-bottom: 1px solid rgba(var(--gold-rgb), 0.15); } .input { width: 100%; padding: 0.4rem 0.6rem; - background: #0f1a30; - border: 1px solid #333; - border-radius: 6px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); + border-radius: 4px; + color: var(--text-primary); font-size: 0.85rem; + font-family: "Alegreya", Georgia, serif; } .input:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .hint { font-size: 0.6rem; - color: #555; + color: var(--text-faint); margin-top: 0.25rem; text-align: center; } @@ -101,7 +108,7 @@ flex-direction: column; gap: 0.4rem; scrollbar-width: thin; - scrollbar-color: #333 transparent; + scrollbar-color: rgba(var(--gold-rgb), 0.15) transparent; } .entries::-webkit-scrollbar { @@ -109,13 +116,13 @@ } .entries::-webkit-scrollbar-thumb { - background: #333; + background: rgba(var(--gold-rgb), 0.15); border-radius: 2px; } .empty { text-align: center; - color: #555; + color: var(--text-faint); font-size: 0.8rem; font-style: italic; padding: 2rem 0; @@ -126,7 +133,7 @@ width: 100%; height: 200px; border-left: none; - border-top: 1px solid #333; + border-top: 2px solid rgba(var(--gold-rgb), 0.2); } .panel.collapsed { diff --git a/client/src/components/StatBlock.module.css b/client/src/components/StatBlock.module.css index 2543972..5ed388f 100644 --- a/client/src/components/StatBlock.module.css +++ b/client/src/components/StatBlock.module.css @@ -8,20 +8,23 @@ display: flex; flex-direction: column; align-items: center; - background: #0f1a30; - border: 1px solid #2a2a4a; - border-radius: 8px; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.12); + border-radius: 4px; padding: 0.5rem 0.3rem; position: relative; + box-shadow: inset 0 2px 4px rgba(var(--shadow-rgb), 0.3); } .statName { + font-family: "Cinzel", Georgia, serif; font-size: 0.7rem; - color: #c9a84c; + color: var(--gold); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.15rem; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .statRow { @@ -33,22 +36,22 @@ .modifier { font-size: 1.3rem; font-weight: 700; - color: #e0e0e0; + color: var(--text-primary); min-width: 2.2rem; text-align: center; } .score { font-size: 0.75rem; - color: #666; - background: #1a1a2e; + color: var(--text-tertiary); + background: var(--bg-inset); border-radius: 3px; padding: 0 0.3rem; margin-top: 0.15rem; } .bonus { - color: #4caf50; + color: var(--hp); font-size: 0.65rem; } @@ -56,9 +59,9 @@ width: 22px; height: 22px; border-radius: 50%; - border: 1px solid #444; - background: #16213e; - color: #e0e0e0; + border: 1px solid rgba(var(--gold-rgb), 0.25); + background: var(--bg-inset); + color: var(--text-primary); cursor: pointer; font-size: 0.8rem; display: flex; @@ -71,8 +74,8 @@ } .btn:hover { - border-color: #c9a84c; - color: #c9a84c; + border-color: var(--gold); + color: var(--gold); } .rollSpace { diff --git a/client/src/components/StatsPanel.module.css b/client/src/components/StatsPanel.module.css index 9216725..caf6dc2 100644 --- a/client/src/components/StatsPanel.module.css +++ b/client/src/components/StatsPanel.module.css @@ -1,21 +1,41 @@ .panel { - background: #16213e; - border: 1px solid #333; - border-radius: 8px; + background-color: var(--bg-surface); + background-image: var(--texture-surface), var(--texture-speckle); + background-size: + 256px 256px, + 128px 128px; + background-repeat: repeat, repeat; + border: 1px solid rgba(var(--gold-rgb), 0.2); + border-radius: 4px; padding: 0.75rem; + box-shadow: + 0 4px 16px rgba(var(--shadow-rgb), 0.5), + 0 1px 4px rgba(var(--shadow-rgb), 0.4), + inset 0 1px 0 rgba(var(--gold-rgb), 0.06), + inset 0 0 30px rgba(var(--shadow-rgb), 0.15); } .sectionTitle { + font-family: "Cinzel", Georgia, serif; font-size: 0.8rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.1em; margin-bottom: 0.5rem; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .separator { border: none; - border-top: 1px solid #2a2a4a; + height: 1px; + background: linear-gradient( + 90deg, + transparent 5%, + rgba(var(--gold-rgb), 0.3) 30%, + rgba(var(--gold-rgb), 0.5) 50%, + rgba(var(--gold-rgb), 0.3) 70%, + transparent 95% + ); margin: 0.75rem 0; } diff --git a/client/src/components/TalentList.module.css b/client/src/components/TalentList.module.css index 7b399d7..642dfa8 100644 --- a/client/src/components/TalentList.module.css +++ b/client/src/components/TalentList.module.css @@ -10,11 +10,13 @@ } .sectionTitle { + font-family: "Cinzel", Georgia, serif; font-size: 0.9rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.1em; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .list { @@ -27,8 +29,9 @@ display: flex; justify-content: space-between; align-items: flex-start; - background: #0f1a30; - border-radius: 6px; + background: rgba(var(--shadow-rgb), 0.2); + border-left: 2px solid rgba(var(--gold-rgb), 0.15); + border-radius: 4px; padding: 0.5rem 0.75rem; } @@ -43,20 +46,20 @@ .itemDesc { font-size: 0.75rem; - color: #888; + color: var(--text-secondary); } .removeBtn { background: none; border: none; - color: #666; + color: var(--text-tertiary); cursor: pointer; font-size: 1rem; padding: 0.25rem; } .removeBtn:hover { - color: #e74c3c; + color: var(--danger); } .addForm { @@ -68,36 +71,46 @@ .addInput { flex: 1; padding: 0.4rem 0.6rem; - background: #0f1a30; - border: 1px solid #333; - border-radius: 6px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); + border-radius: 4px; + color: var(--text-primary); font-size: 0.85rem; + font-family: "Alegreya", Georgia, serif; } .addInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .addBtn { padding: 0.4rem 0.75rem; - background: #c9a84c; - color: #1a1a2e; + background: var(--btn-gold-bg); + color: var(--btn-active-text); border: none; - border-radius: 6px; + border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.85rem; + box-shadow: + 0 2px 4px rgba(var(--shadow-rgb), 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + text-shadow: 0 1px 1px rgba(var(--shadow-rgb), 0.2); } .addBtn:hover { - background: #d4b65a; + background: linear-gradient( + 180deg, + var(--gold-bright), + var(--gold-hover) 40%, + var(--gold) + ); } .empty { font-size: 0.8rem; - color: #555; + color: var(--text-faint); font-style: italic; padding: 0.5rem 0; } diff --git a/client/src/components/TalentPicker.module.css b/client/src/components/TalentPicker.module.css index 38f1dfc..a9f8bd9 100644 --- a/client/src/components/TalentPicker.module.css +++ b/client/src/components/TalentPicker.module.css @@ -5,16 +5,17 @@ .searchInput { width: 100%; padding: 0.5rem 0.75rem; - background: #0f1a30; - border: 1px solid #333; - border-radius: 6px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.25); + border-radius: 4px; + color: var(--text-primary); font-size: 0.85rem; + font-family: "Alegreya", Georgia, serif; } .searchInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .dropdown { @@ -24,12 +25,22 @@ right: 0; max-height: 220px; overflow-y: auto; - background: #16213e; - border: 1px solid #444; - border-radius: 6px; + background: + repeating-linear-gradient( + 0deg, + transparent, + transparent 3px, + rgba(180, 155, 100, 0.02) 3px, + rgba(180, 155, 100, 0.02) 4px + ), + linear-gradient(175deg, #221e18 0%, #1e1b16 50%, #201c17 100%); + border: 1px solid rgba(var(--gold-rgb), 0.25); + border-radius: 4px; margin-bottom: 0.25rem; z-index: 200; - box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.5); + box-shadow: + 0 -4px 20px rgba(var(--shadow-rgb), 0.5), + 0 -2px 8px rgba(var(--shadow-rgb), 0.4); } .group { @@ -37,12 +48,14 @@ } .groupLabel { + font-family: "Cinzel", Georgia, serif; font-size: 0.7rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); text-transform: uppercase; padding: 0.25rem 0.75rem; - letter-spacing: 0.05em; + letter-spacing: 0.08em; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .item { @@ -54,16 +67,16 @@ } .item:hover { - background: rgba(201, 168, 76, 0.15); + background: rgba(var(--gold-rgb), 0.12); } .itemName { - color: #e0e0e0; + color: var(--text-primary); font-weight: 600; } .itemDesc { - color: #888; + color: var(--text-secondary); font-size: 0.75rem; } @@ -71,12 +84,12 @@ padding: 0.5rem 0.75rem; cursor: pointer; font-size: 0.85rem; - color: #888; + color: var(--text-secondary); font-style: italic; - border-top: 1px solid #333; + border-top: 1px solid rgba(var(--gold-rgb), 0.1); } .customOption:hover { - background: rgba(201, 168, 76, 0.15); - color: #c9a84c; + background: rgba(var(--gold-rgb), 0.12); + color: var(--gold); } diff --git a/client/src/components/ThemeToggle.module.css b/client/src/components/ThemeToggle.module.css new file mode 100644 index 0000000..d4919ff --- /dev/null +++ b/client/src/components/ThemeToggle.module.css @@ -0,0 +1,76 @@ +.container { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + z-index: 9999; +} + +.trigger { + background: none; + border: 1px solid rgba(var(--gold-rgb), 0.25); + border-radius: 4px; + padding: 0.3rem 0.5rem; + cursor: pointer; + font-size: 1rem; + line-height: 1; + transition: + border-color 0.15s, + box-shadow 0.15s; +} + +.trigger:hover { + border-color: rgba(var(--gold-rgb), 0.5); + box-shadow: 0 0 6px rgba(var(--gold-rgb), 0.15); +} + +.dropdown { + position: absolute; + top: 100%; + right: 0; + margin-top: 0.35rem; + background-color: var(--bg-modal); + background-image: var(--texture-surface); + background-size: 256px 256px; + background-repeat: repeat; + border: 1px solid rgba(var(--gold-rgb), 0.3); + border-radius: 4px; + padding: 0.3rem; + min-width: 10rem; + z-index: 9999; + box-shadow: + 0 4px 16px rgba(var(--shadow-rgb), 0.5), + inset 0 1px 0 rgba(var(--gold-rgb), 0.06); +} + +.option { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.4rem 0.6rem; + background: none; + border: none; + border-radius: 3px; + color: var(--text-primary); + font-family: "Alegreya", Georgia, serif; + font-size: 0.85rem; + cursor: pointer; + text-align: left; + transition: background 0.1s; +} + +.option:hover { + background: rgba(var(--gold-rgb), 0.1); +} + +.option.active { + color: var(--gold); + font-weight: 600; +} + +.optionIcon { + font-size: 1rem; + width: 1.4rem; + text-align: center; +} diff --git a/client/src/components/ThemeToggle.tsx b/client/src/components/ThemeToggle.tsx new file mode 100644 index 0000000..f424e31 --- /dev/null +++ b/client/src/components/ThemeToggle.tsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import styles from "./ThemeToggle.module.css"; + +const THEMES = [ + { id: "dark-parchment", label: "Dark Parchment", icon: "\u{1F4DC}" }, + { id: "light-parchment", label: "Light Parchment", icon: "\u{1F3F0}" }, + { id: "white", label: "White", icon: "\u2600" }, + { id: "abyss", label: "Abyss", icon: "\u{1F311}" }, +] as const; + +type ThemeId = (typeof THEMES)[number]["id"]; + +function getInitialTheme(): ThemeId { + const saved = localStorage.getItem("shadowdark-theme"); + if (saved && THEMES.some((t) => t.id === saved)) return saved as ThemeId; + return "dark-parchment"; +} + +function applyTheme(theme: ThemeId) { + if (theme === "dark-parchment") { + document.documentElement.removeAttribute("data-theme"); + } else { + document.documentElement.setAttribute("data-theme", theme); + } + localStorage.setItem("shadowdark-theme", theme); +} + +// Apply saved theme immediately on module load +applyTheme(getInitialTheme()); + +export default function ThemeToggle() { + const [current, setCurrent] = useState(getInitialTheme); + const [open, setOpen] = useState(false); + + function selectTheme(theme: ThemeId) { + applyTheme(theme); + setCurrent(theme); + setOpen(false); + } + + const currentTheme = THEMES.find((t) => t.id === current)!; + + return ( +
+ + {open && ( +
+ {THEMES.map((theme) => ( + + ))} +
+ )} +
+ ); +} diff --git a/client/src/main.tsx b/client/src/main.tsx index 77d159f..568f5d4 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,9 +1,10 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import "./theme.css"; import App from "./App"; createRoot(document.getElementById("root")!).render( - + , ); diff --git a/client/src/pages/CampaignList.module.css b/client/src/pages/CampaignList.module.css index 0144820..bed9dcd 100644 --- a/client/src/pages/CampaignList.module.css +++ b/client/src/pages/CampaignList.module.css @@ -14,32 +14,49 @@ display: flex; align-items: center; justify-content: space-between; - background: #16213e; - border: 1px solid #333; - border-radius: 8px; + background-color: var(--bg-surface); + background-image: var(--texture-surface), var(--texture-speckle); + background-size: + 256px 256px, + 128px 128px; + background-repeat: repeat, repeat; + border: 1px solid rgba(var(--gold-rgb), 0.2); + border-radius: 4px; padding: 1rem 1.25rem; cursor: pointer; - transition: border-color 0.15s; + transition: + border-color 0.15s, + box-shadow 0.15s; + box-shadow: + 0 4px 12px rgba(var(--shadow-rgb), 0.5), + 0 1px 4px rgba(var(--shadow-rgb), 0.3), + inset 0 1px 0 rgba(var(--gold-rgb), 0.06); } .campaignCard:hover { - border-color: #c9a84c; + border-color: rgba(var(--gold-rgb), 0.5); + box-shadow: + 0 6px 16px rgba(var(--shadow-rgb), 0.6), + 0 2px 6px rgba(var(--shadow-rgb), 0.4), + inset 0 1px 0 rgba(var(--gold-rgb), 0.1); } .campaignName { + font-family: "Cinzel", Georgia, serif; font-size: 1.1rem; font-weight: 600; + color: var(--text-primary); } .campaignDate { font-size: 0.8rem; - color: #888; + color: var(--text-secondary); } .deleteBtn { background: none; border: none; - color: #666; + color: var(--text-tertiary); cursor: pointer; font-size: 1.2rem; padding: 0.25rem 0.5rem; @@ -47,8 +64,8 @@ } .deleteBtn:hover { - color: #e74c3c; - background: rgba(231, 76, 60, 0.1); + color: var(--danger); + background: rgba(var(--danger-rgb), 0.1); } .createForm { @@ -59,36 +76,47 @@ .createInput { flex: 1; padding: 0.6rem 1rem; - background: #16213e; - border: 1px solid #333; - border-radius: 8px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.2); + border-radius: 4px; + color: var(--text-primary); font-size: 1rem; + font-family: "Alegreya", Georgia, serif; } .createInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .createBtn { padding: 0.6rem 1.25rem; - background: #c9a84c; - color: #1a1a2e; + background: var(--btn-gold-bg); + color: var(--btn-active-text); border: none; - border-radius: 8px; + border-radius: 4px; + font-family: "Cinzel", Georgia, serif; font-weight: 600; cursor: pointer; font-size: 1rem; + box-shadow: + 0 2px 4px rgba(var(--shadow-rgb), 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + text-shadow: 0 1px 1px rgba(var(--shadow-rgb), 0.2); } .createBtn:hover { - background: #d4b65a; + background: linear-gradient( + 180deg, + var(--gold-bright), + var(--gold-hover) 40%, + var(--gold) + ); } .empty { text-align: center; - color: #666; + color: var(--text-tertiary); padding: 3rem 0; font-style: italic; } diff --git a/client/src/pages/CampaignView.module.css b/client/src/pages/CampaignView.module.css index 5c98d63..12b1603 100644 --- a/client/src/pages/CampaignView.module.css +++ b/client/src/pages/CampaignView.module.css @@ -30,34 +30,47 @@ } .backLink { - color: #888; + color: var(--text-secondary); text-decoration: none; font-size: 0.9rem; } .backLink:hover { - color: #c9a84c; + color: var(--gold); } .campaignName { + font-family: "Cinzel", Georgia, serif; font-size: 1.5rem; font-weight: 700; - color: #c9a84c; + color: var(--gold); + letter-spacing: 0.05em; + text-shadow: 0 1px 3px rgba(var(--shadow-rgb), 0.4); } .addBtn { padding: 0.5rem 1rem; - background: #c9a84c; - color: #1a1a2e; + background: var(--btn-gold-bg); + color: var(--btn-active-text); border: none; - border-radius: 8px; + border-radius: 4px; + font-family: "Cinzel", Georgia, serif; font-weight: 600; cursor: pointer; font-size: 0.9rem; + box-shadow: + 0 2px 4px rgba(var(--shadow-rgb), 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + text-shadow: 0 1px 1px rgba(var(--shadow-rgb), 0.2); } .addBtn:hover { - background: #d4b65a; + background: linear-gradient( + 180deg, + var(--gold-bright), + var(--gold-hover) 40%, + var(--gold) + ); } .grid { @@ -80,7 +93,7 @@ .empty { text-align: center; - color: #666; + color: var(--text-tertiary); padding: 3rem 0; font-style: italic; grid-column: 1 / -1; @@ -89,7 +102,7 @@ .createModal { position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.7); + background: var(--bg-overlay); display: flex; align-items: center; justify-content: center; @@ -98,19 +111,32 @@ } .createForm { - background: #1a1a2e; - border: 1px solid #333; - border-radius: 12px; + background-color: var(--bg-modal); + background-image: var(--texture-surface), var(--texture-speckle); + background-size: + 256px 256px, + 128px 128px; + background-repeat: repeat, repeat; + border: 2px solid rgba(var(--gold-rgb), 0.3); + border-radius: 4px; padding: 1.5rem; width: 100%; max-width: 400px; + box-shadow: + 0 8px 40px rgba(var(--shadow-rgb), 0.7), + 0 2px 8px rgba(var(--shadow-rgb), 0.5), + inset 0 1px 0 rgba(var(--gold-rgb), 0.1), + inset 0 0 60px rgba(var(--shadow-rgb), 0.2); } .createTitle { + font-family: "Cinzel", Georgia, serif; font-size: 1.2rem; font-weight: 700; margin-bottom: 1rem; - color: #c9a84c; + color: var(--gold); + letter-spacing: 0.05em; + text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.3); } .formField { @@ -121,33 +147,37 @@ } .formLabel { + font-family: "Cinzel", Georgia, serif; font-size: 0.75rem; - color: #888; + color: var(--text-secondary); text-transform: uppercase; font-weight: 600; + letter-spacing: 0.05em; } .formInput { padding: 0.5rem 0.75rem; - background: #0f1a30; - border: 1px solid #333; - border-radius: 6px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); + border-radius: 4px; + color: var(--text-primary); font-size: 0.9rem; + font-family: "Alegreya", Georgia, serif; } .formInput:focus { outline: none; - border-color: #c9a84c; + border-color: var(--gold); } .formSelect { padding: 0.5rem 0.75rem; - background: #0f1a30; - border: 1px solid #333; - border-radius: 6px; - color: #e0e0e0; + background: var(--bg-inset); + border: 1px solid rgba(var(--gold-rgb), 0.15); + border-radius: 4px; + color: var(--text-primary); font-size: 0.9rem; + font-family: "Alegreya", Georgia, serif; } .formActions { @@ -159,29 +189,39 @@ .formBtn { padding: 0.5rem 1rem; - border-radius: 6px; + border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.9rem; } .formBtnPrimary { - background: #c9a84c; - color: #1a1a2e; + background: var(--btn-gold-bg); + color: var(--btn-active-text); border: none; + font-family: "Cinzel", Georgia, serif; + box-shadow: + 0 2px 4px rgba(var(--shadow-rgb), 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + text-shadow: 0 1px 1px rgba(var(--shadow-rgb), 0.2); } .formBtnPrimary:hover { - background: #d4b65a; + background: linear-gradient( + 180deg, + var(--gold-bright), + var(--gold-hover) 40%, + var(--gold) + ); } .formBtnSecondary { background: transparent; - color: #888; - border: 1px solid #333; + color: var(--text-secondary); + border: 1px solid rgba(var(--gold-rgb), 0.2); } .formBtnSecondary:hover { - border-color: #888; - color: #e0e0e0; + border-color: rgba(var(--gold-rgb), 0.4); + color: var(--text-primary); } diff --git a/client/src/theme.css b/client/src/theme.css new file mode 100644 index 0000000..58a2023 --- /dev/null +++ b/client/src/theme.css @@ -0,0 +1,182 @@ +/* ============================================================ + Shadowdark Theme System + ============================================================ + All theme-dependent colors use CSS custom properties. + Themes are activated via data-theme attribute on . + Default (no attribute) = dark-parchment. + ============================================================ */ + +:root { + /* --- Color components (for rgba() usage) --- */ + --gold-rgb: 201, 168, 76; + --shadow-rgb: 0, 0, 0; + --danger-rgb: 231, 76, 60; + --crit-rgb: 255, 215, 0; + + /* --- Backgrounds --- */ + --bg-body: #0a0908; + --bg-body-vignette: rgba(25, 22, 18, 0.6); + --bg-surface: #2a2520; + --bg-modal: #201c18; + --bg-input: #15130f; + --bg-inset: rgba(0, 0, 0, 0.3); + --bg-overlay: rgba(0, 0, 0, 0.75); + --bg-roll-log: #1a1714; + --bg-roll-entry: #252118; + + /* --- Text --- */ + --text-primary: #dad5c9; + --text-secondary: #908878; + --text-tertiary: #665e52; + --text-faint: #4a4238; + + /* --- Gold accent --- */ + --gold: #c9a84c; + --gold-hover: #d4b65a; + --gold-bright: #dcc06a; + --btn-gold-bg: linear-gradient(180deg, #d4b65a, #c9a84c 40%, #b8973e); + --btn-active-text: #1a1a2e; + + /* --- Status colors --- */ + --hp: #4caf50; + --ac: #5dade2; + --danger: #e74c3c; + --warning: #ff9800; + --crit: #ffd700; + --copper: #b87333; + --silver: #a0a0a0; + + /* --- Textures --- */ + --texture-surface: url("/textures/parchment-noise.png"); + --texture-speckle: url("/textures/speckle.png"); + --texture-body: url("/textures/wood-grain.png"); + --texture-surface-size: 256px 256px; + --texture-speckle-size: 128px 128px; + --texture-body-size: 512px 512px; +} + +/* ============================================================ + Light Parchment — warm beige, like reading by daylight + ============================================================ */ +[data-theme="light-parchment"] { + --gold-rgb: 140, 110, 40; + --shadow-rgb: 80, 60, 30; + --danger-rgb: 200, 60, 45; + + --bg-body: #c4b594; + --bg-body-vignette: rgba(160, 140, 100, 0.3); + --bg-surface: #ddd0b0; + --bg-modal: #d5c8a8; + --bg-input: #c8bb9a; + --bg-inset: rgba(0, 0, 0, 0.06); + --bg-overlay: rgba(0, 0, 0, 0.5); + --bg-roll-log: #d0c3a0; + --bg-roll-entry: #d8cbb0; + + --text-primary: #2a2218; + --text-secondary: #5a5040; + --text-tertiary: #7a7060; + --text-faint: #9a9080; + + --gold: #8a6d2b; + --gold-hover: #a0832e; + --gold-bright: #b89530; + --btn-gold-bg: linear-gradient(180deg, #a08335, #8a6d2b 40%, #7a5d20); + --btn-active-text: #f0e8d0; + + --hp: #2e8b34; + --ac: #2e7da8; + --danger: #c83c2d; + --warning: #c87800; + --crit: #b8960a; + --copper: #8a5520; + --silver: #707070; + + --texture-surface: url("/textures/parchment-noise-light.png"); + --texture-speckle: url("/textures/speckle-light.png"); + --texture-body: url("/textures/wood-grain-light.png"); +} + +/* ============================================================ + White — clean, minimal, near-white + ============================================================ */ +[data-theme="white"] { + --gold-rgb: 154, 125, 48; + --shadow-rgb: 0, 0, 0; + --danger-rgb: 200, 60, 45; + + --bg-body: #f0ede8; + --bg-body-vignette: transparent; + --bg-surface: #faf8f4; + --bg-modal: #f5f3ef; + --bg-input: #eae7e0; + --bg-inset: rgba(0, 0, 0, 0.04); + --bg-overlay: rgba(0, 0, 0, 0.4); + --bg-roll-log: #f2f0ec; + --bg-roll-entry: #f8f6f2; + + --text-primary: #1a1816; + --text-secondary: #555048; + --text-tertiary: #807870; + --text-faint: #aaa498; + + --gold: #9a7d30; + --gold-hover: #b0932e; + --gold-bright: #c8a830; + --btn-gold-bg: linear-gradient(180deg, #b09335, #9a7d30 40%, #8a6d25); + --btn-active-text: #f5f3ef; + + --hp: #2e8b34; + --ac: #2e7da8; + --danger: #c83c2d; + --warning: #c87800; + --crit: #8a7010; + --copper: #8a5520; + --silver: #707070; + + --texture-surface: none; + --texture-speckle: none; + --texture-body: none; +} + +/* ============================================================ + Abyss — maximum dark, pitch black + ============================================================ */ +[data-theme="abyss"] { + --gold-rgb: 212, 182, 90; + --shadow-rgb: 0, 0, 0; + --danger-rgb: 231, 76, 60; + + --bg-body: #050505; + --bg-body-vignette: transparent; + --bg-surface: #0e0e14; + --bg-modal: #0a0a10; + --bg-input: #08080e; + --bg-inset: rgba(0, 0, 0, 0.5); + --bg-overlay: rgba(0, 0, 0, 0.85); + --bg-roll-log: #0a0a10; + --bg-roll-entry: #0e0e14; + + --text-primary: #e8e8e8; + --text-secondary: #888888; + --text-tertiary: #606060; + --text-faint: #404040; + + --gold: #d4b65a; + --gold-hover: #e0c870; + --gold-bright: #edd880; + --btn-gold-bg: linear-gradient(180deg, #e0c870, #d4b65a 40%, #c4a64a); + --btn-active-text: #0a0a10; + + --hp: #4caf50; + --ac: #5dade2; + --danger: #e74c3c; + --warning: #ff9800; + --crit: #ffd700; + --copper: #b87333; + --silver: #a0a0a0; + + --texture-surface: none; + --texture-speckle: none; + --texture-body: none; +}