Reusable framework-free component (core generation engine, seeded procedural SVG art, render layer) plus a standalone app built on it. Produces two separate surfaces per briefing — Player Brief and GM Dossier — in the SLA house style with bundled local fonts. Per-band procedural artwork, deterministic from seed. NPC/monster generation left to a future companion module via a clean seam. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
363 lines
25 KiB
JavaScript
363 lines
25 KiB
JavaScript
/* Hand-authored scenario frames, two or more per colour band. Each frame is a
|
|
coherent, ready-to-run BPN: player-facing situation plus the GM's true
|
|
pressure point and twist. The `contactArchetype` / `threatArchetype` fields
|
|
are deliberately retained as a seam — a future NPC/monster module can read
|
|
them to spin up stat blocks without this module owning any engine schema. */
|
|
|
|
export const FRAMES = {
|
|
blue: [
|
|
{
|
|
id: "blue-pump-clearance",
|
|
shortTitle: "Pump Ghosts",
|
|
missionFocus: "Eliminate a threat before it reaches public view.",
|
|
colourMissionIdea: "Clear Carrien from a sewer pumping station before repair crews go live on-site.",
|
|
location: "Sewer network and maintenance tunnels beneath a low-SCL tenement arc.",
|
|
atmosphere: "Oppressive rain above ground, ankle-deep runoff below, and every tunnel echoing with machine throb.",
|
|
opposition: "Carrien packs and one nest-alpha using the flooded access galleries.",
|
|
oppositionAttitude: "Are starving, territorial, and driven to charge anything making light or noise.",
|
|
civilianFaction: "Maintenance workers trying to keep the block from flooding into the hab levels.",
|
|
extraObjective: "Recover one intact sample of the contamination feeding the nest.",
|
|
escalationEvent: "If the pumps stay offline too long, the whole block begins to flood and civilians panic upward.",
|
|
complication: "The maintenance foreman has been selling access schematics to scavengers and will try to hide it.",
|
|
localTwist: "The contamination is not accidental; someone seeded the tunnels to force a redevelopment contract.",
|
|
issuingDepartment: "Mort Department / Ops Control",
|
|
contactRole: "Shift Maintenance Controller",
|
|
contactArchetype: "controller",
|
|
contactLocation: "A barricaded relay room overlooking the lower pump shafts.",
|
|
threatArchetype: "carrien",
|
|
threatRole: "Nest Alpha",
|
|
threatCountLabel: "One alpha and a fast-moving pack."
|
|
},
|
|
{
|
|
id: "blue-eviction-cleanout",
|
|
shortTitle: "Residue Notice",
|
|
missionFocus: "Clean up an old SLA operational mess.",
|
|
colourMissionIdea: "Evict squatters and secure a condemned recycler before demolition crews arrive.",
|
|
location: "A half-powered recycling depot in a condemned housing strip.",
|
|
atmosphere: "Chemical fog hangs low and the whole block stinks of hot metal, wet plaster, and old blood.",
|
|
opposition: "A desperate street gang holding the depot plus a few terrified scavenger families inside the same shell.",
|
|
oppositionAttitude: "Talk hard, posture for cameras, but are ready to fold if offered one way out.",
|
|
civilianFaction: "Neighbourhood residents who want the gang gone but do not trust SLA to distinguish them from the problem.",
|
|
extraObjective: "Keep the recycler's core machinery functional for sponsor inspection.",
|
|
escalationEvent: "A scavenger fire starts in the upper catwalks, pushing everyone toward the same exit lanes.",
|
|
complication: "The depot contains evidence from an earlier SLA kill-team visit that nobody filed correctly.",
|
|
localTwist: "The original paperwork deliberately misclassified the site so a corporate shell company could seize it cheaply.",
|
|
issuingDepartment: "Department-M / Internal Containment",
|
|
contactRole: "Department-M Disposal Clerk",
|
|
contactArchetype: "analyst",
|
|
contactLocation: "A mobile command kiosk parked behind a portable blast shield.",
|
|
threatArchetype: "ganger",
|
|
threatRole: "Depot Gang Boss",
|
|
threatCountLabel: "One loud boss and half a dozen badly coordinated shooters."
|
|
}
|
|
],
|
|
yellow: [
|
|
{
|
|
id: "yellow-prototype-recovery",
|
|
shortTitle: "Cold Return",
|
|
missionFocus: "Retrieve an item or specimen intact.",
|
|
colourMissionIdea: "Recover a stolen prototype before it disappears into the black-market chain.",
|
|
location: "A transit interchange feeding into a hidden market spine.",
|
|
atmosphere: "Packed concourses, vendor noise, flashing route boards, and too many bodies to easily track the courier.",
|
|
opposition: "A retrieval crew working for organised crime with local route control and fast bikes on standby.",
|
|
oppositionAttitude: "Would rather run than fight, but will kill witnesses if boxed in.",
|
|
civilianFaction: "Black-market traders who know the unofficial routes and sell them to whoever pays first.",
|
|
extraObjective: "Bring back the data case unopened and verifiably untampered.",
|
|
escalationEvent: "The courier network triggers a station lockdown and reroutes all civilian flow through the kill zone.",
|
|
complication: "The package is still broadcasting, making every interested party converge on the same target.",
|
|
localTwist: "The prototype is bait; Head Office actually wants the retrieval crew followed back to a higher-value buyer.",
|
|
issuingDepartment: "Cloak Tactical Oversight",
|
|
contactRole: "Independent Financier",
|
|
contactArchetype: "fixer",
|
|
contactLocation: "A quiet cafe mezzanine overlooking the station's arrivals board.",
|
|
threatArchetype: "retrieval-crew",
|
|
threatRole: "Courier Handler",
|
|
threatCountLabel: "One handler, two bike-runners, and a sniper looking after the exchange."
|
|
}
|
|
],
|
|
green: [
|
|
{
|
|
id: "green-cannibal-perimeter",
|
|
shortTitle: "Wall Bite",
|
|
missionFocus: "Eliminate a threat before it reaches public view.",
|
|
colourMissionIdea: "Hit a breach team before it can break through the Cannibal Sector perimeter and trigger a broadcast panic.",
|
|
location: "The Cannibal Sector perimeter wall and the service roads wrapped around it.",
|
|
atmosphere: "Sirens, floodlights, wet concrete, and the constant thump of distant impact on the wall plating.",
|
|
opposition: "Cannibal raiders with improvised breaching charges and a local guide inside the city side.",
|
|
oppositionAttitude: "Are committed, feral, and keen to take someone back with them if the breach fails.",
|
|
civilianFaction: "A transient cluster of freight workers trapped near the service roads during lockdown.",
|
|
extraObjective: "Capture one living raider who can identify the city-side collaborator.",
|
|
escalationEvent: "A secondary breach warning forces Shiver units to peel away, leaving the squad exposed.",
|
|
complication: "The internal guide is wearing a legitimate contractor pass and can blend into evac traffic.",
|
|
localTwist: "The breach is a distraction for an unrelated extraction happening two gates away.",
|
|
issuingDepartment: "Shiver Command Liaison Desk",
|
|
contactRole: "Perimeter Shiver Sergeant",
|
|
contactArchetype: "shiver",
|
|
contactLocation: "An armoured checkpoint container bolted to the inner wall road.",
|
|
threatArchetype: "raider",
|
|
threatRole: "Breacher Chief",
|
|
threatCountLabel: "A brutal lead breacher with a fast assault cell behind him."
|
|
},
|
|
{
|
|
id: "green-kill-order",
|
|
shortTitle: "Dead Feed",
|
|
missionFocus: "Hunt a named hostile and confirm status.",
|
|
colourMissionIdea: "Stop a contract killer before the second murder finishes the media pattern.",
|
|
location: "A rain-soaked rooftop district threaded with service bridges and maintenance lifts.",
|
|
atmosphere: "Visibility is broken by rain, neon reflections, and intermittent drone spotlights.",
|
|
opposition: "A professional killer with a two-person support team and pre-positioned escape routes.",
|
|
oppositionAttitude: "Remain cool, efficient, and willing to trade casualties for a clean getaway window.",
|
|
civilianFaction: "A cluster of thrill-streamers trying to capture the chase from nearby rooftops.",
|
|
extraObjective: "Recover the killer's weapon package and comms slate intact.",
|
|
escalationEvent: "The support team remotely kills the lights and forces the pursuit onto unsecured skybridges.",
|
|
complication: "The target has a face the public already knows, making collateral narrative control much harder.",
|
|
localTwist: "The supposed contract is only the visible layer; the true target is hidden among the responding authorities.",
|
|
issuingDepartment: "Mort Department / Ops Control",
|
|
contactRole: "Veteran Desk Op",
|
|
contactArchetype: "detective",
|
|
contactLocation: "A temporary command room in a shuttered rooftop transit office.",
|
|
threatArchetype: "killer",
|
|
threatRole: "Named Contract Killer",
|
|
threatCountLabel: "One surgical hitter backed by two disciplined support shooters."
|
|
}
|
|
],
|
|
white: [
|
|
{
|
|
id: "white-serial-pattern",
|
|
shortTitle: "Witness Static",
|
|
missionFocus: "Investigate a crime scene with political fallout.",
|
|
colourMissionIdea: "Link a string of identical killings before the murderer reaches a protected witness.",
|
|
location: "A residential block, its adjoining monorail stop, and the forensic seal around the newest murder scene.",
|
|
atmosphere: "The block is unnaturally quiet, everyone is watching from behind cheap screens, and Shivers are over-managing the cordon.",
|
|
opposition: "A serial killer cell staging scenes to manipulate a bigger investigation.",
|
|
oppositionAttitude: "Enjoy the attention, leave deliberate clues, and want the squad to see the pattern too late.",
|
|
civilianFaction: "Residents terrified of speaking after seeing just enough to know SLA is already late.",
|
|
extraObjective: "Secure the witness and move them somewhere the killer cannot predict.",
|
|
escalationEvent: "An old evidence trail suddenly points at a different scene and splits available manpower.",
|
|
complication: "The witness's own statement is incomplete because they are protecting someone inside the block.",
|
|
localTwist: "The killer is recreating an older SLA-blacklisted case and expects the same official cover-up.",
|
|
issuingDepartment: "Mort Department / Ops Control",
|
|
contactRole: "Case Liaison Detective",
|
|
contactArchetype: "detective",
|
|
contactLocation: "A sealed witness room improvised from a laundrette office.",
|
|
threatArchetype: "killer",
|
|
threatRole: "Pattern Cell Leader",
|
|
threatCountLabel: "A lead killer with one cleanup partner and one obsessed documentarian."
|
|
},
|
|
{
|
|
id: "white-ammo-sabotage",
|
|
shortTitle: "Spent Truth",
|
|
missionFocus: "Retrieve data or recordings without contamination.",
|
|
colourMissionIdea: "Trace sabotaged SLA ammunition back through the supply chain before anyone realises how deep it goes.",
|
|
location: "A corporate ammo depot, a Shiver evidence locker, and a low-grade loading district in between.",
|
|
atmosphere: "Power flickers every few minutes, loading servitors keep stuttering mid-task, and everybody is too tense to blink.",
|
|
opposition: "Corporate security and one inside accountant trying to erase the trail before it reaches Head Office.",
|
|
oppositionAttitude: "Prefer intimidation and paperwork pressure, but will switch to lethal containment if exposed.",
|
|
civilianFaction: "Dock loaders who know the movement pattern of every crate but do not trust official investigators.",
|
|
extraObjective: "Recover one clean crate and one tampered crate for comparison testing.",
|
|
escalationEvent: "Depot lockdown procedures seal the archive level and begin deleting local logs.",
|
|
complication: "The inside accountant is cooperating because their family is already off the board if they stop.",
|
|
localTwist: "The sabotage was designed to make a specific squad fail publicly rather than to broadly poison supply.",
|
|
issuingDepartment: "Department-M / Internal Containment",
|
|
contactRole: "Corporate Asset Auditor",
|
|
contactArchetype: "analyst",
|
|
contactLocation: "A dark logistics office above the depot loading floor.",
|
|
threatArchetype: "corpsec",
|
|
threatRole: "Security Overseer",
|
|
threatCountLabel: "One hard-edged overseer and a disciplined internal security team."
|
|
}
|
|
],
|
|
grey: [
|
|
{
|
|
id: "grey-audit-escort",
|
|
shortTitle: "Quiet Ledger",
|
|
missionFocus: "Protect or escort a cargo asset.",
|
|
colourMissionIdea: "Keep an audit convoy moving while everyone who profited from the missing cargo tries to erase the proof.",
|
|
location: "An industrial spine road running between bonded warehouses and a secure internal archive.",
|
|
atmosphere: "The sector is all sodium light, surveillance masts, and thin excuses from everyone in a suit.",
|
|
opposition: "Departmentally protected smugglers, hired heavies, and one executive aide running the cover story.",
|
|
oppositionAttitude: "Would rather pressure, delay, and redirect than openly trade fire in company view.",
|
|
civilianFaction: "Warehouse staff who know something is wrong but fear losing the only stable work they have.",
|
|
extraObjective: "Keep the audit chain unbroken from seizure to final archive.",
|
|
escalationEvent: "A sponsor legal team arrives and tries to freeze the whole operation with paperwork.",
|
|
complication: "The archive itself contains evidence implicating the issuing department.",
|
|
localTwist: "The convoy is only half the story; the real theft walked out days ago wearing a department badge.",
|
|
issuingDepartment: "Department-M / Internal Containment",
|
|
contactRole: "Departmental Enforcement Liaison",
|
|
contactArchetype: "corporate",
|
|
contactLocation: "An armoured office container chained to the convoy lead truck.",
|
|
threatArchetype: "corpsec",
|
|
threatRole: "Internal Security Chief",
|
|
threatCountLabel: "One security chief and a quiet detail of professional enforcers."
|
|
}
|
|
],
|
|
silver: [
|
|
{
|
|
id: "silver-live-escort",
|
|
shortTitle: "Smile Cut",
|
|
missionFocus: "Protect or escort a person through a hostile district.",
|
|
colourMissionIdea: "Keep a sponsor-backed public figure alive without letting the cameras see how close the job gets.",
|
|
location: "A ribbon of sponsor venues, red-carpet choke points, and one ugly service corridor nobody told the cameras about.",
|
|
atmosphere: "Flash lamps, branded drones, crowd heat, and just enough panic underneath the applause to be dangerous.",
|
|
opposition: "A stalker cell, freelance agitators, and one rival sponsor fixer hoping to turn the event into a scandal.",
|
|
oppositionAttitude: "Would prefer embarrassment over blood but are ready to escalate if the window closes.",
|
|
civilianFaction: "Fans and paid attendees packed too tightly to separate cleanly from the real threat.",
|
|
extraObjective: "Protect the sponsor narrative as much as the body being moved.",
|
|
escalationEvent: "A live-feed malfunction dumps backstage footage into the public stream and sends the route sideways.",
|
|
complication: "The VIP is hiding a personal agenda and keeps deviating from the protected path.",
|
|
localTwist: "The entire appearance is bait designed to expose who inside the sponsor chain is leaking movement plans.",
|
|
issuingDepartment: "Media Relations / Sponsor Operations",
|
|
contactRole: "Media Fixer",
|
|
contactArchetype: "media",
|
|
contactLocation: "A mobile control booth hidden behind the main broadcast gantry.",
|
|
threatArchetype: "security",
|
|
threatRole: "Hostile Fixer",
|
|
threatCountLabel: "One well-dressed operator with hired hands embedded in the crowd."
|
|
}
|
|
],
|
|
jade: [
|
|
{
|
|
id: "jade-containment",
|
|
shortTitle: "Wet Bloom",
|
|
missionFocus: "Contain or quarantine an outbreak or Ebb event.",
|
|
colourMissionIdea: "Lock down a contaminated block before the spill crosses into the transit grid.",
|
|
location: "A mixed-use block connected to drainage canals and a damaged med-lab service annex.",
|
|
atmosphere: "Filtered alarms, chemical stink, and a wet sheen on every surface that might be alive.",
|
|
opposition: "Mutated survivors, a failed lab asset, and one containment officer already planning to burn the evidence.",
|
|
oppositionAttitude: "Act unpredictably and spike between blind panic and sudden aggression.",
|
|
civilianFaction: "A street clinic trying to keep the least infected residents alive long enough to evacuate.",
|
|
extraObjective: "Secure one uncontaminated sample for Karma analysis.",
|
|
escalationEvent: "Containment seals begin dropping floor by floor, isolating parts of the squad from one another.",
|
|
complication: "The official quarantine map is wrong and funnels civilians toward the hottest zone.",
|
|
localTwist: "The contamination profile matches a programme nobody admits still exists.",
|
|
issuingDepartment: "Karma Hazard Response",
|
|
contactRole: "Field Scientist",
|
|
contactArchetype: "scientist",
|
|
contactLocation: "A decontamination vestibule made from two bolted ambulance trailers.",
|
|
threatArchetype: "stormer",
|
|
threatRole: "Mutated Lab Asset",
|
|
threatCountLabel: "One unstable asset and several contaminated survivors acting around it."
|
|
}
|
|
],
|
|
red: [
|
|
{
|
|
id: "red-monorail-hostage",
|
|
shortTitle: "Dead Platform",
|
|
missionFocus: "Protect or escort a person through a hostile district.",
|
|
colourMissionIdea: "Board a hijacked monorail, cut the threat out, and stop a public disaster before it multiplies.",
|
|
location: "A moving monorail line, two sealed stations, and the service deck beneath the track.",
|
|
atmosphere: "Screaming brakes, emergency klaxons, static over public address, and media drones already racing the train.",
|
|
opposition: "A terror cell with improvised explosives and a hardline trigger operator.",
|
|
oppositionAttitude: "Need the spectacle as much as the tactical win and will kill quickly if denied it.",
|
|
civilianFaction: "Hostages trapped inside the train with just enough courage to help if guided correctly.",
|
|
extraObjective: "Keep the train itself intact enough for a survivor extraction.",
|
|
escalationEvent: "A secondary device threatens the inbound station and splits command priorities.",
|
|
complication: "One hostage is secretly tied to the cell and is feeding them squad movement.",
|
|
localTwist: "The train was chosen because someone important is riding under a false manifest.",
|
|
issuingDepartment: "Shiver Command Liaison Desk",
|
|
contactRole: "Transit Crisis Controller",
|
|
contactArchetype: "controller",
|
|
contactLocation: "A sealed signal room with live route telemetry and too many shouting department heads.",
|
|
threatArchetype: "riot",
|
|
threatRole: "Cell Trigger Man",
|
|
threatCountLabel: "One detonation lead, two shooters, and a train full of leverage."
|
|
},
|
|
{
|
|
id: "red-broadcast-retake",
|
|
shortTitle: "Signal Mercy",
|
|
missionFocus: "Eliminate a threat before it reaches public view.",
|
|
colourMissionIdea: "Retake a broadcast tower before the final message turns a bad district into a citywide panic.",
|
|
location: "A media tower, its control floors, and the access streets packed with fleeing civilians.",
|
|
atmosphere: "Emergency beacons, dropped live feeds, smoke in the ventilation stacks, and every screen in Mort pointing at the same problem.",
|
|
opposition: "An extremist crew with tower access and stolen media credentials.",
|
|
oppositionAttitude: "Expect to die but want the audience to watch it happen in the right order.",
|
|
civilianFaction: "Panicking evacuees clogging the lower service roads and making every clean shot harder.",
|
|
extraObjective: "Recover the message core and identify who green-lit tower access.",
|
|
escalationEvent: "Backup transmitters spin up on a timer and broaden the threat window.",
|
|
complication: "The tower engineer who can shut the system down has been wounded and hidden by the crowd.",
|
|
localTwist: "The message is less propaganda than data dump, and one department in SLA wants it to air.",
|
|
issuingDepartment: "Media Relations / Sponsor Operations",
|
|
contactRole: "Broadcast Security Captain",
|
|
contactArchetype: "shiver",
|
|
contactLocation: "A lower-floor security post running on emergency battery.",
|
|
threatArchetype: "security",
|
|
threatRole: "Tower Seizure Lead",
|
|
threatCountLabel: "One charismatic lead and a handful of committed technical shooters."
|
|
}
|
|
],
|
|
black: [
|
|
{
|
|
id: "black-silent-vault",
|
|
shortTitle: "Mute Entry",
|
|
missionFocus: "Retrieve data or recordings without contamination.",
|
|
colourMissionIdea: "Slip into a sealed vault, lift one file set, and leave the archive looking untouched.",
|
|
location: "A corporate research vault buried under a routine logistics floor.",
|
|
atmosphere: "Cold air, sound-dampened corridors, biometric locks, and too much silence to trust.",
|
|
opposition: "Internal black security and one compartmentalised watcher who does not know the whole job.",
|
|
oppositionAttitude: "Stay disciplined, silent, and willing to erase the evidence of their own failure.",
|
|
civilianFaction: "Night-shift technicians who do not understand the stakes but know every route in the building.",
|
|
extraObjective: "Remove a second innocuous-looking archive block that actually matters more than the main objective.",
|
|
escalationEvent: "A silent alarm brings in an internal response team with orders to close the floor permanently.",
|
|
complication: "The file set is mirrored to a live observer station and cannot simply be taken.",
|
|
localTwist: "The client is not paying for the theft; they are paying to confirm what Head Office already suspects.",
|
|
issuingDepartment: "Head Office Special Assignments",
|
|
contactRole: "Darkfinder Case Officer",
|
|
contactArchetype: "corporate",
|
|
contactLocation: "A blackout-safe service apartment three blocks from the target tower.",
|
|
threatArchetype: "blackops",
|
|
threatRole: "Vault Warden",
|
|
threatCountLabel: "One silent warden and a two-person ghost response team."
|
|
},
|
|
{
|
|
id: "black-disavowed-burn",
|
|
shortTitle: "Ash Ledger",
|
|
missionFocus: "Sabotage or destroy a target off the books.",
|
|
colourMissionIdea: "Burn a deniable site and seed the right false evidence before anyone asks why it existed.",
|
|
location: "A shell company warehouse wrapped around an unlisted lab basement.",
|
|
atmosphere: "Generator hum, shuttered loading bays, and an air that already feels abandoned on purpose.",
|
|
opposition: "Contract cleaners and one site supervisor prepared for a final-burn contingency.",
|
|
oppositionAttitude: "Know the site is disposable and only care about buying enough time to erase the real trail.",
|
|
civilianFaction: "Nearby dock crews who heard there was money in tonight's unusual overtime orders.",
|
|
extraObjective: "Leave one trail leading toward a rival corporate culprit.",
|
|
escalationEvent: "A chemical fire spreads faster than expected and starts opening parts of the hidden basement.",
|
|
complication: "One asset inside the site is still alive and worth far more than the building.",
|
|
localTwist: "The false evidence you were told to plant matches a previous cover story almost word for word.",
|
|
issuingDepartment: "Head Office Special Assignments",
|
|
contactRole: "Freelance Financier",
|
|
contactArchetype: "fixer",
|
|
contactLocation: "A remote comms booth patched through a dead sponsor office.",
|
|
threatArchetype: "blackops",
|
|
threatRole: "Cleanup Supervisor",
|
|
threatCountLabel: "One hard cleanup lead, one torch specialist, and a final-burn team."
|
|
}
|
|
],
|
|
platinum: [
|
|
{
|
|
id: "platinum-head-office",
|
|
shortTitle: "Quiet Crown",
|
|
missionFocus: "Protect or escort a person through a hostile district.",
|
|
colourMissionIdea: "Extract a Head Office asset who knows too much without revealing the extraction is even happening.",
|
|
location: "A sealed executive sector stitched into an older transit artery beneath it.",
|
|
atmosphere: "Too clean above, too secret below, and everyone on-site acting as if the wrong word could kill them.",
|
|
opposition: "A compartmentalised internal response team and one asset handler trying to rewrite the operation in real time.",
|
|
oppositionAttitude: "Never shout, rarely miss, and assume everyone around them is expendable.",
|
|
civilianFaction: "None officially, though a handful of overlooked service workers are trapped inside the machinery.",
|
|
extraObjective: "Return every mission record to Head Office only.",
|
|
escalationEvent: "Command changes the extraction route mid-run, implying compromise at the original pickup.",
|
|
complication: "The protected asset is deciding whether the squad is the rescue team or the last cleanup team they will ever see.",
|
|
localTwist: "The extraction itself is cover for a private conversation one Head Office director wants another never to hear.",
|
|
issuingDepartment: "Head Office Special Assignments",
|
|
contactRole: "Head Office Courier",
|
|
contactArchetype: "corporate",
|
|
contactLocation: "A private transit berth accessible only by dead-route credentials.",
|
|
threatArchetype: "blackops",
|
|
threatRole: "Executive Recovery Lead",
|
|
threatCountLabel: "One elite retrieval leader and a pair of immaculate shadow operators."
|
|
}
|
|
]
|
|
};
|
|
|
|
export function framesForColour(key = "white") {
|
|
return FRAMES[String(key ?? "white").toLowerCase()] ?? FRAMES.white;
|
|
}
|