v0.2.0 — SCL-level chooser + credits / SCL-increase sliders
Re-vendors the updated engine and adds to the generator window: an Operative SCL Level select, a credits slider (per chosen payment basis) and an SCL- increase slider, both with live preview and no hardcoded numbers. The chosen SCL level flows through to the briefing and journal output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
"id": "sla-blueprint-news",
|
||||
"title": "SLA Blueprint News",
|
||||
"description": "Generate SLA Industries BPN mission briefings as journal entries — a player-facing Brief and a separate GM-only Dossier, in the SLA house style with procedural artwork. Engine-agnostic core; this module is the Foundry wrapper.",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"compatibility": {
|
||||
"minimum": "13",
|
||||
"verified": "14.361",
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* SLA Blueprint News — generator window (ApplicationV2 + Handlebars).
|
||||
*
|
||||
* Mirrors the standalone app over the vendored engine: pick band / frame /
|
||||
* squad / payment, watch a live preview, then Create Journals (a player-facing
|
||||
* Mission Brief page + a GM-only Dossier page) or Post to Chat.
|
||||
* squad / payment, set the operative SCL level, dial the credits and SCL-
|
||||
* increase rewards on sliders (live preview, no hardcoded numbers), then
|
||||
* Create Journals (player Brief page + GM-only Dossier page) or Post to Chat.
|
||||
*
|
||||
* The render layer is engine-free; this file is the only Foundry-aware code.
|
||||
*/
|
||||
@@ -17,6 +18,14 @@ import { crestDataURI } from "../vendor/art/bpn-art.js";
|
||||
const MODULE_ID = "sla-blueprint-news";
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
const previewHTMLFor = (bpn, view) =>
|
||||
view === "gm" ? dossierHTML(bpn, { artMode: "img" })
|
||||
: view === "both" ? briefHTML(bpn, { artMode: "img" }) + dossierHTML(bpn, { artMode: "img" })
|
||||
: briefHTML(bpn, { artMode: "img" });
|
||||
|
||||
const cbsReadoutFor = (r) =>
|
||||
`${r.cbsBasisValue}c ${r.cbsBasis === "op" ? "per Op" : "per Squad"}${r.cbsOpenEnded ? "+" : ""} · gross ${r.grossContractValue}c · ${r.takeHomeEach}`;
|
||||
|
||||
export class BlueprintNewsApp extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
state = {
|
||||
seed: randomSeed(),
|
||||
@@ -24,6 +33,9 @@ export class BlueprintNewsApp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||
frameId: "",
|
||||
squadSize: 4,
|
||||
paymentType: "",
|
||||
sclLevel: null, // null → core defaults per band
|
||||
cbsValue: null, // null → use the seed's rolled default
|
||||
sclIncrease: null,
|
||||
view: "player"
|
||||
};
|
||||
|
||||
@@ -32,7 +44,7 @@ export class BlueprintNewsApp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||
tag: "div",
|
||||
classes: ["sla-bpn-window"],
|
||||
window: { title: "SLABPN.Title", resizable: true, icon: "fas fa-file-contract" },
|
||||
position: { width: 1080, height: 820 },
|
||||
position: { width: 1080, height: 860 },
|
||||
actions: {
|
||||
reroll: BlueprintNewsApp.#onReroll,
|
||||
applySeed: BlueprintNewsApp.#onApplySeed,
|
||||
@@ -54,54 +66,53 @@ export class BlueprintNewsApp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||
colourKey: this.state.colourKey || undefined,
|
||||
frameId: this.state.frameId || undefined,
|
||||
squadSize: this.state.squadSize,
|
||||
sclLevel: this.state.sclLevel ?? undefined,
|
||||
cbsValue: this.state.cbsValue ?? undefined,
|
||||
sclIncrease: this.state.sclIncrease ?? undefined,
|
||||
overrides: { paymentType: this.state.paymentType || "__RANDOM__" }
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const bpn = this._bpn();
|
||||
// keep state in step with what was actually rolled (band/frame/seed)
|
||||
this.state.seed = bpn.seed;
|
||||
this.state.sclLevel = bpn.sclLevel; // reflect the resolved level in the select
|
||||
|
||||
const colourOptions = [
|
||||
{ key: "", label: "Any (roll d20)", selected: this.state.colourKey === "" }
|
||||
].concat(COLOURS.map((c) => ({
|
||||
key: c.key,
|
||||
label: `${c.label} — ${c.type}`,
|
||||
selected: c.key === this.state.colourKey
|
||||
})));
|
||||
const colourOptions = [{ key: "", label: "Any (roll d20)", selected: this.state.colourKey === "" }]
|
||||
.concat(COLOURS.map((c) => ({ key: c.key, label: `${c.label} — ${c.type}`, selected: c.key === this.state.colourKey })));
|
||||
|
||||
const frames = this.state.colourKey ? framesForColour(this.state.colourKey) : [];
|
||||
const frameOptions = [{ id: "", label: "Any in band", selected: this.state.frameId === "" }]
|
||||
.concat(frames.map((f) => ({ id: f.id, label: f.shortTitle, selected: f.id === this.state.frameId })));
|
||||
|
||||
const previewHTML = this.state.view === "gm"
|
||||
? dossierHTML(bpn, { artMode: "img" })
|
||||
: this.state.view === "both"
|
||||
? briefHTML(bpn, { artMode: "img" }) + dossierHTML(bpn, { artMode: "img" })
|
||||
: briefHTML(bpn, { artMode: "img" });
|
||||
|
||||
const paymentOptions = [
|
||||
{ value: "", label: "Any" },
|
||||
{ value: "Per Squad", label: "Per Squad" },
|
||||
{ value: "Per Operative", label: "Per Operative" }
|
||||
].map((o) => ({ ...o, selected: o.value === this.state.paymentType }));
|
||||
|
||||
const sclOptions = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1].map((n) => ({
|
||||
value: n,
|
||||
label: `SCL ${n}${n === 10 ? " (lowest)" : n === 1 ? " (highest)" : ""}`,
|
||||
selected: n === bpn.sclLevel
|
||||
}));
|
||||
|
||||
const viewButtons = [
|
||||
{ view: "player", label: "Player Brief" },
|
||||
{ view: "gm", label: "GM Dossier" },
|
||||
{ view: "both", label: "Both" }
|
||||
].map((v) => ({ ...v, active: v.view === this.state.view }));
|
||||
|
||||
return {
|
||||
state: this.state,
|
||||
colourOptions,
|
||||
frameOptions,
|
||||
frameDisabled: !this.state.colourKey,
|
||||
paymentOptions,
|
||||
viewButtons,
|
||||
previewHTML
|
||||
const r = bpn.reward;
|
||||
const reward = {
|
||||
basisLabel: r.cbsBasis === "op" ? "per operative" : "per squad",
|
||||
cbsMin: r.cbsMin, cbsMax: r.cbsMax, cbsValue: r.cbsBasisValue,
|
||||
sclMin: r.sclMin, sclMax: r.sclMax, sclValue: r.sclIncreaseValue,
|
||||
cbsReadout: cbsReadoutFor(r),
|
||||
scliReadout: `${r.sclIncrease} SCL`
|
||||
};
|
||||
|
||||
return { state: this.state, colourOptions, frameOptions, paymentOptions, sclOptions, viewButtons, reward, previewHTML: previewHTMLFor(bpn, this.state.view) };
|
||||
}
|
||||
|
||||
_onRender() {
|
||||
@@ -109,18 +120,40 @@ export class BlueprintNewsApp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||
root.querySelectorAll("[data-field]").forEach((el) => {
|
||||
el.addEventListener("change", () => {
|
||||
const f = el.dataset.field;
|
||||
this.state[f] = f === "squadSize" ? Number(el.value) || 4 : el.value;
|
||||
if (f === "colourKey") this.state.frameId = "";
|
||||
if (f === "squadSize") this.state.squadSize = Number(el.value) || 4;
|
||||
else if (f === "sclLevel") this.state.sclLevel = Number(el.value) || 10;
|
||||
else this.state[f] = el.value;
|
||||
// changing the band or payment basis resets the dialled rewards
|
||||
if (f === "colourKey") { this.state.frameId = ""; this.state.cbsValue = null; this.state.sclIncrease = null; }
|
||||
if (f === "paymentType") { this.state.cbsValue = null; this.state.sclIncrease = null; }
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
// sliders update the preview live, without a full re-render (keeps the grip)
|
||||
root.querySelectorAll("[data-slider]").forEach((el) => {
|
||||
el.addEventListener("input", () => {
|
||||
this.state[el.dataset.slider] = Number(el.value);
|
||||
this.#refreshPreview();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static #onReroll() { this.state.seed = randomSeed(); this.render(); }
|
||||
#refreshPreview() {
|
||||
const bpn = this._bpn();
|
||||
const host = this.element.querySelector(".sla-bpn-gen__preview");
|
||||
if (host) host.innerHTML = previewHTMLFor(bpn, this.state.view);
|
||||
const cbsR = this.element.querySelector('[data-readout="cbs"]');
|
||||
const scliR = this.element.querySelector('[data-readout="scli"]');
|
||||
if (cbsR) cbsR.textContent = cbsReadoutFor(bpn.reward);
|
||||
if (scliR) scliR.textContent = `${bpn.reward.sclIncrease} SCL`;
|
||||
}
|
||||
|
||||
static #onReroll() { this.state.seed = randomSeed(); this.state.cbsValue = null; this.state.sclIncrease = null; this.render(); }
|
||||
|
||||
static #onApplySeed() {
|
||||
const v = this.element.querySelector('[data-field="seed"]')?.value?.trim();
|
||||
if (v) this.state.seed = v;
|
||||
this.state.cbsValue = null; this.state.sclIncrease = null;
|
||||
this.render();
|
||||
}
|
||||
|
||||
@@ -128,10 +161,7 @@ export class BlueprintNewsApp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||
try { await navigator.clipboard.writeText(this.state.seed); ui.notifications.info("Seed copied."); } catch (_) {}
|
||||
}
|
||||
|
||||
static #onView(event, target) {
|
||||
this.state.view = target.dataset.view || "player";
|
||||
this.render();
|
||||
}
|
||||
static #onView(event, target) { this.state.view = target.dataset.view || "player"; this.render(); }
|
||||
|
||||
static async #onCreate() {
|
||||
const bpn = this._bpn();
|
||||
@@ -142,7 +172,7 @@ export class BlueprintNewsApp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||
const entry = await JournalEntry.create({
|
||||
name: `${bpn.colour.label} BPN — ${bpn.shortTitle}`,
|
||||
ownership: { default: O.OBSERVER },
|
||||
flags: { [MODULE_ID]: { seed: bpn.seed, colourKey: bpn.colourKey, frameId: bpn.gm.frameId } },
|
||||
flags: { [MODULE_ID]: { seed: bpn.seed, colourKey: bpn.colourKey, frameId: bpn.gm.frameId, sclLevel: bpn.sclLevel } },
|
||||
pages: [
|
||||
{
|
||||
name: "Mission Brief",
|
||||
@@ -173,7 +203,7 @@ export class BlueprintNewsApp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||
<div>
|
||||
<div class="bpn-chat-card__band">${bpn.colour.label} BPN · ${bpn.colour.type}</div>
|
||||
<div class="bpn-chat-card__title">${bpn.shortTitle}</div>
|
||||
<div class="bpn-chat-card__meta">${bpn.bpnId} · SCL ${bpn.sclRequirement} · ${bpn.reward.grossContract}</div>
|
||||
<div class="bpn-chat-card__meta">${bpn.bpnId} · ${bpn.sclRequirement} · ${bpn.reward.grossContract}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="bpn-chat-card__brief">${foundry.utils.escapeHTML(bpn.missionBrief)}</p>
|
||||
|
||||
Vendored
+86
-17
@@ -108,6 +108,19 @@ function mediaFromRoll(roll = 1) {
|
||||
return "GoreZone / Contract Circuit Special";
|
||||
}
|
||||
|
||||
function clampNum(n, lo, hi) {
|
||||
return Math.max(lo, Math.min(hi, n));
|
||||
}
|
||||
|
||||
/* For an open-ended band (Platinum has no max) we still need a finite slider
|
||||
ceiling — three times the floor reads as "and up". */
|
||||
function basisBounds(range) {
|
||||
const min = Number(range?.min ?? 0);
|
||||
const openEnded = range?.max == null;
|
||||
const max = openEnded ? Math.round(min * 3) : Number(range.max);
|
||||
return { min, max, openEnded };
|
||||
}
|
||||
|
||||
function rewardPackage(rng, colour, input) {
|
||||
const paymentTypeRoll = rollDie(rng, 6);
|
||||
const cbsTierRoll = rollDie(rng, 6);
|
||||
@@ -119,12 +132,18 @@ function rewardPackage(rng, colour, input) {
|
||||
const paymentType = input.paymentType && input.paymentType !== "__RANDOM__"
|
||||
? String(input.paymentType)
|
||||
: (paymentTypeRoll <= 4 ? "Per Squad" : "Per Operative");
|
||||
const cbsTier = tierFromRoll(cbsTierRoll);
|
||||
const sclTier = tierFromRoll(sclTierRoll);
|
||||
|
||||
const grossPerOp = creditsFromRange(colour.cbsPerOpRange, cbsTier);
|
||||
const grossPerSquad = creditsFromRange(colour.cbsPerSquadRange, cbsTier);
|
||||
const grossContractValue = paymentType === "Per Operative" ? grossPerOp * squadSize : grossPerSquad;
|
||||
// Credits are expressed per the chosen payment basis (per Operative or per
|
||||
// Squad). The slider runs across that band's range; an explicit cbsValue
|
||||
// (from the slider) overrides the rolled default.
|
||||
const basis = paymentType === "Per Operative" ? "op" : "squad";
|
||||
const range = basis === "op" ? colour.cbsPerOpRange : colour.cbsPerSquadRange;
|
||||
const { min: cbsMin, max: cbsMax, openEnded: cbsOpenEnded } = basisBounds(range);
|
||||
const rolledBasis = creditsFromRange(range, tierFromRoll(cbsTierRoll));
|
||||
const cbsBasisValue = input.cbsValue != null && Number.isFinite(Number(input.cbsValue))
|
||||
? clampNum(Math.round(Number(input.cbsValue)), cbsMin, cbsOpenEnded ? Math.max(cbsMax, Math.round(Number(input.cbsValue))) : cbsMax)
|
||||
: clampNum(rolledBasis, cbsMin, cbsMax);
|
||||
const grossContractValue = basis === "op" ? cbsBasisValue * squadSize : cbsBasisValue;
|
||||
|
||||
const financierCutPercent = input.financierCutPercent
|
||||
? Number(input.financierCutPercent)
|
||||
@@ -132,22 +151,37 @@ function rewardPackage(rng, colour, input) {
|
||||
const netContract = Math.max(0, Math.round(grossContractValue * (1 - financierCutPercent / 100)));
|
||||
const takeHomeEach = Math.max(1, Math.floor(netContract / squadSize));
|
||||
|
||||
// SCL increase also runs across the band's range; an explicit sclIncrease
|
||||
// (from the slider) overrides the rolled default.
|
||||
const sclMin = Number(colour.sclRange?.min ?? 0.05);
|
||||
const sclMax = Number(colour.sclRange?.max ?? colour.sclRange?.typical ?? 0.2);
|
||||
const sclByTier = {
|
||||
low: Number(colour.sclRange?.min ?? 0.1),
|
||||
mid: Number(colour.sclRange?.typical ?? colour.sclRange?.min ?? 0.1),
|
||||
high: Number(colour.sclRange?.max ?? colour.sclRange?.typical ?? 0.2)
|
||||
low: sclMin,
|
||||
mid: Number(colour.sclRange?.typical ?? sclMin),
|
||||
high: sclMax
|
||||
};
|
||||
const sclIncreaseValue = input.sclIncrease != null && Number.isFinite(Number(input.sclIncrease))
|
||||
? clampNum(Number(input.sclIncrease), sclMin, sclMax)
|
||||
: clampNum(sclByTier[tierFromRoll(sclTierRoll)] ?? sclByTier.mid, sclMin, sclMax);
|
||||
|
||||
return {
|
||||
paymentType,
|
||||
squadSize,
|
||||
basis,
|
||||
cbsBasisValue,
|
||||
cbsMin,
|
||||
cbsMax,
|
||||
cbsOpenEnded,
|
||||
grossContractValue,
|
||||
netContract,
|
||||
takeHomeEach,
|
||||
financierCutPercent,
|
||||
cbsBandOp: `${colour.cbsPerOpRange?.min ?? 0}c - ${colour.cbsPerOpRange?.max ?? "?"}c`,
|
||||
cbsBandSquad: `${colour.cbsPerSquadRange?.min ?? 0}c - ${colour.cbsPerSquadRange?.max ?? "?"}c`,
|
||||
sclIncrease: formatScl(sclByTier[sclTier] ?? sclByTier.mid),
|
||||
sclIncreaseValue,
|
||||
sclMin,
|
||||
sclMax,
|
||||
sclIncrease: formatScl(sclIncreaseValue),
|
||||
mediaCoverage: input.mediaCoverage && input.mediaCoverage !== "__RANDOM__"
|
||||
? String(input.mediaCoverage)
|
||||
: mediaFromRoll(mediaRoll),
|
||||
@@ -214,16 +248,34 @@ function buildMissionBrief(b) {
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
/* ---- SCL level ---------------------------------------------------------- */
|
||||
|
||||
/** Operative Security Clearance Level: 10 (lowest) … 1 (highest). */
|
||||
function clampScl(n) {
|
||||
const v = Number(n);
|
||||
if (!Number.isFinite(v)) return 10;
|
||||
return Math.max(1, Math.min(10, Math.round(v)));
|
||||
}
|
||||
|
||||
/** A sensible default level per band — the top (easiest) end of its range. */
|
||||
function defaultSclFromBand(colour) {
|
||||
const m = String(colour?.sclReq ?? "10").match(/\d+/);
|
||||
return clampScl(m ? Number(m[0]) : 10);
|
||||
}
|
||||
|
||||
/* ---- public API --------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Resolve a complete BPN from inputs.
|
||||
* @param {object} opts
|
||||
* @param {string} [opts.seed] Reproducibility seed; one is generated if absent.
|
||||
* @param {string} [opts.colourKey] Force a colour band; otherwise rolled on the d20 table.
|
||||
* @param {string} [opts.frameId] Force a scenario frame within the band.
|
||||
* @param {object} [opts.overrides] Any field to pin to a specific value (e.g. {location: "..."}).
|
||||
* @param {number} [opts.squadSize] Default 4.
|
||||
* @param {string} [opts.seed] Reproducibility seed; one is generated if absent.
|
||||
* @param {string} [opts.colourKey] Force a colour band; otherwise rolled on the d20 table.
|
||||
* @param {string} [opts.frameId] Force a scenario frame within the band.
|
||||
* @param {number} [opts.squadSize] Default 4.
|
||||
* @param {number} [opts.sclLevel] Operative SCL level 1–10; defaults to the band's top.
|
||||
* @param {number} [opts.cbsValue] Explicit credits for the chosen payment basis (slider).
|
||||
* @param {number} [opts.sclIncrease] Explicit SCL increase reward (slider).
|
||||
* @param {object} [opts.overrides] Any field to pin to a specific value (e.g. {location: "..."}).
|
||||
*/
|
||||
export function generateBPN(opts = {}) {
|
||||
const seed = take(opts.seed, randomSeed());
|
||||
@@ -245,7 +297,14 @@ export function generateBPN(opts = {}) {
|
||||
const frames = framesForColour(colour.key);
|
||||
const frame = (opts.frameId && frames.find((f) => f.id === opts.frameId)) || pick(rng, frames);
|
||||
|
||||
const reward = rewardPackage(rng, colour, { squadSize: opts.squadSize ?? ov.squadSize, ...ov });
|
||||
const reward = rewardPackage(rng, colour, {
|
||||
squadSize: opts.squadSize ?? ov.squadSize,
|
||||
...ov,
|
||||
cbsValue: opts.cbsValue ?? ov.cbsValue,
|
||||
sclIncrease: opts.sclIncrease ?? ov.sclIncrease
|
||||
});
|
||||
|
||||
const sclLevel = clampScl(opts.sclLevel ?? ov.sclLevel ?? defaultSclFromBand(colour));
|
||||
|
||||
const contactName = take(
|
||||
ov.contactName,
|
||||
@@ -262,7 +321,9 @@ export function generateBPN(opts = {}) {
|
||||
colour,
|
||||
issuingDepartment: take(ov.issuingDepartment, frame.issuingDepartment || pick(rng, TABLES.issuingDepartments)),
|
||||
squadSize: reward.squadSize,
|
||||
sclRequirement: take(ov.sclRequirement, colour.sclReq),
|
||||
sclLevel,
|
||||
sclRequirement: take(ov.sclRequirement, `SCL ${sclLevel}`),
|
||||
sclBandRange: colour.sclReq,
|
||||
|
||||
contact: {
|
||||
name: contactName,
|
||||
@@ -288,9 +349,17 @@ export function generateBPN(opts = {}) {
|
||||
financierCut: `${reward.financierCutPercent}%`,
|
||||
financierCutPercent: reward.financierCutPercent,
|
||||
sclIncrease: reward.sclIncrease,
|
||||
sclIncreaseValue: reward.sclIncreaseValue,
|
||||
sclMin: reward.sclMin,
|
||||
sclMax: reward.sclMax,
|
||||
mediaCoverage: reward.mediaCoverage,
|
||||
cbsBandOp: reward.cbsBandOp,
|
||||
cbsBandSquad: reward.cbsBandSquad
|
||||
cbsBandSquad: reward.cbsBandSquad,
|
||||
cbsBasis: reward.basis,
|
||||
cbsBasisValue: reward.cbsBasisValue,
|
||||
cbsMin: reward.cbsMin,
|
||||
cbsMax: reward.cbsMax,
|
||||
cbsOpenEnded: reward.cbsOpenEnded
|
||||
},
|
||||
|
||||
// Seam for a future NPC/monster module: archetype keys only, no stat schema.
|
||||
|
||||
@@ -48,6 +48,34 @@
|
||||
}
|
||||
.sla-bpn-gen__row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
|
||||
.sla-bpn-gen__field input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, #b88a24, #d6b24e);
|
||||
cursor: pointer;
|
||||
}
|
||||
.sla-bpn-gen__field input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 18px; height: 18px; border-radius: 50%;
|
||||
background: #f4efe5; border: 2px solid #b88a24; cursor: pointer;
|
||||
}
|
||||
.sla-bpn-gen__field input[type="range"]::-moz-range-thumb {
|
||||
width: 18px; height: 18px; border-radius: 50%;
|
||||
background: #f4efe5; border: 2px solid #b88a24; cursor: pointer;
|
||||
}
|
||||
.sla-bpn-gen__readout {
|
||||
font-family: "Roboto Mono", monospace;
|
||||
font-size: 0.72rem;
|
||||
color: #d6b24e;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.sla-bpn-gen__btn {
|
||||
border-radius: 7px;
|
||||
border: 1px solid #2c333f;
|
||||
|
||||
@@ -27,6 +27,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sla-bpn-gen__field">
|
||||
<label>Operative SCL Level</label>
|
||||
<select data-field="sclLevel">
|
||||
{{#each sclOptions}}<option value="{{value}}" {{#if selected}}selected{{/if}}>{{label}}</option>{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="sla-bpn-gen__field">
|
||||
<label>Reward — {{reward.basisLabel}}</label>
|
||||
<input type="range" data-slider="cbsValue" min="{{reward.cbsMin}}" max="{{reward.cbsMax}}" step="5" value="{{reward.cbsValue}}">
|
||||
<div class="sla-bpn-gen__readout" data-readout="cbs">{{reward.cbsReadout}}</div>
|
||||
</div>
|
||||
|
||||
<div class="sla-bpn-gen__field">
|
||||
<label>SCL Increase Reward</label>
|
||||
<input type="range" data-slider="sclIncrease" min="{{reward.sclMin}}" max="{{reward.sclMax}}" step="0.05" value="{{reward.sclValue}}">
|
||||
<div class="sla-bpn-gen__readout" data-readout="scli">{{reward.scliReadout}}</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="sla-bpn-gen__btn sla-bpn-gen__btn--primary" data-action="reroll">
|
||||
<i class="fas fa-dice"></i> Re-roll (new seed)
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user