chore: initial deploy — SLA Industries Foundry VTT

Co-authored-by: slaguru666 <timevans666@gmail.com>
This commit is contained in:
2026-03-31 17:00:35 +01:00
commit 4d8cfa6e48
17 changed files with 2248 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
node_modules/
npm-debug.log*
.npm/
dist/
*.min.js
*.min.css
.DS_Store
.DS_Store?
._*
Thumbs.db
.vscode/
.idea/
*.swp
*.swo
worlds/
+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#9a7cff" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#9a7cff" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#9a7cff" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#f3efff"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#57b3ff" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#57b3ff" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#57b3ff" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#eef6ff"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#78d48f" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#78d48f" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#78d48f" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#effef2"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#9da6b3" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#9da6b3" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#9da6b3" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#f2f4f7"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#41d0a3" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#41d0a3" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#41d0a3" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#eefef9"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#f5f0d7" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#f5f0d7" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#f5f0d7" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#fffdf6"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#ff6f6f" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#ff6f6f" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#ff6f6f" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#fff0f0"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#d7dee8" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#d7dee8" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#d7dee8" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#fbfdff"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#dce8f9" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#dce8f9" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#dce8f9" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#ffffff"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<rect width="128" height="128" rx="24" fill="#101722"/>
<rect x="8" y="8" width="112" height="112" rx="20" fill="none" stroke="#ffdb6e" stroke-width="4"/>
<path d="M64 18 95 34v28c0 24-14 39-31 48C47 101 33 86 33 62V34z" fill="#ffdb6e" opacity=".16"/>
<path d="M64 26 87 38v22c0 18-10 30-23 38-13-8-23-20-23-38V38z" fill="none" stroke="#ffdb6e" stroke-width="4"/>
<path d="M49 50h20c10 0 16 5 16 13 0 6-3 10-9 12 8 2 12 6 12 14 0 10-8 17-22 17H49z" fill="#fff8de"/>
<path d="M59 58v14h8c5 0 8-3 8-7s-3-7-8-7zm0 22v15h9c6 0 10-3 10-7 0-6-4-8-10-8z" fill="#101722"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

+352
View File
@@ -0,0 +1,352 @@
{
"colours": [
{
"key": "blue",
"label": "Blue",
"type": "Street Maintenance",
"summary": "Street maintenance and dirty clean-up contracts.",
"details": "Sewer clearances, vermin extermination, infrastructure support, and low-end evictions.",
"sclReq": "10 - 9",
"sclRange": { "min": 0.05, "typical": 0.1, "max": 0.2 },
"cbsPerOpRange": { "min": 50, "max": 150 },
"cbsPerSquadRange": { "min": 100, "max": 300 },
"mediaCoverage": "Rare / None"
},
{
"key": "yellow",
"label": "Yellow",
"type": "Retrieval",
"summary": "Recover a person, item, data package, or specimen.",
"details": "Primary objective is bring it back, often issued by Housing, Cloak, or Karma.",
"sclReq": "10 - 9",
"sclRange": { "min": 0.05, "typical": 0.1, "max": 0.2 },
"cbsPerOpRange": { "min": 100, "max": 200 },
"cbsPerSquadRange": { "min": 200, "max": 400 },
"mediaCoverage": "Station Analysis / None"
},
{
"key": "green",
"label": "Green",
"type": "Cloak / Extermination",
"summary": "Combat-focused extermination and suppression assignments.",
"details": "Kill or capture designated threats before they escalate into full emergency response.",
"sclReq": "10 - 8",
"sclRange": { "min": 0.1, "typical": 0.1, "max": 0.3 },
"cbsPerOpRange": { "min": 100, "max": 250 },
"cbsPerSquadRange": { "min": 200, "max": 500 },
"mediaCoverage": "Station Analysis / Third Eye"
},
{
"key": "white",
"label": "White",
"type": "Investigation",
"summary": "Detective and analytical assignments.",
"details": "Evidence-led cases with witness work, profiling, and pattern analysis.",
"sclReq": "10 - 7",
"sclRange": { "min": 0.1, "typical": 0.1, "max": 0.3 },
"cbsPerOpRange": { "min": 150, "max": 300 },
"cbsPerSquadRange": { "min": 250, "max": 500 },
"mediaCoverage": "Third Eye / Station Analysis"
},
{
"key": "grey",
"label": "Grey",
"type": "Departmental Enforcement / Corporate",
"summary": "Internal enforcement and politically sensitive corporate work.",
"details": "Escort, asset protection, audit support, and departmentally awkward clean-ups.",
"sclReq": "9 - 7",
"sclRange": { "min": 0.1, "typical": 0.2, "max": 0.3 },
"cbsPerOpRange": { "min": 200, "max": 400 },
"cbsPerSquadRange": { "min": 300, "max": 600 },
"mediaCoverage": "Station Analysis"
},
{
"key": "silver",
"label": "Silver",
"type": "Diplomatic / Escort / PR",
"summary": "High-profile, social, and reputation-sensitive contracts.",
"details": "VIP escort, public appearances, negotiations, and camera-facing operations.",
"sclReq": "9 - 6",
"sclRange": { "min": 0.1, "typical": 0.2, "max": 0.4 },
"cbsPerOpRange": { "min": 200, "max": 500 },
"cbsPerSquadRange": { "min": 300, "max": 800 },
"mediaCoverage": "Third Eye / GoreZone"
},
{
"key": "jade",
"label": "Jade",
"type": "Environmental / Biohazard",
"summary": "Containment and neutralisation of biological and chemical threats.",
"details": "Biohazard incidents, toxic leaks, mutations, and containment failures.",
"sclReq": "9 - 6",
"sclRange": { "min": 0.1, "typical": 0.2, "max": 0.4 },
"cbsPerOpRange": { "min": 250, "max": 500 },
"cbsPerSquadRange": { "min": 400, "max": 800 },
"mediaCoverage": "Third Eye / Contract Circuit"
},
{
"key": "red",
"label": "Red",
"type": "Emergency Response",
"summary": "Crisis-response and red-alert operations.",
"details": "Riots, terror incidents, major failures, and high-risk live-response deployments.",
"sclReq": "10 - 5",
"sclRange": { "min": 0.1, "typical": 0.2, "max": 0.5 },
"cbsPerOpRange": { "min": 200, "max": 500 },
"cbsPerSquadRange": { "min": 300, "max": 1000 },
"mediaCoverage": "Third Eye / GoreZone"
},
{
"key": "black",
"label": "Black",
"type": "Covert / Black Ops",
"summary": "Classified, deniable, and often off-record operations.",
"details": "Assassination, sabotage, deep infiltration, and disavowed clean-up actions.",
"sclReq": "7 - 4",
"sclRange": { "min": 0.2, "typical": 0.4, "max": 0.7 },
"cbsPerOpRange": { "min": 500, "max": 1000 },
"cbsPerSquadRange": { "min": 800, "max": 2000 },
"mediaCoverage": "None (classified)"
},
{
"key": "platinum",
"label": "Platinum",
"type": "Head Office / Top Secret",
"summary": "Highest-level Head Office operations.",
"details": "World-shaping, deeply classified, and politically lethal assignments.",
"sclReq": "5 - 1",
"sclRange": { "min": 0.3, "typical": 0.5, "max": 1.0 },
"cbsPerOpRange": { "min": 1000, "max": null },
"cbsPerSquadRange": { "min": 1500, "max": null },
"mediaCoverage": "Classified / Special"
}
],
"colourRollTable": [
{ "min": 1, "max": 4, "key": "blue" },
{ "min": 5, "max": 7, "key": "yellow" },
{ "min": 8, "max": 9, "key": "green" },
{ "min": 10, "max": 12, "key": "white" },
{ "min": 13, "max": 13, "key": "grey" },
{ "min": 14, "max": 14, "key": "silver" },
{ "min": 15, "max": 15, "key": "jade" },
{ "min": 16, "max": 17, "key": "red" },
{ "min": 18, "max": 18, "key": "black" },
{ "min": 19, "max": 19, "key": "black" },
{ "min": 20, "max": 20, "key": "platinum" }
],
"missionFocus": [
"Eliminate a threat before it reaches public view.",
"Capture a target alive for interrogation.",
"Protect or escort a person through a hostile district.",
"Protect or escort a cargo asset.",
"Retrieve an item or specimen intact.",
"Retrieve data or recordings without contamination.",
"Investigate a crime scene with political fallout.",
"Profile and locate a serial killer before media lock-on.",
"Contain or quarantine an outbreak or Ebb event.",
"Crowd control and riot suppression under cameras.",
"Hunt a named hostile and confirm status.",
"Clean up an old SLA operational mess."
],
"locations": [
"Downtown backstreets.",
"Sewer network and maintenance tunnels.",
"Industrial sector and power spine.",
"Residential block or tenement arc.",
"Transit system and monorail stations.",
"Cannibal Sector perimeter.",
"Corporate laboratory or asset vault.",
"Nightclub and entertainment block.",
"Broadcast tower and media hub.",
"Hospital, triage zone, or Karma lab."
],
"opposition": [
"Street gang with light arms and local lookouts.",
"Organised crime syndicate with political backing.",
"Serial killer cell already moving to a second target.",
"DarkNight agents with a deniable brief.",
"Carrien pack using service tunnels.",
"Rogue biogenetic asset or escaped Stormer variant.",
"Rival SLA squad chasing the same payout.",
"Corrupt Shivers protecting the wrong people.",
"Cultists, Sh'ien influence, or Ebb residue.",
"Corporate security with strict no-survivor orders."
],
"complications": [
"The issuing department withheld one critical fact.",
"Third Eye goes live before the squad enters the zone.",
"A witness can identify one operative by name.",
"The target is embedded in civilians.",
"The package is alive and unstable.",
"Transit lockdown traps everyone inside the district.",
"The real employer is not the signatory on the BPN.",
"L.A.D. response in the zone is unreliable.",
"Communications are jammed or intercepted.",
"The BPN is partly a ratings play dressed up as emergency work."
],
"colourMissionIdeas": {
"blue": [
"Clear out Carrien and secure the pump station before repairs begin.",
"Survey a derelict subway spur and mark safe lanes for clean-up crews.",
"Evict a low-level gang from a recycling depot without wrecking the machinery."
],
"yellow": [
"Recover a stolen prototype before it disappears through the black market.",
"Locate a missing departmental courier and return the carried drives intact.",
"Retrieve a captured informant before rivals extract anything useful."
],
"green": [
"Neutralise a threat before it can trigger full emergency escalation.",
"Sweep a district after repeated monster sightings and stop the panic source.",
"Guard a prisoner transfer while hostile assets try to reclaim the cargo."
],
"white": [
"Uncover who is issuing forged BPNs to lure squads into ambushes.",
"Trace sabotaged SLA-manufactured ammo back to the source.",
"Link a string of identical killings across multiple sectors."
],
"grey": [
"Protect engineers installing surveillance hardware in a tense district.",
"Assist a corporate audit team investigating missing shipments.",
"Guard a prototype vehicle during a live test run."
],
"silver": [
"Escort a diplomatic envoy without provoking a public incident.",
"Keep a sponsor-backed appearance alive while controlling optics.",
"Secure a high-profile interview venue before the cameras roll."
],
"jade": [
"Contain a biohazard leak in a mixed-use block and secure samples.",
"Neutralise a mutated outbreak before it reaches transport lines.",
"Lock down a failed research ward and extract surviving technicians."
],
"red": [
"Board a hijacked monorail in motion and rescue the hostages.",
"Retake a broadcast tower seized by extremists.",
"Stabilise a district where fires, looters, and panic are all spreading."
],
"black": [
"Infiltrate a sealed compound, remove a target, and leave no public trace.",
"Sabotage a rival operation and redirect blame elsewhere.",
"Extract records from an internal vault while command denies the mission exists."
],
"platinum": [
"Recover a lost prototype before anyone realises it escaped.",
"Investigate an off-limits sector where sensors keep going dark.",
"Quietly remove an inconvenient public hero from the board."
]
},
"contactRoles": [
"Shiver precinct liaison",
"Mort Department dispatcher",
"Department-M observer",
"Corporate asset handler",
"Embedded media fixer",
"Sector security captain",
"Karma field scientist",
"Independent financier",
"Freelance financier",
"Darkfinder case officer"
],
"contactFirstNames": [
"Anika",
"Blaine",
"Caro",
"Drex",
"Ephra",
"Fenn",
"Galia",
"Havel",
"Iona",
"Jarek",
"Kiva",
"Luther",
"Mara",
"Nikol",
"Orin",
"Petra"
],
"contactLastNames": [
"Voss",
"Kane",
"Talon",
"Mercer",
"Sloane",
"Varik",
"Drahl",
"Kessler",
"Morn",
"Black",
"Harrow",
"Vale",
"Kestral",
"Renn",
"Soren",
"Yarrow"
],
"civilianFactions": [
"Local residents trying to keep their block standing.",
"Black-market traders who know the unofficial routes.",
"A fan club for a specific operative or squad.",
"A semi-legal street clinic with decent gear and bad ties.",
"Displaced refugees from a devastated sector.",
"A corporate outreach team doing smiling PR work."
],
"oppositionAttitudes": [
"Underestimate the squad and get cocky.",
"Are terrified of SLA but cornered and desperate.",
"See the squad as expendable obstacles, not people.",
"Are only in it for the money.",
"Hope to cut a deal or run rather than die.",
"Use the squad as a distraction for a bigger move."
],
"atmospheres": [
"Oppressive rain, thunder, and ankle-deep water in the streets.",
"Chemical smog and neon halos around every light source.",
"Rolling blackouts, flicker failures, and generator hum.",
"Industrial noise and heavy machine vibration.",
"Crowded streets with sale events or festivals in progress.",
"Curfew with Shivers on every corner and everyone watching."
],
"localTwists": [
"The district is regarded as cursed by locals.",
"A recent promo shoot left half-finished set pieces everywhere.",
"A minor celebrity is present and drawing attention.",
"Surveillance coverage is patchy or disabled.",
"Someone is independently live-streaming the operation.",
"A rival squad failed a BPN here recently and left damage behind."
],
"extraObjectives": [
"Capture at least one enemy alive for interrogation.",
"Recover specific data intact with no EMP or overkill.",
"Ensure a particular building remains undamaged.",
"Plant a tracking device unnoticed.",
"Secure a specific piece of gear for a sponsor.",
"Ensure that no civilians witness one critical event."
],
"escalationEvents": [
"Opposition reinforcements arrive mid-operation.",
"The environment becomes more dangerous: fire, flood, or collapse.",
"Media crews show up and start reframing the mission.",
"A key NPC flees, forcing an ugly pursuit.",
"Communications fail at the worst possible moment.",
"A supposedly neutral faction chooses a side."
],
"issuingDepartments": [
"Mort Department / Ops Control",
"Department-M / Internal Containment",
"Shiver Command Liaison Desk",
"Karma Hazard Response",
"Cloak Tactical Oversight",
"Media Relations / Sponsor Operations",
"Head Office Special Assignments"
],
"mediaOptions": [
"None",
"Rare / None",
"Station Analysis",
"Third Eye News",
"GoreZone / Contract Circuit Special",
"Classified / Special"
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"SLABPNDispatch.Title": "SLA BPN Dispatch"
}
+32
View File
@@ -0,0 +1,32 @@
{
"id": "sla-bpn-dispatch",
"title": "SLA BPN Dispatch",
"description": "Creates random SLA BPN briefings and matching GM debrief journals using SLA mission colour logic.",
"version": "0.3.0",
"compatibility": {
"minimum": "13",
"verified": "13.350",
"maximum": "13"
},
"authors": [
{
"name": "Tim Evans"
}
],
"esmodules": [
"module/sla-bpn-dispatch.mjs"
],
"styles": [
"styles/sla-bpn-dispatch.css"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"manifest": "https://raw.githubusercontent.com/slaguru666/sla-bpn-dispatch/main/module.json",
"download": "https://github.com/slaguru666/sla-bpn-dispatch/releases/latest/download/sla-bpn-dispatch.zip",
"url": "https://github.com/slaguru666/sla-bpn-dispatch"
}
File diff suppressed because it is too large Load Diff
+471
View File
@@ -0,0 +1,471 @@
.sla-bpn-dispatch {
font-family: "Trebuchet MS", "Arial", sans-serif;
}
.sla-bpn-dispatch .window-content {
background:
radial-gradient(circle at top right, rgba(255, 255, 255, 0.06), transparent 28%),
linear-gradient(180deg, #0d1117 0%, #151d27 56%, #11151d 100%);
color: #edf3fb !important;
}
.sla-bpn-dispatch-form {
--panel: rgba(18, 24, 34, 0.92);
--panel-alt: rgba(11, 16, 24, 0.9);
--line: rgba(255, 255, 255, 0.11);
--soft: rgba(255, 255, 255, 0.68);
--text: #edf3fb;
display: grid;
gap: 14px;
padding: 4px;
background-image:
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
background-size: 26px 26px;
}
.sla-bpn-dispatch .window-title,
.sla-bpn-dispatch p,
.sla-bpn-dispatch span,
.sla-bpn-dispatch strong,
.sla-bpn-dispatch label,
.sla-bpn-dispatch h1,
.sla-bpn-dispatch h2,
.sla-bpn-dispatch h3,
.sla-bpn-dispatch input,
.sla-bpn-dispatch select,
.sla-bpn-dispatch textarea,
.sla-bpn-dispatch option {
color: var(--text) !important;
}
.sla-bpn-hero {
display: grid;
grid-template-columns: 150px 1fr 310px;
gap: 14px;
align-items: stretch;
padding: 16px;
border: 1px solid color-mix(in srgb, var(--bpn-accent) 55%, transparent);
border-radius: 18px;
background:
linear-gradient(135deg, color-mix(in srgb, var(--bpn-accent) 16%, transparent), transparent 45%),
linear-gradient(180deg, rgba(21, 28, 39, 0.95), rgba(8, 13, 20, 0.97));
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03) inset, 0 18px 32px rgba(0, 0, 0, 0.28);
}
.sla-bpn-hero-band {
display: grid;
place-items: center;
align-content: center;
min-height: 112px;
border-radius: 14px;
background:
linear-gradient(180deg, color-mix(in srgb, var(--bpn-accent) 25%, #0c1119), #0d1117);
border: 1px solid color-mix(in srgb, var(--bpn-accent) 58%, transparent);
font-size: 1.25rem;
font-weight: 900;
letter-spacing: 0.14em;
text-transform: uppercase;
text-align: center;
}
.sla-bpn-hero-icon {
width: 60px;
height: 60px;
margin-bottom: 8px;
}
.sla-bpn-hero-band-label {
line-height: 1.05;
}
.sla-bpn-overline {
font-size: 0.74rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: color-mix(in srgb, var(--bpn-accent) 66%, #dce6f3) !important;
}
.sla-bpn-hero-copy h1 {
margin: 8px 0 4px;
font-size: 2rem;
line-height: 1.05;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.sla-bpn-hero-copy p {
margin: 0;
color: var(--soft) !important;
font-size: 0.98rem;
}
.sla-bpn-hero-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.sla-bpn-chip {
padding: 10px 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.035);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.sla-bpn-chip span {
display: block;
margin-bottom: 4px;
font-size: 0.7rem;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
color: color-mix(in srgb, var(--bpn-accent) 55%, #dde7f5) !important;
}
.sla-bpn-chip strong {
font-size: 1rem;
font-weight: 900;
}
.sla-bpn-dispatch-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.sla-bpn-dispatch-grid--top,
.sla-bpn-dispatch-grid--meta {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.sla-bpn-dispatch-grid--top {
grid-template-columns: 1.15fr 1fr 0.9fr 1fr;
}
.sla-bpn-dispatch-field,
.sla-bpn-dispatch-card {
position: relative;
padding: 12px 14px;
border-radius: 16px;
background:
linear-gradient(180deg, rgba(27, 35, 48, 0.92), rgba(13, 18, 26, 0.95));
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
.sla-bpn-dispatch-field::before,
.sla-bpn-dispatch-card::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 4px;
border-radius: 16px 0 0 16px;
background: linear-gradient(180deg, var(--bpn-accent), transparent);
opacity: 0.8;
}
.sla-bpn-dispatch-field label,
.sla-bpn-dispatch-card h3 {
display: block;
margin-bottom: 8px;
font-size: 0.74rem;
font-weight: 900;
letter-spacing: 0.12em;
text-transform: uppercase;
color: color-mix(in srgb, var(--bpn-accent) 55%, #ebf4ff) !important;
}
.sla-bpn-dispatch-card p {
margin: 0 0 8px;
color: #f2f6fd !important;
}
.sla-bpn-dispatch-card p strong {
color: var(--bpn-accent) !important;
}
.sla-bpn-dispatch-field input,
.sla-bpn-dispatch-field select,
.sla-bpn-dispatch-field textarea {
width: 100%;
padding: 10px 12px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(5, 9, 14, 0.48);
color: #f5f8fd !important;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
.sla-bpn-dispatch-field textarea {
min-height: 90px;
resize: vertical;
}
.sla-bpn-field-help {
margin-top: 8px;
font-size: 0.72rem;
line-height: 1.25;
color: color-mix(in srgb, var(--bpn-accent) 38%, #c2cfdf) !important;
}
.sla-bpn-dispatch-field--wide textarea,
.sla-bpn-dispatch-field textarea[name="missionBrief"] {
min-height: 120px;
}
.sla-bpn-dispatch-field--danger {
background:
linear-gradient(180deg, rgba(45, 21, 28, 0.9), rgba(18, 11, 16, 0.96));
border-color: rgba(255, 123, 123, 0.18);
}
.sla-bpn-dispatch-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.sla-bpn-dispatch-actions button,
.sla-bpn-dispatch-open,
.sla-bpn-dispatch-settings-open {
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--bpn-accent) 45%, rgba(255,255,255,0.2)) !important;
background: linear-gradient(180deg, color-mix(in srgb, var(--bpn-accent) 48%, #f7d17d), color-mix(in srgb, var(--bpn-accent) 36%, #8f631d)) !important;
color: #111317 !important;
font-weight: 900;
letter-spacing: 0.05em;
text-transform: uppercase;
padding: 10px 16px;
}
.sla-bpn-dispatch-launch-row {
display: flex;
justify-content: flex-end;
padding: 6px 0 0;
}
.journal-sidebar .sla-bpn-dispatch-launch-row .sla-bpn-dispatch-open,
.tab.journal .sla-bpn-dispatch-launch-row .sla-bpn-dispatch-open,
.sla-bpn-dispatch-launch-row .sla-bpn-dispatch-open {
width: 100%;
justify-content: center;
display: inline-flex;
align-items: center;
gap: 8px;
color: #f8f2e3 !important;
background: linear-gradient(180deg, #1d2430, #10161f) !important;
border: 1px solid color-mix(in srgb, var(--bpn-accent) 38%, #ffb174) !important;
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.04);
}
.sla-bpn-dispatch-actions button:hover,
.sla-bpn-dispatch-open:hover,
.sla-bpn-dispatch-settings-open:hover {
filter: brightness(1.06);
}
.sla-bpn-dispatch-form[data-colour-key="red"] .sla-bpn-hero,
.sla-bpn-dispatch-form[data-colour-key="black"] .sla-bpn-hero,
.sla-bpn-dispatch-form[data-colour-key="jade"] .sla-bpn-hero {
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03) inset, 0 18px 32px rgba(0, 0, 0, 0.36), 0 0 28px color-mix(in srgb, var(--bpn-accent) 18%, transparent);
}
.chat-message .sla-bpn-chat {
--bpn-accent: #dce8f9;
color: #181512 !important;
background:
radial-gradient(circle at top right, rgba(0,0,0,0.04), transparent 30%),
linear-gradient(180deg, #f0ead8 0%, #e6ddc6 100%);
border: 1px solid rgba(56, 43, 20, 0.18);
border-radius: 16px;
overflow: hidden;
}
.sla-bpn-chat__hero {
display: grid;
grid-template-columns: 64px 1fr;
gap: 12px;
align-items: center;
padding: 14px;
background:
linear-gradient(90deg, color-mix(in srgb, var(--bpn-accent) 28%, transparent), transparent 50%),
rgba(16, 20, 27, 0.94);
}
.sla-bpn-chat__icon {
width: 56px;
height: 56px;
}
.sla-bpn-chat__eyebrow {
font-size: 0.66rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: color-mix(in srgb, var(--bpn-accent) 70%, #ffffff) !important;
}
.sla-bpn-chat__heading h3,
.sla-bpn-chat__heading p {
margin: 0;
color: #f4f7fb !important;
}
.sla-bpn-chat__heading h3 {
font-size: 1.55rem;
line-height: 1;
text-transform: uppercase;
}
.sla-bpn-chat__heading p {
margin-top: 6px;
font-weight: 700;
color: color-mix(in srgb, var(--bpn-accent) 32%, #f4f7fb) !important;
}
.sla-bpn-chat__body {
padding: 14px;
}
.sla-bpn-chat__brief {
margin: 0 0 12px;
font-size: 1rem;
line-height: 1.42;
color: #211b15 !important;
}
.sla-bpn-chat__grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.sla-bpn-chat__grid div {
padding: 10px 12px;
border-radius: 12px;
background: rgba(255,255,255,0.46);
border: 1px solid rgba(56, 43, 20, 0.12);
}
.sla-bpn-chat__grid strong,
.sla-bpn-chat__grid span,
.sla-bpn-chat__grid em {
display: block;
color: #191613 !important;
}
.sla-bpn-chat__grid strong {
margin-bottom: 4px;
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.sla-bpn-chat__grid em {
margin-top: 4px;
opacity: 0.72;
font-style: normal;
}
.sla-bpn-journal {
--bpn-accent: #dce8f9;
color: #181512;
display: grid;
gap: 16px;
}
.sla-bpn-journal__hero {
display: grid;
grid-template-columns: 88px 1fr;
gap: 16px;
align-items: center;
padding: 18px;
border-radius: 18px;
background:
linear-gradient(90deg, color-mix(in srgb, var(--bpn-accent) 22%, transparent), transparent 45%),
linear-gradient(180deg, #161c24, #10161e);
}
.sla-bpn-journal__icon {
width: 76px;
height: 76px;
}
.sla-bpn-journal__eyebrow {
font-size: 0.72rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: color-mix(in srgb, var(--bpn-accent) 68%, #ffffff) !important;
}
.sla-bpn-journal__hero h2,
.sla-bpn-journal__hero p {
margin: 0;
color: #f6f8fc !important;
}
.sla-bpn-journal__hero h2 {
margin-top: 6px;
text-transform: uppercase;
}
.sla-bpn-journal__hero p {
margin-top: 8px;
color: color-mix(in srgb, var(--bpn-accent) 35%, #f6f8fc) !important;
}
.sla-bpn-journal__meta,
.sla-bpn-journal__grid {
display: grid;
gap: 12px;
}
.sla-bpn-journal__meta {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.sla-bpn-journal__grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sla-bpn-journal__meta div,
.sla-bpn-journal__grid div,
.sla-bpn-journal__briefing {
padding: 14px 16px;
border-radius: 16px;
background: linear-gradient(180deg, #f4eddb, #e6dcc5);
border: 1px solid rgba(72, 54, 24, 0.16);
}
.sla-bpn-journal__meta strong,
.sla-bpn-journal__grid strong,
.sla-bpn-journal__briefing h3 {
display: block;
margin-bottom: 6px;
font-size: 0.76rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: color-mix(in srgb, var(--bpn-accent) 40%, #312615) !important;
}
.sla-bpn-journal__meta span,
.sla-bpn-journal__grid span,
.sla-bpn-journal__briefing p {
color: #1b1712 !important;
}
@media (max-width: 960px) {
.sla-bpn-hero {
grid-template-columns: 1fr;
}
.sla-bpn-hero-meta,
.sla-bpn-dispatch-grid--top,
.sla-bpn-dispatch-grid--meta,
.sla-bpn-dispatch-grid,
.sla-bpn-chat__grid,
.sla-bpn-journal__meta,
.sla-bpn-journal__grid {
grid-template-columns: 1fr;
}
}
+168
View File
@@ -0,0 +1,168 @@
<form class="sla-bpn-dispatch-form" data-colour-key="{{draft.colourKey}}" style="--bpn-accent: {{draft.colourAccent}};">
<header class="sla-bpn-hero">
<div class="sla-bpn-hero-band">
<img class="sla-bpn-hero-icon" src="{{draft.iconPath}}" alt="{{draft.colourLabel}} BPN">
<div class="sla-bpn-hero-band-label">{{draft.colourLabel}} BPN</div>
</div>
<div class="sla-bpn-hero-copy">
<div class="sla-bpn-overline">SLA INDUSTRIES // CORPORATE DISPATCH GRID</div>
<h1>{{draft.shortTitle}}</h1>
<p>{{draft.colourType}} // {{draft.colourSummary}}</p>
</div>
<div class="sla-bpn-hero-meta">
<div class="sla-bpn-chip">
<span>BPN ID</span>
<strong>{{draft.bpnId}}</strong>
</div>
<div class="sla-bpn-chip">
<span>SCL</span>
<strong>{{draft.sclRequirement}}</strong>
</div>
<div class="sla-bpn-chip">
<span>Gross</span>
<strong>{{draft.grossContract}}</strong>
</div>
<div class="sla-bpn-chip">
<span>Take-home</span>
<strong>{{draft.estimatedTakeHomeEach}}</strong>
</div>
</div>
</header>
<section class="sla-bpn-dispatch-grid sla-bpn-dispatch-grid--top">
<div class="sla-bpn-dispatch-field">
<label>Job Name</label>
<input type="text" name="shortTitle" value="{{draft.shortTitle}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>BPN ID</label>
<input type="text" name="bpnId" value="{{draft.bpnId}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Colour Band</label>
<select name="colourKey">
{{selectOptions colourOptions selected=draft.colourKey valueAttr="key" labelAttr="label"}}
</select>
<div class="sla-bpn-field-help">{{draft.colourType}}</div>
</div>
<div class="sla-bpn-dispatch-field">
<label>Scenario Frame</label>
<select name="frameKey">
{{selectOptions frameOptions selected=draft.frameKey valueAttr="key" labelAttr="label"}}
</select>
<div class="sla-bpn-field-help">{{draft.shortTitle}}</div>
</div>
</section>
<section class="sla-bpn-dispatch-grid sla-bpn-dispatch-grid--meta">
<div class="sla-bpn-dispatch-field">
<label>Issuing Department</label>
<input type="text" name="issuingDepartment" value="{{draft.issuingDepartment}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Squad Size</label>
<input type="number" min="1" max="8" name="squadSize" value="{{draft.squadSize}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Primary Contact</label>
<input type="text" name="primaryContact" value="{{draft.primaryContact}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Contact Position</label>
<input type="text" name="primaryContactLocation" value="{{draft.primaryContactLocation}}">
</div>
</section>
<section class="sla-bpn-dispatch-grid sla-bpn-dispatch-grid--summary">
<div class="sla-bpn-dispatch-card">
<h3>Colour Dossier</h3>
<p><strong>Type:</strong> {{draft.colourType}}</p>
<p><strong>Summary:</strong> {{draft.colourSummary}}</p>
<p><strong>Details:</strong> {{draft.colourDetails}}</p>
<p><strong>Primary Contact:</strong> {{draft.primaryContact}}</p>
</div>
<div class="sla-bpn-dispatch-card">
<h3>Pressure Stack</h3>
<p><strong>Media:</strong> {{draft.mediaCoverage}}</p>
<p><strong>CBS Band:</strong> {{draft.cbsBand}}</p>
<p><strong>Financier Cut:</strong> {{draft.financierCut}}</p>
<p><strong>SCL Increase:</strong> {{draft.sclIncrease}}</p>
<p><strong>Threat Lead:</strong> {{draft.threatLead}}</p>
</div>
</section>
<section class="sla-bpn-dispatch-grid">
<div class="sla-bpn-dispatch-field">
<label>Mission Focus</label>
<input type="text" name="missionFocus" value="{{draft.missionFocus}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Operational Hook</label>
<input type="text" name="colourMissionIdea" value="{{draft.colourMissionIdea}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Location</label>
<input type="text" name="location" value="{{draft.location}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Atmosphere</label>
<input type="text" name="atmosphere" value="{{draft.atmosphere}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Opposition</label>
<input type="text" name="opposition" value="{{draft.opposition}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Opposition Attitude</label>
<input type="text" name="oppositionAttitude" value="{{draft.oppositionAttitude}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Civilian Faction</label>
<input type="text" name="civilianFaction" value="{{draft.civilianFaction}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Extra Objective</label>
<input type="text" name="extraObjective" value="{{draft.extraObjective}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Escalation Event</label>
<input type="text" name="escalationEvent" value="{{draft.escalationEvent}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Threat Lead</label>
<input type="text" name="threatLead" value="{{draft.threatLead}}">
</div>
<div class="sla-bpn-dispatch-field">
<label>Threat Count</label>
<input type="text" name="threatCountLabel" value="{{draft.threatCountLabel}}">
</div>
</section>
<section class="sla-bpn-dispatch-grid">
<div class="sla-bpn-dispatch-field">
<label>Complication</label>
<textarea name="complication">{{draft.complication}}</textarea>
</div>
<div class="sla-bpn-dispatch-field sla-bpn-dispatch-field--danger">
<label>GM Twist</label>
<textarea name="localTwist">{{draft.localTwist}}</textarea>
</div>
</section>
<section class="sla-bpn-dispatch-field sla-bpn-dispatch-field--wide">
<label>Mission Brief</label>
<textarea name="missionBrief">{{draft.missionBrief}}</textarea>
</section>
<section class="sla-bpn-dispatch-field">
<label>Operational Notes</label>
<textarea name="operationalNotes">{{draft.operationalNotes}}</textarea>
</section>
<div class="sla-bpn-dispatch-actions">
<button type="button" data-action="randomize">Randomize BPN</button>
<button type="button" data-action="chat">Post To Chat</button>
<button type="button" data-action="journal">Create Journal + NPCs</button>
<button type="button" data-action="save">Save Draft</button>
</div>
</form>