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:
Cal Corum 2025-12-12 14:32:15 -06:00
commit 37300ccf90
23 changed files with 3410 additions and 0 deletions

12
.env.example Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

110
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;

View 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);
}
}

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

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

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

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

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

View 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
View 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
View 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"
}