Initial project setup for Vagabond RPG Foundry VTT system
- System manifest (system.json) for Foundry v13 - Project structure with module/, templates/, styles/, lang/, packs/ - Docker Compose for local Foundry development environment - SCSS architecture with parchment theme and accessibility colors - Base system entry point with CONFIG and Handlebars helpers - English localization file with all game terms - Project roadmap with 98 tasks across 11 phases Phase 0 (Foundation) complete. Ready for Phase 1 (Data Models). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
37300ccf90
12
.env.example
Normal file
12
.env.example
Normal file
@ -0,0 +1,12 @@
|
||||
# Foundry VTT Credentials
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# Your Foundry VTT account credentials (for downloading Foundry)
|
||||
FOUNDRY_USERNAME=your-foundry-username
|
||||
FOUNDRY_PASSWORD=your-foundry-password
|
||||
|
||||
# Your Foundry license key
|
||||
FOUNDRY_LICENSE_KEY=your-license-key
|
||||
|
||||
# Admin password for the Foundry instance
|
||||
FOUNDRY_ADMIN_KEY=vagabond-dev-admin
|
||||
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Foundry data (dev environment)
|
||||
foundrydata/
|
||||
|
||||
# Build artifacts
|
||||
styles/*.css
|
||||
styles/*.css.map
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
*.local
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Secrets
|
||||
*.pem
|
||||
*.key
|
||||
credentials.json
|
||||
86
CLAUDE.md
Normal file
86
CLAUDE.md
Normal file
@ -0,0 +1,86 @@
|
||||
# Vagabond RPG Foundry VTT System - Development Context
|
||||
|
||||
## Project Overview
|
||||
This is a complete Foundry VTT v13 system implementation for Vagabond RPG (Pulp Fantasy TTRPG).
|
||||
|
||||
## Key Architecture Decisions
|
||||
|
||||
### Data Models (Foundry v13 style)
|
||||
- Use TypeDataModel classes in `module/data/` for Actor and Item schemas
|
||||
- Character stats: Might, Dexterity, Awareness, Reason, Presence, Luck (range 2-7)
|
||||
- Derived values calculated in `prepareData()`: HP, Speed, Save difficulties, Skill difficulties
|
||||
|
||||
### Roll System
|
||||
- Base formula: d20 >= (20 - Stat) for untrained, d20 >= (20 - Stat*2) for trained
|
||||
- Favor: +d6 bonus die
|
||||
- Hinder: -d6 penalty die
|
||||
- Crit: Natural 20 by default, but threshold can be modified per-skill by Active Effects
|
||||
- Exploding dice: d6! notation for certain abilities
|
||||
|
||||
### Spell Casting
|
||||
- Dynamic mana cost = base + delivery cost + duration cost + extra damage dice
|
||||
- Delivery types: Touch(0), Remote(0), Imbue(0), Cube(1), Aura(2), Cone(2), Glyph(2), Line(2), Sphere(2)
|
||||
- Duration: Instant (free), Focus (ongoing), Continual (permanent)
|
||||
- Cast dialog must calculate and display total mana cost before casting
|
||||
|
||||
### Class System
|
||||
- Classes are Items with progression tables
|
||||
- When dragged to character, creates Active Effects for current level
|
||||
- On level up, update Active Effects to grant new features
|
||||
- Supports future multiclassing by allowing multiple class items
|
||||
|
||||
### Crit Threshold System
|
||||
- Each skill/action has a `critThreshold` field (default 20)
|
||||
- Active Effects from classes/perks can modify: `system.skills.melee.critThreshold`
|
||||
- Fighter's Valor reduces crit by 1/2/3 at levels 1/4/8
|
||||
- Gunslinger's Deadeye dynamically reduces on consecutive hits
|
||||
|
||||
### Resources
|
||||
- HP: max = Might * Level
|
||||
- Mana: class-dependent, max from class progression
|
||||
- Luck: equals Luck stat, refreshes on rest
|
||||
- Fatigue: 0-5, death at 5, each reduces item slots by 1
|
||||
- Studied Dice: some classes grant these
|
||||
- Custom resources can be added dynamically
|
||||
|
||||
## File Naming Conventions
|
||||
- Main system entry: `vagabond.mjs`
|
||||
- Document classes: `VagabondActor.mjs`, `VagabondItem.mjs`
|
||||
- Sheet classes: `VagabondCharacterSheet.mjs`, `VagabondNPCSheet.mjs`
|
||||
- Data models: `CharacterData.mjs`, `NPCData.mjs`, `SpellData.mjs`, etc.
|
||||
- Templates: `character-sheet.hbs`, `npc-sheet.hbs`, `spell-item.hbs`
|
||||
|
||||
## Testing Commands
|
||||
```bash
|
||||
# Start local Foundry
|
||||
docker compose up -d
|
||||
|
||||
# Watch SCSS
|
||||
npm run watch
|
||||
|
||||
# View logs
|
||||
docker compose logs -f foundry
|
||||
```
|
||||
|
||||
## Reference Data Location
|
||||
Game rules and content are documented in NoteDiscovery under `gaming/vagabond-rpg/`:
|
||||
- `core-mechanics.md` - Stats, checks, dice, HP
|
||||
- `combat.md` - Actions, movement, defending, zones
|
||||
- `character-creation.md` - Ancestries, classes, leveling
|
||||
- `magic-system.md` - Casting, mana, delivery, duration
|
||||
- `spells-full-text.md` - All 55+ spells with full descriptions
|
||||
- `perks-full-list.md` - All 90+ perks with prerequisites
|
||||
- `classes-full-text.md` - All 18 classes with progression tables
|
||||
- `bestiary.md` - Creature categories, TL reference
|
||||
|
||||
Original PDF at: `/mnt/NV2/Development/claude-home/gaming/Vagabond_RPG_-_Pulp_Fantasy_Core_Rulebook_Interactive_PDF.pdf`
|
||||
Character sheet reference: `/mnt/NV2/Development/claude-home/gaming/Vagabond_-_Hero_Record_Interactive_PDF.pdf`
|
||||
|
||||
## Project Roadmap
|
||||
See `PROJECT_ROADMAP.json` for complete task breakdown with dependencies.
|
||||
|
||||
## Style Guidelines
|
||||
- Parchment color scheme with high contrast (WCAG AA compliant)
|
||||
- Match official Hero Record layout where possible
|
||||
- Use CSS custom properties for theming
|
||||
- SCSS with BEM naming convention
|
||||
28
LICENSE
Normal file
28
LICENSE
Normal file
@ -0,0 +1,28 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Cal Corum
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
Vagabond RPG is a product of its respective copyright holders. This system
|
||||
implementation is a fan project and is not affiliated with or endorsed by
|
||||
the original game creators. Game rules and content are used under fair use
|
||||
for the purpose of creating a virtual tabletop implementation.
|
||||
1034
PROJECT_ROADMAP.json
Normal file
1034
PROJECT_ROADMAP.json
Normal file
File diff suppressed because it is too large
Load Diff
110
README.md
Normal file
110
README.md
Normal file
@ -0,0 +1,110 @@
|
||||
# Vagabond RPG - Foundry VTT System
|
||||
|
||||
A complete Foundry VTT v13 system implementation for **Vagabond RPG** - Pulp Fantasy Roleplaying.
|
||||
|
||||
## Features
|
||||
|
||||
- Full character sheet matching the official Hero Record design
|
||||
- Dynamic spell casting system with delivery/duration/damage customization
|
||||
- Automated skill checks with favor/hinder modifiers
|
||||
- Variable crit thresholds per skill (modified by class features and perks)
|
||||
- Complete compendiums: 18 classes, 55+ spells, 90+ perks, ancestries, equipment, bestiary
|
||||
- NPC/Monster stat blocks with morale system
|
||||
- Parchment-themed UI with accessibility (color-blind friendly)
|
||||
|
||||
## Installation
|
||||
|
||||
### From Foundry
|
||||
1. Open Foundry VTT Setup
|
||||
2. Navigate to Game Systems
|
||||
3. Click "Install System"
|
||||
4. Search for "Vagabond" or paste the manifest URL:
|
||||
```
|
||||
https://github.com/calcorum/vagabond-rpg-foundryvtt/releases/latest/download/system.json
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
1. Download the latest release from GitHub
|
||||
2. Extract to `Data/systems/vagabond/`
|
||||
3. Restart Foundry VTT
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
- Node.js 18+
|
||||
- Docker & Docker Compose (for local Foundry instance)
|
||||
|
||||
### Quick Start
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/calcorum/vagabond-rpg-foundryvtt.git
|
||||
cd vagabond-rpg-foundryvtt
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start SCSS watcher
|
||||
npm run watch
|
||||
|
||||
# Start local Foundry instance
|
||||
docker compose up -d
|
||||
|
||||
# Access Foundry at http://localhost:30000
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
vagabond-rpg-foundryvtt/
|
||||
├── module/ # JavaScript modules
|
||||
│ ├── vagabond.mjs # System entry point
|
||||
│ ├── documents/ # Actor/Item document classes
|
||||
│ ├── sheets/ # Sheet classes
|
||||
│ ├── helpers/ # Utility functions
|
||||
│ └── dice/ # Roll handling
|
||||
├── templates/ # Handlebars templates
|
||||
│ ├── actor/ # Actor sheet templates
|
||||
│ ├── item/ # Item sheet templates
|
||||
│ ├── chat/ # Chat message templates
|
||||
│ └── dialog/ # Roll dialog templates
|
||||
├── styles/ # SCSS/CSS
|
||||
│ └── scss/ # SCSS source files
|
||||
├── lang/ # Localization files
|
||||
├── packs/ # Compendium data
|
||||
├── assets/ # Images and icons
|
||||
├── system.json # System manifest
|
||||
└── docker-compose.yml # Local dev environment
|
||||
```
|
||||
|
||||
### Building Styles
|
||||
```bash
|
||||
# One-time build
|
||||
npm run build
|
||||
|
||||
# Watch for changes
|
||||
npm run watch
|
||||
```
|
||||
|
||||
### Creating a Release
|
||||
```bash
|
||||
npm run release
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
||||
Vagabond RPG is a product of its respective copyright holders. This system implementation is a fan project and is not affiliated with or endorsed by the original game creators.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Vagabond RPG by [Publisher] for the amazing game system
|
||||
- Foundry VTT community for documentation and examples
|
||||
- All contributors to this project
|
||||
28
docker-compose.yml
Normal file
28
docker-compose.yml
Normal file
@ -0,0 +1,28 @@
|
||||
services:
|
||||
foundry:
|
||||
image: felddy/foundryvtt:release
|
||||
container_name: vagabond-foundry-dev
|
||||
hostname: vagabond-foundry-dev
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- FOUNDRY_USERNAME=${FOUNDRY_USERNAME}
|
||||
- FOUNDRY_PASSWORD=${FOUNDRY_PASSWORD}
|
||||
- FOUNDRY_ADMIN_KEY=${FOUNDRY_ADMIN_KEY:-vagabond-dev}
|
||||
- FOUNDRY_LICENSE_KEY=${FOUNDRY_LICENSE_KEY}
|
||||
- CONTAINER_PRESERVE_CONFIG=true
|
||||
volumes:
|
||||
# Foundry data directory
|
||||
- ./foundrydata:/data
|
||||
# Mount system directly for hot reload development
|
||||
- ./:/data/Data/systems/vagabond:ro
|
||||
ports:
|
||||
- "30000:30000"
|
||||
# Required for Docker-in-LXC or rootless podman
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
|
||||
# Note: Create a .env file with your Foundry credentials:
|
||||
# FOUNDRY_USERNAME=your-username
|
||||
# FOUNDRY_PASSWORD=your-password
|
||||
# FOUNDRY_LICENSE_KEY=your-license-key
|
||||
# FOUNDRY_ADMIN_KEY=your-admin-key
|
||||
213
lang/en.json
Normal file
213
lang/en.json
Normal file
@ -0,0 +1,213 @@
|
||||
{
|
||||
"VAGABOND.SystemName": "Vagabond RPG",
|
||||
"VAGABOND.SystemDescription": "A Foundry VTT system for Vagabond RPG - Pulp Fantasy Roleplaying",
|
||||
|
||||
"VAGABOND.SheetCharacter": "Character Sheet",
|
||||
"VAGABOND.SheetNPC": "NPC Sheet",
|
||||
"VAGABOND.SheetItem": "Item Sheet",
|
||||
|
||||
"VAGABOND.StatMight": "Might",
|
||||
"VAGABOND.StatDexterity": "Dexterity",
|
||||
"VAGABOND.StatAwareness": "Awareness",
|
||||
"VAGABOND.StatReason": "Reason",
|
||||
"VAGABOND.StatPresence": "Presence",
|
||||
"VAGABOND.StatLuck": "Luck",
|
||||
|
||||
"VAGABOND.StatMightAbbr": "MIT",
|
||||
"VAGABOND.StatDexterityAbbr": "DEX",
|
||||
"VAGABOND.StatAwarenessAbbr": "AWR",
|
||||
"VAGABOND.StatReasonAbbr": "RSN",
|
||||
"VAGABOND.StatPresenceAbbr": "PRS",
|
||||
"VAGABOND.StatLuckAbbr": "LUK",
|
||||
|
||||
"VAGABOND.SkillArcana": "Arcana",
|
||||
"VAGABOND.SkillBrawl": "Brawl",
|
||||
"VAGABOND.SkillCraft": "Craft",
|
||||
"VAGABOND.SkillDetect": "Detect",
|
||||
"VAGABOND.SkillFinesse": "Finesse",
|
||||
"VAGABOND.SkillInfluence": "Influence",
|
||||
"VAGABOND.SkillLeadership": "Leadership",
|
||||
"VAGABOND.SkillMedicine": "Medicine",
|
||||
"VAGABOND.SkillMysticism": "Mysticism",
|
||||
"VAGABOND.SkillPerformance": "Performance",
|
||||
"VAGABOND.SkillSneak": "Sneak",
|
||||
"VAGABOND.SkillSurvival": "Survival",
|
||||
|
||||
"VAGABOND.AttackMelee": "Melee",
|
||||
"VAGABOND.AttackBrawl": "Brawl",
|
||||
"VAGABOND.AttackRanged": "Ranged",
|
||||
"VAGABOND.AttackFinesse": "Finesse",
|
||||
|
||||
"VAGABOND.SaveReflex": "Reflex",
|
||||
"VAGABOND.SaveEndure": "Endure",
|
||||
"VAGABOND.SaveWill": "Will",
|
||||
|
||||
"VAGABOND.DeliveryTouch": "Touch",
|
||||
"VAGABOND.DeliveryRemote": "Remote",
|
||||
"VAGABOND.DeliveryImbue": "Imbue",
|
||||
"VAGABOND.DeliveryCube": "Cube",
|
||||
"VAGABOND.DeliveryAura": "Aura",
|
||||
"VAGABOND.DeliveryCone": "Cone",
|
||||
"VAGABOND.DeliveryGlyph": "Glyph",
|
||||
"VAGABOND.DeliveryLine": "Line",
|
||||
"VAGABOND.DeliverySphere": "Sphere",
|
||||
|
||||
"VAGABOND.DurationInstant": "Instant",
|
||||
"VAGABOND.DurationFocus": "Focus",
|
||||
"VAGABOND.DurationContinual": "Continual",
|
||||
|
||||
"VAGABOND.DamageBlunt": "Blunt",
|
||||
"VAGABOND.DamageSlash": "Slash",
|
||||
"VAGABOND.DamagePierce": "Pierce",
|
||||
"VAGABOND.DamageFire": "Fire",
|
||||
"VAGABOND.DamageCold": "Cold",
|
||||
"VAGABOND.DamageShock": "Shock",
|
||||
"VAGABOND.DamagePoison": "Poison",
|
||||
"VAGABOND.DamageAcid": "Acid",
|
||||
|
||||
"VAGABOND.PropertyFinesse": "Finesse",
|
||||
"VAGABOND.PropertyThrown": "Thrown",
|
||||
"VAGABOND.PropertyCleave": "Cleave",
|
||||
"VAGABOND.PropertyReach": "Reach",
|
||||
"VAGABOND.PropertyLoading": "Loading",
|
||||
"VAGABOND.PropertyBrawl": "Brawl",
|
||||
"VAGABOND.PropertyCrude": "Crude",
|
||||
"VAGABOND.PropertyVersatile": "Versatile",
|
||||
|
||||
"VAGABOND.Grip1H": "1H",
|
||||
"VAGABOND.Grip2H": "2H",
|
||||
"VAGABOND.GripVersatile": "Versatile",
|
||||
"VAGABOND.GripFist": "Fist",
|
||||
|
||||
"VAGABOND.ArmorLight": "Light",
|
||||
"VAGABOND.ArmorHeavy": "Heavy",
|
||||
"VAGABOND.ArmorShield": "Shield",
|
||||
|
||||
"VAGABOND.SizeSmall": "Small",
|
||||
"VAGABOND.SizeMedium": "Medium",
|
||||
"VAGABOND.SizeLarge": "Large",
|
||||
"VAGABOND.SizeHuge": "Huge",
|
||||
"VAGABOND.SizeGiant": "Giant",
|
||||
"VAGABOND.SizeColossal": "Colossal",
|
||||
|
||||
"VAGABOND.BeingHumanlike": "Humanlike",
|
||||
"VAGABOND.BeingFae": "Fae",
|
||||
"VAGABOND.BeingCryptid": "Cryptid",
|
||||
"VAGABOND.BeingArtificial": "Artificial",
|
||||
"VAGABOND.BeingBeast": "Beast",
|
||||
"VAGABOND.BeingOuter": "Outer",
|
||||
"VAGABOND.BeingPrimordial": "Primordial",
|
||||
"VAGABOND.BeingUndead": "Undead",
|
||||
|
||||
"VAGABOND.ZoneFrontline": "Frontline",
|
||||
"VAGABOND.ZoneMidline": "Midline",
|
||||
"VAGABOND.ZoneBackline": "Backline",
|
||||
|
||||
"VAGABOND.Level": "Level",
|
||||
"VAGABOND.XP": "XP",
|
||||
"VAGABOND.Class": "Class",
|
||||
"VAGABOND.Ancestry": "Ancestry",
|
||||
"VAGABOND.Name": "Name",
|
||||
|
||||
"VAGABOND.HitPoints": "Hit Points",
|
||||
"VAGABOND.HP": "HP",
|
||||
"VAGABOND.Current": "Current",
|
||||
"VAGABOND.Max": "Max",
|
||||
|
||||
"VAGABOND.Armor": "Armor",
|
||||
"VAGABOND.Fatigue": "Fatigue",
|
||||
"VAGABOND.Speed": "Speed",
|
||||
"VAGABOND.SpeedBonus": "Speed Bonus",
|
||||
"VAGABOND.CrawlSpeed": "Crawl Speed",
|
||||
"VAGABOND.TravelSpeed": "Travel Speed",
|
||||
|
||||
"VAGABOND.Saves": "Saves",
|
||||
"VAGABOND.Skills": "Skills",
|
||||
"VAGABOND.Attacks": "Attacks",
|
||||
"VAGABOND.Trained": "Trained",
|
||||
"VAGABOND.Difficulty": "Difficulty",
|
||||
|
||||
"VAGABOND.Inventory": "Inventory",
|
||||
"VAGABOND.Abilities": "Abilities",
|
||||
"VAGABOND.Magic": "Magic",
|
||||
"VAGABOND.Biography": "Biography",
|
||||
|
||||
"VAGABOND.Mana": "Mana",
|
||||
"VAGABOND.CastingMax": "Casting Max",
|
||||
"VAGABOND.Spells": "Spells",
|
||||
|
||||
"VAGABOND.Wealth": "Wealth",
|
||||
"VAGABOND.Gold": "Gold",
|
||||
"VAGABOND.Silver": "Silver",
|
||||
"VAGABOND.Copper": "Copper",
|
||||
"VAGABOND.ItemSlots": "Item Slots",
|
||||
"VAGABOND.Occupied": "Occupied",
|
||||
"VAGABOND.Bonus": "Bonus",
|
||||
|
||||
"VAGABOND.Features": "Features",
|
||||
"VAGABOND.Perks": "Perks",
|
||||
"VAGABOND.Traits": "Traits",
|
||||
|
||||
"VAGABOND.Roll": "Roll",
|
||||
"VAGABOND.RollCheck": "Roll Check",
|
||||
"VAGABOND.RollSave": "Roll Save",
|
||||
"VAGABOND.RollAttack": "Roll Attack",
|
||||
"VAGABOND.RollDamage": "Roll Damage",
|
||||
"VAGABOND.Cast": "Cast",
|
||||
|
||||
"VAGABOND.Favor": "Favor",
|
||||
"VAGABOND.Hinder": "Hinder",
|
||||
"VAGABOND.Modifier": "Modifier",
|
||||
|
||||
"VAGABOND.CritThreshold": "Crit on",
|
||||
"VAGABOND.Success": "Success",
|
||||
"VAGABOND.Failure": "Failure",
|
||||
"VAGABOND.Critical": "Critical!",
|
||||
|
||||
"VAGABOND.Damage": "Damage",
|
||||
"VAGABOND.DamageType": "Damage Type",
|
||||
"VAGABOND.Effect": "Effect",
|
||||
"VAGABOND.CritEffect": "Crit Effect",
|
||||
"VAGABOND.Delivery": "Delivery",
|
||||
"VAGABOND.Duration": "Duration",
|
||||
"VAGABOND.ManaCost": "Mana Cost",
|
||||
|
||||
"VAGABOND.Prerequisites": "Prerequisites",
|
||||
"VAGABOND.Description": "Description",
|
||||
|
||||
"VAGABOND.HD": "HD",
|
||||
"VAGABOND.TL": "TL",
|
||||
"VAGABOND.Zone": "Zone",
|
||||
"VAGABOND.Morale": "Morale",
|
||||
"VAGABOND.Appearing": "# Appearing",
|
||||
"VAGABOND.Immune": "Immune",
|
||||
"VAGABOND.Weak": "Weak",
|
||||
"VAGABOND.Actions": "Actions",
|
||||
|
||||
"VAGABOND.MoraleCheck": "Morale Check",
|
||||
"VAGABOND.MoraleHolds": "Morale Holds",
|
||||
"VAGABOND.MoraleBreaks": "Morale Breaks!",
|
||||
|
||||
"VAGABOND.Block": "Block",
|
||||
"VAGABOND.Dodge": "Dodge",
|
||||
|
||||
"VAGABOND.CurrentLuck": "Current Luck",
|
||||
"VAGABOND.StudiedDice": "Studied Dice",
|
||||
|
||||
"VAGABOND.Add": "Add",
|
||||
"VAGABOND.Remove": "Remove",
|
||||
"VAGABOND.Edit": "Edit",
|
||||
"VAGABOND.Delete": "Delete",
|
||||
"VAGABOND.Cancel": "Cancel",
|
||||
"VAGABOND.Confirm": "Confirm",
|
||||
"VAGABOND.Save": "Save",
|
||||
|
||||
"VAGABOND.ItemTypeAncestry": "Ancestry",
|
||||
"VAGABOND.ItemTypeClass": "Class",
|
||||
"VAGABOND.ItemTypeSpell": "Spell",
|
||||
"VAGABOND.ItemTypePerk": "Perk",
|
||||
"VAGABOND.ItemTypeFeature": "Feature",
|
||||
"VAGABOND.ItemTypeWeapon": "Weapon",
|
||||
"VAGABOND.ItemTypeArmor": "Armor",
|
||||
"VAGABOND.ItemTypeEquipment": "Equipment"
|
||||
}
|
||||
200
module/helpers/config.mjs
Normal file
200
module/helpers/config.mjs
Normal file
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Vagabond RPG Configuration
|
||||
* Contains all system constants and configuration data
|
||||
*/
|
||||
|
||||
export const VAGABOND = {};
|
||||
|
||||
/**
|
||||
* The set of Ability Scores (Stats) used within the system
|
||||
*/
|
||||
VAGABOND.stats = {
|
||||
might: "VAGABOND.StatMight",
|
||||
dexterity: "VAGABOND.StatDexterity",
|
||||
awareness: "VAGABOND.StatAwareness",
|
||||
reason: "VAGABOND.StatReason",
|
||||
presence: "VAGABOND.StatPresence",
|
||||
luck: "VAGABOND.StatLuck"
|
||||
};
|
||||
|
||||
/**
|
||||
* Abbreviated stat labels
|
||||
*/
|
||||
VAGABOND.statsAbbr = {
|
||||
might: "VAGABOND.StatMightAbbr",
|
||||
dexterity: "VAGABOND.StatDexterityAbbr",
|
||||
awareness: "VAGABOND.StatAwarenessAbbr",
|
||||
reason: "VAGABOND.StatReasonAbbr",
|
||||
presence: "VAGABOND.StatPresenceAbbr",
|
||||
luck: "VAGABOND.StatLuckAbbr"
|
||||
};
|
||||
|
||||
/**
|
||||
* Skills and their associated stats
|
||||
*/
|
||||
VAGABOND.skills = {
|
||||
arcana: { label: "VAGABOND.SkillArcana", stat: "reason" },
|
||||
brawl: { label: "VAGABOND.SkillBrawl", stat: "might" },
|
||||
craft: { label: "VAGABOND.SkillCraft", stat: "reason" },
|
||||
detect: { label: "VAGABOND.SkillDetect", stat: "awareness" },
|
||||
finesse: { label: "VAGABOND.SkillFinesse", stat: "dexterity" },
|
||||
influence: { label: "VAGABOND.SkillInfluence", stat: "presence" },
|
||||
leadership: { label: "VAGABOND.SkillLeadership", stat: "presence" },
|
||||
medicine: { label: "VAGABOND.SkillMedicine", stat: "reason" },
|
||||
mysticism: { label: "VAGABOND.SkillMysticism", stat: "awareness" },
|
||||
performance: { label: "VAGABOND.SkillPerformance", stat: "presence" },
|
||||
sneak: { label: "VAGABOND.SkillSneak", stat: "dexterity" },
|
||||
survival: { label: "VAGABOND.SkillSurvival", stat: "awareness" }
|
||||
};
|
||||
|
||||
/**
|
||||
* Attack types and their associated stats
|
||||
*/
|
||||
VAGABOND.attackTypes = {
|
||||
melee: { label: "VAGABOND.AttackMelee", stat: "might" },
|
||||
brawl: { label: "VAGABOND.AttackBrawl", stat: "might" },
|
||||
ranged: { label: "VAGABOND.AttackRanged", stat: "awareness" },
|
||||
finesse: { label: "VAGABOND.AttackFinesse", stat: "dexterity" }
|
||||
};
|
||||
|
||||
/**
|
||||
* Save types and their stat combinations
|
||||
*/
|
||||
VAGABOND.saves = {
|
||||
reflex: { label: "VAGABOND.SaveReflex", stats: ["dexterity", "awareness"] },
|
||||
endure: { label: "VAGABOND.SaveEndure", stats: ["might", "might"] },
|
||||
will: { label: "VAGABOND.SaveWill", stats: ["reason", "presence"] }
|
||||
};
|
||||
|
||||
/**
|
||||
* Spell delivery types with base costs
|
||||
*/
|
||||
VAGABOND.spellDelivery = {
|
||||
touch: { label: "VAGABOND.DeliveryTouch", cost: 0 },
|
||||
remote: { label: "VAGABOND.DeliveryRemote", cost: 0 },
|
||||
imbue: { label: "VAGABOND.DeliveryImbue", cost: 0 },
|
||||
cube: { label: "VAGABOND.DeliveryCube", cost: 1 },
|
||||
aura: { label: "VAGABOND.DeliveryAura", cost: 2 },
|
||||
cone: { label: "VAGABOND.DeliveryCone", cost: 2 },
|
||||
glyph: { label: "VAGABOND.DeliveryGlyph", cost: 2 },
|
||||
line: { label: "VAGABOND.DeliveryLine", cost: 2 },
|
||||
sphere: { label: "VAGABOND.DeliverySphere", cost: 2 }
|
||||
};
|
||||
|
||||
/**
|
||||
* Spell duration types
|
||||
*/
|
||||
VAGABOND.spellDuration = {
|
||||
instant: { label: "VAGABOND.DurationInstant", focus: false },
|
||||
focus: { label: "VAGABOND.DurationFocus", focus: true },
|
||||
continual: { label: "VAGABOND.DurationContinual", focus: false }
|
||||
};
|
||||
|
||||
/**
|
||||
* Damage types
|
||||
*/
|
||||
VAGABOND.damageTypes = {
|
||||
blunt: "VAGABOND.DamageBlunt",
|
||||
slash: "VAGABOND.DamageSlash",
|
||||
pierce: "VAGABOND.DamagePierce",
|
||||
fire: "VAGABOND.DamageFire",
|
||||
cold: "VAGABOND.DamageCold",
|
||||
shock: "VAGABOND.DamageShock",
|
||||
poison: "VAGABOND.DamagePoison",
|
||||
acid: "VAGABOND.DamageAcid"
|
||||
};
|
||||
|
||||
/**
|
||||
* Weapon properties
|
||||
*/
|
||||
VAGABOND.weaponProperties = {
|
||||
finesse: "VAGABOND.PropertyFinesse",
|
||||
thrown: "VAGABOND.PropertyThrown",
|
||||
cleave: "VAGABOND.PropertyCleave",
|
||||
reach: "VAGABOND.PropertyReach",
|
||||
loading: "VAGABOND.PropertyLoading",
|
||||
brawl: "VAGABOND.PropertyBrawl",
|
||||
crude: "VAGABOND.PropertyCrude",
|
||||
versatile: "VAGABOND.PropertyVersatile"
|
||||
};
|
||||
|
||||
/**
|
||||
* Weapon grip types
|
||||
*/
|
||||
VAGABOND.gripTypes = {
|
||||
"1h": "VAGABOND.Grip1H",
|
||||
"2h": "VAGABOND.Grip2H",
|
||||
versatile: "VAGABOND.GripVersatile",
|
||||
fist: "VAGABOND.GripFist"
|
||||
};
|
||||
|
||||
/**
|
||||
* Armor types
|
||||
*/
|
||||
VAGABOND.armorTypes = {
|
||||
light: "VAGABOND.ArmorLight",
|
||||
heavy: "VAGABOND.ArmorHeavy",
|
||||
shield: "VAGABOND.ArmorShield"
|
||||
};
|
||||
|
||||
/**
|
||||
* Size categories
|
||||
*/
|
||||
VAGABOND.sizes = {
|
||||
small: "VAGABOND.SizeSmall",
|
||||
medium: "VAGABOND.SizeMedium",
|
||||
large: "VAGABOND.SizeLarge",
|
||||
huge: "VAGABOND.SizeHuge",
|
||||
giant: "VAGABOND.SizeGiant",
|
||||
colossal: "VAGABOND.SizeColossal"
|
||||
};
|
||||
|
||||
/**
|
||||
* Being types
|
||||
*/
|
||||
VAGABOND.beingTypes = {
|
||||
humanlike: "VAGABOND.BeingHumanlike",
|
||||
fae: "VAGABOND.BeingFae",
|
||||
cryptid: "VAGABOND.BeingCryptid",
|
||||
artificial: "VAGABOND.BeingArtificial",
|
||||
beast: "VAGABOND.BeingBeast",
|
||||
outer: "VAGABOND.BeingOuter",
|
||||
primordial: "VAGABOND.BeingPrimordial",
|
||||
undead: "VAGABOND.BeingUndead"
|
||||
};
|
||||
|
||||
/**
|
||||
* Enemy zones for AI behavior
|
||||
*/
|
||||
VAGABOND.zones = {
|
||||
frontline: "VAGABOND.ZoneFrontline",
|
||||
midline: "VAGABOND.ZoneMidline",
|
||||
backline: "VAGABOND.ZoneBackline"
|
||||
};
|
||||
|
||||
/**
|
||||
* Default crit threshold (Natural 20)
|
||||
*/
|
||||
VAGABOND.defaultCritThreshold = 20;
|
||||
|
||||
/**
|
||||
* Speed values by Dexterity
|
||||
*/
|
||||
VAGABOND.speedByDex = {
|
||||
2: 25,
|
||||
3: 25,
|
||||
4: 30,
|
||||
5: 30,
|
||||
6: 35,
|
||||
7: 35
|
||||
};
|
||||
|
||||
/**
|
||||
* Max fatigue before death
|
||||
*/
|
||||
VAGABOND.maxFatigue = 5;
|
||||
|
||||
/**
|
||||
* Base item slots calculation
|
||||
*/
|
||||
VAGABOND.baseItemSlots = 8;
|
||||
139
module/vagabond.mjs
Normal file
139
module/vagabond.mjs
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Vagabond RPG System for Foundry VTT
|
||||
* @module vagabond
|
||||
*/
|
||||
|
||||
// Import configuration
|
||||
import { VAGABOND } from "./helpers/config.mjs";
|
||||
|
||||
// Import document classes
|
||||
// import { VagabondActor } from "./documents/actor.mjs";
|
||||
// import { VagabondItem } from "./documents/item.mjs";
|
||||
|
||||
// Import sheet classes
|
||||
// import { VagabondCharacterSheet } from "./sheets/actor-sheet.mjs";
|
||||
// import { VagabondItemSheet } from "./sheets/item-sheet.mjs";
|
||||
|
||||
// Import helper functions
|
||||
// import { preloadHandlebarsTemplates } from "./helpers/templates.mjs";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Foundry VTT Initialization */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Init hook - runs once when Foundry initializes
|
||||
*/
|
||||
Hooks.once("init", function() {
|
||||
console.log("Vagabond RPG | Initializing Vagabond RPG System");
|
||||
|
||||
// Add custom constants for configuration
|
||||
CONFIG.VAGABOND = VAGABOND;
|
||||
|
||||
// Define custom Document classes
|
||||
// CONFIG.Actor.documentClass = VagabondActor;
|
||||
// CONFIG.Item.documentClass = VagabondItem;
|
||||
|
||||
// Register sheet application classes
|
||||
// Actors.unregisterSheet("core", ActorSheet);
|
||||
// Actors.registerSheet("vagabond", VagabondCharacterSheet, {
|
||||
// types: ["character"],
|
||||
// makeDefault: true,
|
||||
// label: "VAGABOND.SheetCharacter"
|
||||
// });
|
||||
|
||||
// Items.unregisterSheet("core", ItemSheet);
|
||||
// Items.registerSheet("vagabond", VagabondItemSheet, {
|
||||
// makeDefault: true,
|
||||
// label: "VAGABOND.SheetItem"
|
||||
// });
|
||||
|
||||
// Preload Handlebars templates
|
||||
// return preloadHandlebarsTemplates();
|
||||
});
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Ready Hook */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Ready hook - runs when Foundry is fully loaded
|
||||
*/
|
||||
Hooks.once("ready", function() {
|
||||
console.log("Vagabond RPG | System Ready");
|
||||
|
||||
// Display welcome message for GMs
|
||||
if (game.user.isGM) {
|
||||
const version = game.system.version;
|
||||
ui.notifications.info(`Vagabond RPG v${version} - System loaded successfully!`);
|
||||
}
|
||||
});
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Handlebars Helpers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Define Handlebars helpers used throughout the system
|
||||
*/
|
||||
Hooks.once("init", function() {
|
||||
// Multiply helper for formulas
|
||||
Handlebars.registerHelper("multiply", function(a, b) {
|
||||
return Number(a) * Number(b);
|
||||
});
|
||||
|
||||
// Subtract helper
|
||||
Handlebars.registerHelper("subtract", function(a, b) {
|
||||
return Number(a) - Number(b);
|
||||
});
|
||||
|
||||
// Calculate difficulty (20 - stat or 20 - stat*2 if trained)
|
||||
Handlebars.registerHelper("difficulty", function(stat, trained) {
|
||||
const statValue = Number(stat) || 0;
|
||||
return trained ? 20 - (statValue * 2) : 20 - statValue;
|
||||
});
|
||||
|
||||
// Check if value equals comparison
|
||||
Handlebars.registerHelper("eq", function(a, b) {
|
||||
return a === b;
|
||||
});
|
||||
|
||||
// Check if value is greater than
|
||||
Handlebars.registerHelper("gt", function(a, b) {
|
||||
return Number(a) > Number(b);
|
||||
});
|
||||
|
||||
// Check if value is less than
|
||||
Handlebars.registerHelper("lt", function(a, b) {
|
||||
return Number(a) < Number(b);
|
||||
});
|
||||
|
||||
// Concatenate strings
|
||||
Handlebars.registerHelper("concat", function(...args) {
|
||||
// Remove the Handlebars options object from args
|
||||
args.pop();
|
||||
return args.join("");
|
||||
});
|
||||
|
||||
// Capitalize first letter
|
||||
Handlebars.registerHelper("capitalize", function(str) {
|
||||
if (typeof str !== "string") return "";
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
});
|
||||
|
||||
// Format number with sign (+/-)
|
||||
Handlebars.registerHelper("signedNumber", function(num) {
|
||||
const n = Number(num) || 0;
|
||||
return n >= 0 ? `+${n}` : `${n}`;
|
||||
});
|
||||
});
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Hot Reload Support (Development) */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept((newModule) => {
|
||||
console.log("Vagabond RPG | Hot reload triggered");
|
||||
});
|
||||
}
|
||||
26
package.json
Normal file
26
package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "vagabond-foundryvtt",
|
||||
"version": "0.1.0",
|
||||
"description": "Foundry VTT system for Vagabond RPG",
|
||||
"scripts": {
|
||||
"build": "sass styles/scss/vagabond.scss styles/vagabond.css --style=compressed",
|
||||
"watch": "sass styles/scss/vagabond.scss styles/vagabond.css --watch --style=expanded --source-map",
|
||||
"lint": "eslint module/",
|
||||
"release": "npm run build && zip -r vagabond.zip system.json module/ templates/ styles/vagabond.css lang/ packs/ assets/ LICENSE README.md"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/calcorum/vagabond-rpg-foundryvtt.git"
|
||||
},
|
||||
"author": "Cal Corum <cal.corum@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/calcorum/vagabond-rpg-foundryvtt/issues"
|
||||
},
|
||||
"homepage": "https://github.com/calcorum/vagabond-rpg-foundryvtt#readme",
|
||||
"devDependencies": {
|
||||
"sass": "^1.69.0",
|
||||
"eslint": "^8.56.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
156
styles/scss/_base.scss
Normal file
156
styles/scss/_base.scss
Normal file
@ -0,0 +1,156 @@
|
||||
// Vagabond RPG - Base Styles
|
||||
// ===========================
|
||||
|
||||
// Reset/normalize for Foundry context
|
||||
.vagabond {
|
||||
// Base typography
|
||||
font-family: $font-family-body;
|
||||
font-size: $font-size-base;
|
||||
line-height: $line-height-normal;
|
||||
color: $color-text-primary;
|
||||
|
||||
// Box sizing
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// Links
|
||||
a {
|
||||
color: $color-accent-primary;
|
||||
text-decoration: none;
|
||||
transition: color $transition-fast;
|
||||
|
||||
&:hover {
|
||||
color: $color-accent-secondary;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@include focus-visible;
|
||||
}
|
||||
|
||||
// Headings
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: $font-family-header;
|
||||
font-weight: $font-weight-bold;
|
||||
line-height: $line-height-tight;
|
||||
color: $color-text-primary;
|
||||
margin: 0 0 $spacing-2 0;
|
||||
}
|
||||
|
||||
h1 { font-size: $font-size-4xl; }
|
||||
h2 { font-size: $font-size-3xl; }
|
||||
h3 { font-size: $font-size-2xl; }
|
||||
h4 { font-size: $font-size-xl; }
|
||||
h5 { font-size: $font-size-lg; }
|
||||
h6 { font-size: $font-size-base; }
|
||||
|
||||
// Paragraphs
|
||||
p {
|
||||
margin: 0 0 $spacing-4 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Lists
|
||||
ul, ol {
|
||||
margin: 0 0 $spacing-4 0;
|
||||
padding-left: $spacing-6;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: $spacing-1;
|
||||
}
|
||||
|
||||
// Images
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
// Tables
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: $spacing-4;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: $spacing-2 $spacing-3;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid $color-border-light;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: $font-weight-semibold;
|
||||
background-color: $color-parchment-dark;
|
||||
}
|
||||
|
||||
// Horizontal rule
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid $color-border;
|
||||
margin: $spacing-4 0;
|
||||
}
|
||||
|
||||
// Code
|
||||
code {
|
||||
font-family: $font-family-mono;
|
||||
font-size: $font-size-sm;
|
||||
padding: $spacing-1 $spacing-2;
|
||||
background-color: $color-parchment-dark;
|
||||
border-radius: $radius-sm;
|
||||
}
|
||||
|
||||
// Blockquote
|
||||
blockquote {
|
||||
margin: 0 0 $spacing-4 0;
|
||||
padding: $spacing-3 $spacing-4;
|
||||
border-left: 3px solid $color-accent-primary;
|
||||
background-color: $color-parchment-dark;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// Selection
|
||||
::selection {
|
||||
background-color: $color-accent-highlight;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
}
|
||||
|
||||
// Foundry sheet wrapper styles
|
||||
.sheet.vagabond {
|
||||
@include custom-scrollbar;
|
||||
background: $color-parchment;
|
||||
background-image:
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
rgba($color-parchment-light, 0.3) 0%,
|
||||
transparent 5%,
|
||||
transparent 95%,
|
||||
rgba($color-parchment-dark, 0.3) 100%
|
||||
);
|
||||
}
|
||||
|
||||
// Window header customization
|
||||
.window-app.vagabond {
|
||||
.window-header {
|
||||
background: linear-gradient(to bottom, $color-parchment-dark, $color-parchment-darker);
|
||||
border-bottom: 2px solid $color-border-dark;
|
||||
|
||||
.window-title {
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-lg;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.header-button {
|
||||
color: $color-text-secondary;
|
||||
|
||||
&:hover {
|
||||
color: $color-text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
200
styles/scss/_mixins.scss
Normal file
200
styles/scss/_mixins.scss
Normal file
@ -0,0 +1,200 @@
|
||||
// Vagabond RPG - SCSS Mixins
|
||||
// ===========================
|
||||
|
||||
// Flexbox shortcuts
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@mixin flex-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@mixin flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// Grid shortcuts
|
||||
@mixin grid($columns: 1, $gap: $spacing-4) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat($columns, 1fr);
|
||||
gap: $gap;
|
||||
}
|
||||
|
||||
// Typography
|
||||
@mixin heading($size: $font-size-xl) {
|
||||
font-family: $font-family-header;
|
||||
font-size: $size;
|
||||
font-weight: $font-weight-bold;
|
||||
line-height: $line-height-tight;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
@mixin body-text($size: $font-size-base) {
|
||||
font-family: $font-family-body;
|
||||
font-size: $size;
|
||||
font-weight: $font-weight-normal;
|
||||
line-height: $line-height-normal;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
@mixin button-base {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $spacing-2 $spacing-4;
|
||||
font-family: $font-family-body;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $radius-md;
|
||||
cursor: pointer;
|
||||
transition: all $transition-fast;
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid $color-accent-primary;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin button-primary {
|
||||
@include button-base;
|
||||
background-color: $color-accent-primary;
|
||||
color: $color-text-inverse;
|
||||
border-color: $color-accent-secondary;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: $color-accent-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin button-secondary {
|
||||
@include button-base;
|
||||
background-color: transparent;
|
||||
color: $color-text-primary;
|
||||
border-color: $color-border;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: $color-parchment-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// Form inputs
|
||||
@mixin input-base {
|
||||
padding: $spacing-2 $spacing-3;
|
||||
font-family: $font-family-body;
|
||||
font-size: $font-size-base;
|
||||
color: $color-text-primary;
|
||||
background-color: $color-parchment-light;
|
||||
border: 1px solid $color-border;
|
||||
border-radius: $radius-md;
|
||||
transition: border-color $transition-fast;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $color-accent-primary;
|
||||
box-shadow: 0 0 0 2px rgba($color-accent-primary, 0.2);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: $color-parchment-darker;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// Cards/Panels
|
||||
@mixin panel {
|
||||
background-color: $color-parchment;
|
||||
border: 1px solid $color-border;
|
||||
border-radius: $radius-md;
|
||||
box-shadow: 0 1px 3px $shadow-light;
|
||||
}
|
||||
|
||||
@mixin panel-header {
|
||||
@include flex-between;
|
||||
padding: $spacing-3 $spacing-4;
|
||||
background-color: $color-parchment-dark;
|
||||
border-bottom: 1px solid $color-border;
|
||||
border-radius: $radius-md $radius-md 0 0;
|
||||
}
|
||||
|
||||
// Scrollbar styling
|
||||
@mixin custom-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: $color-parchment-dark;
|
||||
border-radius: $radius-full;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: $color-border;
|
||||
border-radius: $radius-full;
|
||||
|
||||
&:hover {
|
||||
background: $color-border-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Accessibility - focus visible
|
||||
@mixin focus-visible {
|
||||
&:focus-visible {
|
||||
outline: 2px solid $color-accent-primary;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Screen reader only (visually hidden but accessible)
|
||||
@mixin sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
// Truncate text with ellipsis
|
||||
@mixin truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Stat badge (for the large stat numbers)
|
||||
@mixin stat-badge($color: $color-text-primary) {
|
||||
@include flex-center;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-3xl;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $color;
|
||||
background-color: $color-parchment-light;
|
||||
border: 2px solid $color-border;
|
||||
border-radius: $radius-md;
|
||||
}
|
||||
102
styles/scss/_variables.scss
Normal file
102
styles/scss/_variables.scss
Normal file
@ -0,0 +1,102 @@
|
||||
// Vagabond RPG - SCSS Variables
|
||||
// ==============================
|
||||
|
||||
// Color Palette - Parchment Theme
|
||||
// Designed for WCAG AA accessibility (4.5:1 contrast ratio minimum)
|
||||
|
||||
// Background colors (parchment tones)
|
||||
$color-parchment-light: #f5f0e1;
|
||||
$color-parchment: #e8dcc8;
|
||||
$color-parchment-dark: #d4c4a8;
|
||||
$color-parchment-darker: #c4b08c;
|
||||
|
||||
// Text colors (high contrast)
|
||||
$color-text-primary: #2c2416; // Main text - dark brown/black
|
||||
$color-text-secondary: #4a3f2f; // Secondary text
|
||||
$color-text-muted: #6b5d4d; // Muted/disabled text
|
||||
$color-text-inverse: #f5f0e1; // Text on dark backgrounds
|
||||
|
||||
// Accent colors
|
||||
$color-accent-primary: #8b4513; // Saddle brown - primary actions
|
||||
$color-accent-secondary: #654321; // Dark brown - secondary
|
||||
$color-accent-highlight: #cd853f; // Peru - highlights/hover
|
||||
|
||||
// Semantic colors
|
||||
$color-success: #2d5a27; // Dark green - success/healing
|
||||
$color-danger: #8b0000; // Dark red - damage/danger
|
||||
$color-warning: #b8860b; // Dark goldenrod - warnings
|
||||
$color-info: #2f4f4f; // Dark slate gray - info
|
||||
|
||||
// Stat colors (for visual distinction)
|
||||
$color-might: #8b0000; // Red - strength/power
|
||||
$color-dexterity: #228b22; // Green - agility
|
||||
$color-awareness: #4169e1; // Royal blue - perception
|
||||
$color-reason: #9932cc; // Purple - intellect
|
||||
$color-presence: #daa520; // Goldenrod - charisma
|
||||
$color-luck: #20b2aa; // Light sea green - fortune
|
||||
|
||||
// Borders
|
||||
$color-border: #8b7355;
|
||||
$color-border-light: #a08060;
|
||||
$color-border-dark: #5c4a3a;
|
||||
|
||||
// Shadows
|
||||
$shadow-light: rgba(0, 0, 0, 0.1);
|
||||
$shadow-medium: rgba(0, 0, 0, 0.2);
|
||||
$shadow-dark: rgba(0, 0, 0, 0.3);
|
||||
|
||||
// Typography
|
||||
$font-family-header: 'Modesto Condensed', 'Roboto Slab', 'Georgia', serif;
|
||||
$font-family-body: 'Signika', 'Roboto', 'Helvetica Neue', sans-serif;
|
||||
$font-family-mono: 'Roboto Mono', 'Consolas', monospace;
|
||||
|
||||
// Font sizes (using rem for accessibility)
|
||||
$font-size-xs: 0.75rem; // 12px
|
||||
$font-size-sm: 0.875rem; // 14px
|
||||
$font-size-base: 1rem; // 16px
|
||||
$font-size-lg: 1.125rem; // 18px
|
||||
$font-size-xl: 1.25rem; // 20px
|
||||
$font-size-2xl: 1.5rem; // 24px
|
||||
$font-size-3xl: 1.875rem; // 30px
|
||||
$font-size-4xl: 2.25rem; // 36px
|
||||
|
||||
// Font weights
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-medium: 500;
|
||||
$font-weight-semibold: 600;
|
||||
$font-weight-bold: 700;
|
||||
|
||||
// Line heights
|
||||
$line-height-tight: 1.25;
|
||||
$line-height-normal: 1.5;
|
||||
$line-height-relaxed: 1.75;
|
||||
|
||||
// Spacing scale
|
||||
$spacing-0: 0;
|
||||
$spacing-1: 0.25rem; // 4px
|
||||
$spacing-2: 0.5rem; // 8px
|
||||
$spacing-3: 0.75rem; // 12px
|
||||
$spacing-4: 1rem; // 16px
|
||||
$spacing-5: 1.25rem; // 20px
|
||||
$spacing-6: 1.5rem; // 24px
|
||||
$spacing-8: 2rem; // 32px
|
||||
$spacing-10: 2.5rem; // 40px
|
||||
|
||||
// Border radius
|
||||
$radius-sm: 2px;
|
||||
$radius-md: 4px;
|
||||
$radius-lg: 8px;
|
||||
$radius-full: 9999px;
|
||||
|
||||
// Transitions
|
||||
$transition-fast: 150ms ease;
|
||||
$transition-base: 250ms ease;
|
||||
$transition-slow: 350ms ease;
|
||||
|
||||
// Z-index scale
|
||||
$z-dropdown: 100;
|
||||
$z-sticky: 200;
|
||||
$z-fixed: 300;
|
||||
$z-modal-backdrop: 400;
|
||||
$z-modal: 500;
|
||||
$z-tooltip: 600;
|
||||
153
styles/scss/chat/_chat-cards.scss
Normal file
153
styles/scss/chat/_chat-cards.scss
Normal file
@ -0,0 +1,153 @@
|
||||
// Vagabond RPG - Chat Card Styles
|
||||
// ================================
|
||||
|
||||
// Placeholder - will be expanded in Phase 7
|
||||
.vagabond.chat-card {
|
||||
@include panel;
|
||||
overflow: hidden;
|
||||
|
||||
// Card header
|
||||
.card-header {
|
||||
@include flex-between;
|
||||
padding: $spacing-2 $spacing-3;
|
||||
background-color: $color-parchment-dark;
|
||||
border-bottom: 1px solid $color-border;
|
||||
|
||||
.card-title {
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-size: $font-size-sm;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
// Card content
|
||||
.card-content {
|
||||
padding: $spacing-3;
|
||||
}
|
||||
|
||||
// Roll result
|
||||
.roll-result {
|
||||
@include flex-center;
|
||||
gap: $spacing-3;
|
||||
padding: $spacing-3;
|
||||
background-color: $color-parchment-light;
|
||||
border-radius: $radius-md;
|
||||
|
||||
.roll-total {
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-3xl;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.roll-formula {
|
||||
font-size: $font-size-sm;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
// Result status
|
||||
.result-status {
|
||||
@include flex-center;
|
||||
padding: $spacing-2;
|
||||
font-weight: $font-weight-bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
border-radius: $radius-md;
|
||||
|
||||
&.success {
|
||||
background-color: rgba($color-success, 0.2);
|
||||
color: $color-success;
|
||||
}
|
||||
|
||||
&.failure {
|
||||
background-color: rgba($color-danger, 0.2);
|
||||
color: $color-danger;
|
||||
}
|
||||
|
||||
&.critical {
|
||||
background-color: rgba($color-warning, 0.2);
|
||||
color: $color-warning;
|
||||
animation: pulse 1s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
// Damage display
|
||||
.damage-display {
|
||||
@include flex-center;
|
||||
gap: $spacing-2;
|
||||
margin-top: $spacing-3;
|
||||
padding: $spacing-2;
|
||||
background-color: rgba($color-danger, 0.1);
|
||||
border: 1px solid $color-danger;
|
||||
border-radius: $radius-md;
|
||||
|
||||
.damage-label {
|
||||
font-size: $font-size-sm;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.damage-value {
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-xl;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $color-danger;
|
||||
}
|
||||
|
||||
.damage-type {
|
||||
font-size: $font-size-sm;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
// Card buttons (for interactive cards)
|
||||
.card-buttons {
|
||||
display: flex;
|
||||
gap: $spacing-2;
|
||||
padding: $spacing-3;
|
||||
border-top: 1px solid $color-border;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spell card specific
|
||||
.vagabond.chat-card.spell-card {
|
||||
.spell-effect {
|
||||
padding: $spacing-3;
|
||||
font-style: italic;
|
||||
border-left: 3px solid $color-accent-primary;
|
||||
background-color: $color-parchment-light;
|
||||
margin: $spacing-3 0;
|
||||
}
|
||||
|
||||
.spell-meta {
|
||||
@include grid(2, $spacing-2);
|
||||
font-size: $font-size-sm;
|
||||
|
||||
.meta-item {
|
||||
@include flex-between;
|
||||
padding: $spacing-1;
|
||||
|
||||
.meta-label {
|
||||
color: $color-text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
108
styles/scss/components/_buttons.scss
Normal file
108
styles/scss/components/_buttons.scss
Normal file
@ -0,0 +1,108 @@
|
||||
// Vagabond RPG - Button Styles
|
||||
// =============================
|
||||
|
||||
.vagabond {
|
||||
// Primary button
|
||||
.btn-primary {
|
||||
@include button-primary;
|
||||
}
|
||||
|
||||
// Secondary button
|
||||
.btn-secondary {
|
||||
@include button-secondary;
|
||||
}
|
||||
|
||||
// Small button variant
|
||||
.btn-sm {
|
||||
padding: $spacing-1 $spacing-2;
|
||||
font-size: $font-size-xs;
|
||||
}
|
||||
|
||||
// Large button variant
|
||||
.btn-lg {
|
||||
padding: $spacing-3 $spacing-6;
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
|
||||
// Icon button (square, for icons only)
|
||||
.btn-icon {
|
||||
@include button-base;
|
||||
@include flex-center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: $color-parchment-dark;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
}
|
||||
|
||||
// Rollable button (for dice rolls)
|
||||
.btn-roll {
|
||||
@include button-base;
|
||||
background-color: $color-parchment-light;
|
||||
color: $color-text-primary;
|
||||
border-color: $color-border;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: $color-accent-highlight;
|
||||
border-color: $color-accent-primary;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: $spacing-2;
|
||||
}
|
||||
}
|
||||
|
||||
// Danger button
|
||||
.btn-danger {
|
||||
@include button-base;
|
||||
background-color: $color-danger;
|
||||
color: $color-text-inverse;
|
||||
border-color: darken($color-danger, 10%);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: darken($color-danger, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
// Success button
|
||||
.btn-success {
|
||||
@include button-base;
|
||||
background-color: $color-success;
|
||||
color: $color-text-inverse;
|
||||
border-color: darken($color-success, 10%);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: darken($color-success, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
// Button group
|
||||
.btn-group {
|
||||
display: inline-flex;
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
border-radius: 0;
|
||||
|
||||
&:first-child {
|
||||
border-radius: $radius-md 0 0 $radius-md;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 $radius-md $radius-md 0;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
181
styles/scss/components/_forms.scss
Normal file
181
styles/scss/components/_forms.scss
Normal file
@ -0,0 +1,181 @@
|
||||
// Vagabond RPG - Form Styles
|
||||
// ===========================
|
||||
|
||||
.vagabond {
|
||||
// Text input
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="search"] {
|
||||
@include input-base;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Number input (smaller for stats)
|
||||
input[type="number"] {
|
||||
text-align: center;
|
||||
|
||||
// Hide spinner buttons
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
// Textarea
|
||||
textarea {
|
||||
@include input-base;
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
// Select
|
||||
select {
|
||||
@include input-base;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%232c2416' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right $spacing-3 center;
|
||||
padding-right: $spacing-8;
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
input[type="checkbox"] {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
accent-color: $color-accent-primary;
|
||||
|
||||
@include focus-visible;
|
||||
}
|
||||
|
||||
// Checkbox with label
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-2;
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// Radio button
|
||||
input[type="radio"] {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
accent-color: $color-accent-primary;
|
||||
|
||||
@include focus-visible;
|
||||
}
|
||||
|
||||
// Form group (label + input)
|
||||
.form-group {
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: $spacing-1;
|
||||
font-weight: $font-weight-medium;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
&.inline {
|
||||
@include flex-between;
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
margin-right: $spacing-2;
|
||||
}
|
||||
|
||||
input, select {
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Form row (multiple inputs side by side)
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: $spacing-4;
|
||||
|
||||
.form-group {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Stat input (large centered number)
|
||||
.stat-input {
|
||||
@include stat-badge;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-3xl;
|
||||
font-weight: $font-weight-bold;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resource input (current / max)
|
||||
.resource-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-1;
|
||||
|
||||
input {
|
||||
width: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: $color-text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
// Inline editable field
|
||||
.inline-edit {
|
||||
padding: $spacing-1 $spacing-2;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $radius-sm;
|
||||
transition: all $transition-fast;
|
||||
|
||||
&:hover {
|
||||
border-color: $color-border-light;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: $color-parchment-light;
|
||||
border-color: $color-accent-primary;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Readonly display (looks like text, not input)
|
||||
.readonly-value {
|
||||
padding: $spacing-2 $spacing-3;
|
||||
background-color: $color-parchment-dark;
|
||||
border: 1px solid $color-border-light;
|
||||
border-radius: $radius-md;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
}
|
||||
111
styles/scss/components/_tabs.scss
Normal file
111
styles/scss/components/_tabs.scss
Normal file
@ -0,0 +1,111 @@
|
||||
// Vagabond RPG - Tab Styles
|
||||
// ==========================
|
||||
|
||||
.vagabond {
|
||||
// Tab navigation
|
||||
.sheet-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
border-bottom: 2px solid $color-border;
|
||||
background-color: $color-parchment-dark;
|
||||
|
||||
.item {
|
||||
margin: 0;
|
||||
padding: $spacing-2 $spacing-4;
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-medium;
|
||||
color: $color-text-secondary;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
cursor: pointer;
|
||||
transition: all $transition-fast;
|
||||
|
||||
&:hover {
|
||||
color: $color-text-primary;
|
||||
background-color: $color-parchment;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $color-text-primary;
|
||||
background-color: $color-parchment;
|
||||
border-bottom-color: $color-accent-primary;
|
||||
}
|
||||
|
||||
@include focus-visible;
|
||||
|
||||
i {
|
||||
margin-right: $spacing-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab content area
|
||||
.sheet-body {
|
||||
@include custom-scrollbar;
|
||||
overflow-y: auto;
|
||||
padding: $spacing-4;
|
||||
|
||||
.tab {
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical tabs variant
|
||||
.sheet-tabs.vertical {
|
||||
flex-direction: column;
|
||||
border-bottom: none;
|
||||
border-right: 2px solid $color-border;
|
||||
|
||||
.item {
|
||||
border-bottom: none;
|
||||
border-right: 2px solid transparent;
|
||||
margin-bottom: 0;
|
||||
margin-right: -2px;
|
||||
|
||||
&.active {
|
||||
border-right-color: $color-accent-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-tabs (within a tab)
|
||||
.sub-tabs {
|
||||
display: flex;
|
||||
gap: $spacing-2;
|
||||
margin-bottom: $spacing-4;
|
||||
padding-bottom: $spacing-2;
|
||||
border-bottom: 1px solid $color-border-light;
|
||||
|
||||
.sub-tab {
|
||||
padding: $spacing-1 $spacing-3;
|
||||
font-size: $font-size-sm;
|
||||
color: $color-text-muted;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $radius-md;
|
||||
cursor: pointer;
|
||||
transition: all $transition-fast;
|
||||
|
||||
&:hover {
|
||||
color: $color-text-primary;
|
||||
background-color: $color-parchment-dark;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $color-text-primary;
|
||||
background-color: $color-parchment-dark;
|
||||
border-color: $color-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
154
styles/scss/dialogs/_roll-dialog.scss
Normal file
154
styles/scss/dialogs/_roll-dialog.scss
Normal file
@ -0,0 +1,154 @@
|
||||
// Vagabond RPG - Roll Dialog Styles
|
||||
// ==================================
|
||||
|
||||
// Placeholder - will be expanded in Phase 7
|
||||
.vagabond.dialog.roll-dialog {
|
||||
.dialog-content {
|
||||
padding: $spacing-4;
|
||||
}
|
||||
|
||||
// Roll info section
|
||||
.roll-info {
|
||||
@include panel;
|
||||
padding: $spacing-3;
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
.difficulty-display {
|
||||
@include flex-center;
|
||||
gap: $spacing-4;
|
||||
|
||||
.difficulty-value {
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-3xl;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.crit-threshold {
|
||||
font-size: $font-size-sm;
|
||||
color: $color-text-muted;
|
||||
|
||||
.crit-value {
|
||||
color: $color-accent-primary;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Favor/Hinder toggles
|
||||
.modifiers {
|
||||
@include flex-center;
|
||||
gap: $spacing-4;
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
.modifier-toggle {
|
||||
@include flex-center;
|
||||
gap: $spacing-2;
|
||||
|
||||
label {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
&.favor .toggle-active {
|
||||
color: $color-success;
|
||||
}
|
||||
|
||||
&.hinder .toggle-active {
|
||||
color: $color-danger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Situational modifier
|
||||
.situational-modifier {
|
||||
@include flex-center;
|
||||
gap: $spacing-2;
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
input {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Roll button
|
||||
.roll-button {
|
||||
@include button-primary;
|
||||
width: 100%;
|
||||
padding: $spacing-3;
|
||||
font-size: $font-size-lg;
|
||||
|
||||
i {
|
||||
margin-right: $spacing-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spell cast dialog specific
|
||||
.vagabond.dialog.spell-cast-dialog {
|
||||
.mana-cost {
|
||||
@include panel;
|
||||
@include flex-between;
|
||||
padding: $spacing-3;
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
.cost-label {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
|
||||
.cost-value {
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-xl;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $color-accent-primary;
|
||||
}
|
||||
|
||||
&.insufficient {
|
||||
border-color: $color-danger;
|
||||
|
||||
.cost-value {
|
||||
color: $color-danger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delivery-selection,
|
||||
.duration-selection,
|
||||
.damage-selection {
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
.selection-label {
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-2;
|
||||
}
|
||||
|
||||
.selection-grid {
|
||||
@include grid(2, $spacing-2);
|
||||
}
|
||||
|
||||
.selection-option {
|
||||
@include panel;
|
||||
padding: $spacing-2;
|
||||
cursor: pointer;
|
||||
transition: all $transition-fast;
|
||||
|
||||
&:hover {
|
||||
border-color: $color-accent-primary;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $color-parchment-dark;
|
||||
border-color: $color-accent-primary;
|
||||
}
|
||||
|
||||
.option-name {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.option-cost {
|
||||
font-size: $font-size-sm;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
83
styles/scss/sheets/_actor-sheet.scss
Normal file
83
styles/scss/sheets/_actor-sheet.scss
Normal file
@ -0,0 +1,83 @@
|
||||
// Vagabond RPG - Actor Sheet Styles
|
||||
// ==================================
|
||||
|
||||
// Placeholder - will be expanded in Phase 3
|
||||
.vagabond.sheet.actor {
|
||||
min-width: 600px;
|
||||
min-height: 500px;
|
||||
|
||||
.sheet-header {
|
||||
@include flex-between;
|
||||
padding: $spacing-4;
|
||||
background-color: $color-parchment-dark;
|
||||
border-bottom: 2px solid $color-border;
|
||||
|
||||
.profile-img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border: 2px solid $color-border;
|
||||
border-radius: $radius-md;
|
||||
}
|
||||
|
||||
.header-fields {
|
||||
flex: 1;
|
||||
margin-left: $spacing-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Character sheet specific
|
||||
.vagabond.sheet.actor.character {
|
||||
// Stats column (left side, matching official sheet)
|
||||
.stats-column {
|
||||
@include flex-column;
|
||||
gap: $spacing-3;
|
||||
padding: $spacing-4;
|
||||
background-color: $color-parchment-dark;
|
||||
border-right: 2px solid $color-border;
|
||||
|
||||
.stat-block {
|
||||
text-align: center;
|
||||
|
||||
.stat-label {
|
||||
font-family: $font-family-header;
|
||||
font-size: $font-size-xs;
|
||||
font-weight: $font-weight-bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: $color-text-secondary;
|
||||
margin-bottom: $spacing-1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
@include stat-badge;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NPC/Monster sheet specific
|
||||
.vagabond.sheet.actor.npc {
|
||||
min-width: 400px;
|
||||
|
||||
.stat-block-display {
|
||||
@include panel;
|
||||
padding: $spacing-4;
|
||||
|
||||
.stat-line {
|
||||
@include flex-between;
|
||||
padding: $spacing-1 0;
|
||||
border-bottom: 1px solid $color-border-light;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
styles/scss/sheets/_item-sheet.scss
Normal file
101
styles/scss/sheets/_item-sheet.scss
Normal file
@ -0,0 +1,101 @@
|
||||
// Vagabond RPG - Item Sheet Styles
|
||||
// =================================
|
||||
|
||||
// Placeholder - will be expanded in Phase 4
|
||||
.vagabond.sheet.item {
|
||||
min-width: 400px;
|
||||
min-height: 300px;
|
||||
|
||||
.sheet-header {
|
||||
@include flex-between;
|
||||
padding: $spacing-4;
|
||||
background-color: $color-parchment-dark;
|
||||
border-bottom: 2px solid $color-border;
|
||||
|
||||
.item-img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border: 2px solid $color-border;
|
||||
border-radius: $radius-md;
|
||||
}
|
||||
|
||||
.header-fields {
|
||||
flex: 1;
|
||||
margin-left: $spacing-4;
|
||||
}
|
||||
}
|
||||
|
||||
.sheet-body {
|
||||
padding: $spacing-4;
|
||||
}
|
||||
|
||||
// Item description
|
||||
.item-description {
|
||||
.editor {
|
||||
min-height: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spell item specific
|
||||
.vagabond.sheet.item.spell {
|
||||
.spell-details {
|
||||
@include grid(2);
|
||||
margin-bottom: $spacing-4;
|
||||
}
|
||||
|
||||
.delivery-options {
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
.option-grid {
|
||||
@include grid(3, $spacing-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perk item specific
|
||||
.vagabond.sheet.item.perk {
|
||||
.prerequisites {
|
||||
@include panel;
|
||||
padding: $spacing-3;
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
.prereq-list {
|
||||
@include flex-column;
|
||||
gap: $spacing-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Weapon item specific
|
||||
.vagabond.sheet.item.weapon {
|
||||
.weapon-properties {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $spacing-2;
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
.property-tag {
|
||||
padding: $spacing-1 $spacing-2;
|
||||
font-size: $font-size-sm;
|
||||
background-color: $color-parchment-dark;
|
||||
border: 1px solid $color-border;
|
||||
border-radius: $radius-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Class item specific
|
||||
.vagabond.sheet.item.class {
|
||||
.progression-table {
|
||||
width: 100%;
|
||||
margin-bottom: $spacing-4;
|
||||
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: $color-parchment-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
styles/scss/vagabond.scss
Normal file
24
styles/scss/vagabond.scss
Normal file
@ -0,0 +1,24 @@
|
||||
// Vagabond RPG - Foundry VTT System Styles
|
||||
// ==========================================
|
||||
|
||||
// Configuration
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
// Base styles
|
||||
@import 'base';
|
||||
|
||||
// Components
|
||||
@import 'components/buttons';
|
||||
@import 'components/forms';
|
||||
@import 'components/tabs';
|
||||
|
||||
// Sheets
|
||||
@import 'sheets/actor-sheet';
|
||||
@import 'sheets/item-sheet';
|
||||
|
||||
// Dialogs
|
||||
@import 'dialogs/roll-dialog';
|
||||
|
||||
// Chat
|
||||
@import 'chat/chat-cards';
|
||||
127
system.json
Normal file
127
system.json
Normal file
@ -0,0 +1,127 @@
|
||||
{
|
||||
"id": "vagabond",
|
||||
"title": "Vagabond RPG",
|
||||
"description": "A Foundry VTT system implementation for Vagabond RPG - Pulp Fantasy Roleplaying",
|
||||
"version": "0.1.0",
|
||||
"compatibility": {
|
||||
"minimum": "13",
|
||||
"verified": "13",
|
||||
"maximum": "13"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Cal Corum",
|
||||
"email": "cal.corum@gmail.com"
|
||||
}
|
||||
],
|
||||
"esmodules": ["module/vagabond.mjs"],
|
||||
"styles": ["styles/vagabond.css"],
|
||||
"languages": [
|
||||
{
|
||||
"lang": "en",
|
||||
"name": "English",
|
||||
"path": "lang/en.json"
|
||||
}
|
||||
],
|
||||
"packs": [
|
||||
{
|
||||
"name": "ancestries",
|
||||
"label": "Ancestries",
|
||||
"path": "packs/ancestries",
|
||||
"type": "Item",
|
||||
"ownership": {
|
||||
"PLAYER": "OBSERVER",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "classes",
|
||||
"label": "Classes",
|
||||
"path": "packs/classes",
|
||||
"type": "Item",
|
||||
"ownership": {
|
||||
"PLAYER": "OBSERVER",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spells",
|
||||
"label": "Spells",
|
||||
"path": "packs/spells",
|
||||
"type": "Item",
|
||||
"ownership": {
|
||||
"PLAYER": "OBSERVER",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "perks",
|
||||
"label": "Perks",
|
||||
"path": "packs/perks",
|
||||
"type": "Item",
|
||||
"ownership": {
|
||||
"PLAYER": "OBSERVER",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "weapons",
|
||||
"label": "Weapons",
|
||||
"path": "packs/weapons",
|
||||
"type": "Item",
|
||||
"ownership": {
|
||||
"PLAYER": "OBSERVER",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "armor",
|
||||
"label": "Armor",
|
||||
"path": "packs/armor",
|
||||
"type": "Item",
|
||||
"ownership": {
|
||||
"PLAYER": "OBSERVER",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "equipment",
|
||||
"label": "Equipment",
|
||||
"path": "packs/equipment",
|
||||
"type": "Item",
|
||||
"ownership": {
|
||||
"PLAYER": "OBSERVER",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bestiary",
|
||||
"label": "Bestiary",
|
||||
"path": "packs/bestiary",
|
||||
"type": "Actor",
|
||||
"ownership": {
|
||||
"PLAYER": "NONE",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
}
|
||||
],
|
||||
"packFolders": [
|
||||
{
|
||||
"name": "Vagabond RPG",
|
||||
"sorting": "a",
|
||||
"packs": ["ancestries", "classes", "spells", "perks", "weapons", "armor", "equipment", "bestiary"]
|
||||
}
|
||||
],
|
||||
"socket": false,
|
||||
"url": "https://github.com/calcorum/vagabond-rpg-foundryvtt",
|
||||
"manifest": "https://github.com/calcorum/vagabond-rpg-foundryvtt/releases/latest/download/system.json",
|
||||
"download": "https://github.com/calcorum/vagabond-rpg-foundryvtt/releases/latest/download/vagabond.zip",
|
||||
"license": "LICENSE",
|
||||
"readme": "README.md",
|
||||
"bugs": "https://github.com/calcorum/vagabond-rpg-foundryvtt/issues",
|
||||
"changelog": "CHANGELOG.md",
|
||||
"primaryTokenAttribute": "resources.hp",
|
||||
"secondaryTokenAttribute": "resources.mana",
|
||||
"gridDistance": 5,
|
||||
"gridUnits": "ft"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user