From ff9203f754d904a4a2241d919651eb8d415f83ca Mon Sep 17 00:00:00 2001 From: Aaron Wood Date: Thu, 9 Apr 2026 17:06:50 -0400 Subject: [PATCH] Medieval visual theme with 4 switchable color schemes - Cinzel + Alegreya fonts for old-tome D&D book feel - Dark parchment (default), light parchment, white, and abyss themes - CSS custom properties system for all colors, borders, shadows - Generated PNG textures for parchment grain, speckle, and wood grain - Ornamental gold gradient separators and decorative header lines - Deep dramatic shadows and gold-tinted borders on all panels - ThemeToggle component with localStorage persistence - Embossed gold buttons with gradient and inner shadow Co-Authored-By: Claude Opus 4.6 (1M context) --- client/index.html | 8 +- .../public/textures/parchment-noise-light.png | Bin 0 -> 8552 bytes client/public/textures/parchment-noise.png | Bin 0 -> 8196 bytes client/public/textures/speckle-light.png | Bin 0 -> 7519 bytes client/public/textures/speckle.png | Bin 0 -> 8438 bytes client/public/textures/wood-grain-light.png | Bin 0 -> 7388 bytes client/public/textures/wood-grain.png | Bin 0 -> 7375 bytes client/src/App.module.css | 56 +++++- client/src/App.tsx | 2 + client/src/components/AcDisplay.module.css | 26 +-- client/src/components/AttackBlock.module.css | 27 +-- .../src/components/CharacterCard.module.css | 41 ++-- .../src/components/CharacterDetail.module.css | 45 +++-- .../src/components/CharacterSheet.module.css | 66 ++++--- client/src/components/CurrencyRow.module.css | 27 +-- client/src/components/DiceButton.module.css | 30 +-- client/src/components/GearList.module.css | 73 ++++--- client/src/components/GearPanel.module.css | 16 +- client/src/components/HpBar.module.css | 24 +-- client/src/components/HpDisplay.module.css | 46 ++--- client/src/components/InfoPanel.module.css | 58 ++++-- client/src/components/InlineNumber.module.css | 8 +- client/src/components/ItemPicker.module.css | 49 +++-- client/src/components/RollEntry.module.css | 54 ++++-- client/src/components/RollLog.module.css | 45 +++-- client/src/components/StatBlock.module.css | 29 +-- client/src/components/StatsPanel.module.css | 32 ++- client/src/components/TalentList.module.css | 47 +++-- client/src/components/TalentPicker.module.css | 49 +++-- client/src/components/ThemeToggle.module.css | 76 ++++++++ client/src/components/ThemeToggle.tsx | 68 +++++++ client/src/main.tsx | 3 +- client/src/pages/CampaignList.module.css | 66 +++++-- client/src/pages/CampaignView.module.css | 102 +++++++--- client/src/theme.css | 182 ++++++++++++++++++ 35 files changed, 996 insertions(+), 359 deletions(-) create mode 100644 client/public/textures/parchment-noise-light.png create mode 100644 client/public/textures/parchment-noise.png create mode 100644 client/public/textures/speckle-light.png create mode 100644 client/public/textures/speckle.png create mode 100644 client/public/textures/wood-grain-light.png create mode 100644 client/public/textures/wood-grain.png create mode 100644 client/src/components/ThemeToggle.module.css create mode 100644 client/src/components/ThemeToggle.tsx create mode 100644 client/src/theme.css 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 0000000000000000000000000000000000000000..b5af4ec09cf64363cd72fc99de2827d5009f55d4 GIT binary patch literal 8552 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

Lkr6iq=c;2+@6w2Z9I1X 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 literal 0 HcmV?d00001 diff --git a/client/public/textures/wood-grain-light.png b/client/public/textures/wood-grain-light.png new file mode 100644 index 0000000000000000000000000000000000000000..12d371e2d38c094f6d4c773fb3574094f81c3f7d GIT binary patch literal 7388 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

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; +}