diff --git a/database/schema.sql b/database/schema.sql index 38df82a..343d186 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -220,7 +220,7 @@ INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-moder CREATE TABLE IF NOT EXISTS oauth_states (state_hash TEXT PRIMARY KEY, provider TEXT NOT NULL, mode TEXT NOT NULL, user_id TEXT REFERENCES users(id) ON DELETE CASCADE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS data_change_proposals ( id TEXT PRIMARY KEY, - module TEXT NOT NULL CHECK(module IN ('attributes','talents','brands','gear','weapons','protocols','skillchips')), + module TEXT NOT NULL CHECK(module IN ('configuration','attributes','talents','brands','gear','weapons','protocols','skillchips')), action TEXT NOT NULL, entity_id TEXT, draft_entity_id TEXT, @@ -455,6 +455,16 @@ CREATE TABLE IF NOT EXISTS weapons ( ) ); CREATE UNIQUE INDEX IF NOT EXISTS weapons_name_nocase_idx ON weapons(name COLLATE NOCASE); +CREATE TABLE IF NOT EXISTS core_attribute_tier_ranges ( + tier INTEGER PRIMARY KEY CHECK(tier BETWEEN 0 AND 2), + nominal_min REAL NOT NULL CHECK(nominal_min >= 0), + nominal_max REAL NOT NULL CHECK(nominal_max >= nominal_min), + reference_level INTEGER NOT NULL DEFAULT 50 CHECK(reference_level BETWEEN 1 AND 50), + updated_at TEXT NOT NULL +); +INSERT OR IGNORE INTO core_attribute_tier_ranges(tier,nominal_min,nominal_max,reference_level,updated_at) VALUES(0,30,78,50,CURRENT_TIMESTAMP); +INSERT OR IGNORE INTO core_attribute_tier_ranges(tier,nominal_min,nominal_max,reference_level,updated_at) VALUES(1,36,94,50,CURRENT_TIMESTAMP); +INSERT OR IGNORE INTO core_attribute_tier_ranges(tier,nominal_min,nominal_max,reference_level,updated_at) VALUES(2,44,112,50,CURRENT_TIMESTAMP); CREATE TABLE IF NOT EXISTS weapon_tier_ranges ( weapon_id TEXT NOT NULL REFERENCES weapons(id) ON DELETE CASCADE, tier INTEGER NOT NULL CHECK(tier BETWEEN 0 AND 2), diff --git a/docs/DESIGN_SYSTEM.md b/docs/DESIGN_SYSTEM.md index 438ea2a..5671aef 100644 --- a/docs/DESIGN_SYSTEM.md +++ b/docs/DESIGN_SYSTEM.md @@ -757,6 +757,51 @@ each keystroke does not create a navigation entry. Retain an explicit Search button for immediate submission and keyboard/accessibility fallback. During navigation, expose a textual pending state and suppress duplicate submissions. +Builder option grids support an icon-only square mode for small, icon-led +catalogs such as a gear item's two-to-four compatible Brand Sets. In this mode, +the compact 58-pixel cells reset the ordinary text-and-icon grid to a single +centered track, and the transparent icon fills that square. Align the fieldset +with adjacent compact controls rather than inheriting the standard radio-grid +top margin. Visible truncated names are omitted. Preserve the complete name as +the radio control's accessible label and +as hover text. Selection, hover, pressed, and keyboard-focus states remain on +the square perimeter; missing icons fall back to the entity initials. + +Builder level controls enforce item availability without changing the scaling +curve's origin. T0 and T1 gear and weapons allow levels 1–50; T2 gear and +weapons allow levels 40–50 because lower-level T2 items are not found in game. +Their base-value progression still follows the same level-1-based curve. +Selecting T2 on a weapon below level 40 raises the configured level to 40 and +calculates the ordinary level-40 curve value; never rebase level 40 as T2 level +1. Slider bounds, direct-input validation, displayed bounds, and clamping must +all enforce the same availability minimum. + +ISAC weapon-tier damage and core ranges are observations recorded at their +explicit reference level. The progression curve still has a level-1 origin, +but builder scaling must calculate the ratio between the target level and the +stored observation's `reference_level`. Do not treat every stored endpoint as a +level-1 value merely because the curve begins there. + +### Shared core allocation + +Firepower, Toughness, and Engineering use one system-wide tier profile shared +by gear and weapons. Maintain the T0/T1/T2 nominal ranges in ISAC Global +Configuration rather than repeating them on every item editor. The initial +level-50 profiles are T0 `30–78`, T1 `36–94`, and T2 `44–112`. Global +configuration mutations follow ordinary contributor moderation and append-only +ISAC audit rules. + +The three nominal ranges define a combined allocation pool, not three hard +per-stat clamps. At a given level, the valid total lies between three times the +scaled nominal minimum and three times the scaled nominal maximum. An +individual core may be zero or exceed the nominal maximum when points are +redistributed, up to the entire pool maximum. Builder gear and weapon editors +show three sliders/direct inputs plus an explicit current-total versus +valid-pool readout; out-of-pool totals use textual red/restricted feedback. +Both equipment families contribute their configured cores to the build summary. +Treat this redistribution model as empirical and keep it configurable as more +in-game edge cases are observed. + Pending records may be used as dependencies in later proposals by any contributor. Render these choices in the ordinary assignment control with a restrained orange border/background and the explicit text `PENDING` plus the diff --git a/docs/STAT_LEVEL_SCALING.md b/docs/STAT_LEVEL_SCALING.md index cacb251..2040c47 100644 --- a/docs/STAT_LEVEL_SCALING.md +++ b/docs/STAT_LEVEL_SCALING.md @@ -150,6 +150,19 @@ targetRaw = / weaponMultiplier(referenceLevel) ``` +SHD.build stores the reference level associated with each weapon-tier range. +Builder calculations use that recorded observation level in the ratio above. +The curve's level-1 origin does not imply that every stored range was observed +at level 1. T2 availability limits likewise do not change a range's recorded +reference level. + +Firepower, Toughness, and Engineering nominal tier ranges are stored once in +the shared core-allocation configuration at reference level 50. Scale those +nominal endpoints to the target level using the appropriate equipment-family +multiplier, then derive the combined pool bounds by multiplying each endpoint +by three. Redistribution changes the split between the three cores without +changing the selected item's total allocation. + The `1.14` offset is provisional and is currently validated only for the level-40-to-level-50 interval. Preserve enough decimal precision to revise it without migrating rounded data. @@ -217,4 +230,3 @@ Useful known outputs include: `2187 DMG`, `139 FP`, `142 TGH`, `36 ENG`. - Weapon sample: level 40 `753 DMG`, `44 FP`, `15 TGH`, `45 ENG`; level 50 `873 DMG`, `51 FP`, `18 TGH`, `52 ENG`. - diff --git a/scripts/db-migrate.mjs b/scripts/db-migrate.mjs index fea3a19..ac92573 100644 --- a/scripts/db-migrate.mjs +++ b/scripts/db-migrate.mjs @@ -96,12 +96,12 @@ await database.exec('CREATE UNIQUE INDEX IF NOT EXISTS data_change_proposals_dra const proposalDefinition = await (await database.prepare( "SELECT sql FROM sqlite_master WHERE type='table' AND name='data_change_proposals'" )).get(); -if (typeof proposalDefinition?.sql === 'string' && !proposalDefinition.sql.includes("'attributes'")) { - console.log('Renaming the moderated Stats module to Attributes.'); +if (typeof proposalDefinition?.sql === 'string' && !proposalDefinition.sql.includes("'configuration'")) { + console.log('Expanding moderated data modules for Global Configuration.'); await database.exec('PRAGMA foreign_keys=OFF'); await database.exec(`CREATE TABLE data_change_proposals_expanded ( id TEXT PRIMARY KEY, - module TEXT NOT NULL CHECK(module IN ('attributes','talents','brands','gear','weapons','protocols','skillchips')), + module TEXT NOT NULL CHECK(module IN ('configuration','attributes','talents','brands','gear','weapons','protocols','skillchips')), action TEXT NOT NULL, entity_id TEXT, draft_entity_id TEXT, diff --git a/src/app.css b/src/app.css index 27616a9..83d40e9 100644 --- a/src/app.css +++ b/src/app.css @@ -164,7 +164,8 @@ .gear-tier-costs{margin:8px 0 0;padding:7px;border:1px solid #303536}.gear-tier-costs legend{padding:0 5px;color:#697174;font-size:6px;font-weight:900;letter-spacing:.7px}.gear-tier-costs>div{display:grid;grid-template-columns:repeat(3,1fr);gap:5px}.gear-tier-costs .field{margin:0}.gear-augment-profile-grid>button>div em{white-space:nowrap}@media(max-width:560px){.gear-tier-costs>div{grid-template-columns:1fr 1fr 1fr}} .gear-identity{grid-template-columns:minmax(360px,1fr) 200px minmax(150px,180px);align-items:end}.gear-identity>.field,.gear-identity>.segmented-choice{margin:0}.gear-identity .segmented-choice legend{font-size:7px}.gear-identity .segmented-choice>div{margin-top:5px}.gear-identity .segmented-choice:first-of-type>div{grid-template-columns:repeat(6,32px);width:max-content}.gear-identity .segmented-choice:first-of-type label,.gear-identity .segmented-choice:first-of-type span{width:32px}@media(max-width:900px){.gear-identity{grid-template-columns:1fr 1fr}.gear-identity>.field{grid-column:1/3}}@media(max-width:700px){.gear-identity{grid-template-columns:1fr}.gear-identity>.field{grid-column:auto}.gear-identity .segmented-choice>div{height:auto}.gear-identity .segmented-choice span{min-height:44px}.gear-identity .segmented-choice:first-of-type>div{grid-template-columns:repeat(3,44px)}.gear-identity .segmented-choice:first-of-type label,.gear-identity .segmented-choice:first-of-type span{width:44px}} .builder-field-range{float:right;color:#5f6769;font-size:6px}.builder-scaling-note{margin:7px 0 0;padding:7px 9px;border-left:2px solid #667174;background:#101415;color:#727a7c;font-size:7px;line-height:1.45} -.builder-scaling-note.provisional{border-left-color:var(--quality-high-end);background:#18150d;color:#a89568}.range-value-control{min-width:0;display:grid;gap:6px;padding:8px;border:1px solid var(--shd-border);background:#0d1011}.range-value-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.range-value-head b{color:#7f8789;font-size:7px;letter-spacing:.7px}.range-value-head small{color:#667072;font-size:6px;letter-spacing:.4px}.range-value-inputs{display:grid;grid-template-columns:minmax(90px,1fr) 76px;align-items:center;gap:8px}.range-value-slider{width:100%;height:22px!important;margin:0!important;padding:0!important;accent-color:var(--shd-orange);cursor:pointer}.range-value-number{width:100%;height:34px!important;margin:0!important;padding:0 7px!important}.range-value-control:focus-within{border-color:#8a672b;background:#121516}.range-value-slider:active{filter:brightness(.86)}.builder-equipment-controls{display:grid;grid-template-columns:minmax(190px,.7fr) minmax(0,1.3fr);align-items:start;gap:7px;margin-top:8px}.builder-equipment-controls>.builder-radio-grid:last-child:nth-child(3){grid-column:1/3}.builder-weapon-controls{display:grid;grid-template-columns:minmax(190px,.85fr) minmax(120px,.55fr) minmax(230px,1fr);align-items:end;gap:7px;margin-top:8px}.builder-weapon-controls>.segmented-choice>div{height:50px}.builder-radio-grid{min-width:0;margin:8px 0 0;padding:0;border:0}.builder-radio-grid legend{margin-bottom:5px;color:var(--shd-text-muted);font-size:7px;font-weight:900;letter-spacing:.8px}.builder-radio-search{height:34px!important;margin:0 0 5px!important;padding:0 9px!important;font-size:7px!important}.builder-radio-grid>div{display:grid;grid-template-columns:repeat(var(--builder-radio-columns),minmax(0,1fr));gap:4px}.builder-radio-grid>div>label{min-width:0;cursor:pointer}.builder-radio-grid input[type=radio]{position:absolute;width:1px;height:1px;margin:0;opacity:0;pointer-events:none}.builder-radio-grid label>span{min-height:58px;display:grid;grid-template-columns:34px minmax(0,1fr);grid-template-rows:1fr 1fr;align-items:center;gap:1px 7px;padding:6px;border:1px solid #303536;background:#0d1011;color:#747c7e;transition:var(--shd-transition)}.builder-radio-grid img,.builder-radio-grid i{grid-row:1/3;width:32px;height:32px;object-fit:contain}.builder-radio-grid i{display:grid;place-items:center;border:1px solid #343a3b;color:#9a702e;font-size:7px;font-style:normal}.builder-radio-grid b{align-self:end;overflow:hidden;color:#b9bfc0;font-size:7px;line-height:1.25;text-overflow:ellipsis;white-space:nowrap}.builder-radio-grid small{align-self:start;overflow:hidden;color:#626a6c;font-size:5px;text-overflow:ellipsis;white-space:nowrap}.builder-radio-grid label:hover>span{border-color:#765b2e;background:#16191a}.builder-radio-grid label:active>span{transform:translateY(1px);filter:brightness(.88)}.builder-radio-grid input:checked+span{border-color:#9a6c27;background:#1d180e;color:var(--shd-orange);box-shadow:inset 0 -2px var(--shd-orange)}.builder-radio-grid input:focus-visible+span{outline:2px solid var(--shd-orange);outline-offset:2px}.builder-radio-grid>div>p{grid-column:1/-1;margin:0;padding:12px;border:1px solid var(--shd-border);color:#626a6c;font-size:7px;text-align:center}.builder-talent-grids{display:grid;gap:3px;margin-top:6px}.builder-rolls.required>div{grid-template-columns:minmax(0,1fr) minmax(180px,.65fr)}@media(max-width:650px){.builder-equipment-controls,.builder-weapon-controls{grid-template-columns:1fr}.builder-equipment-controls>.builder-radio-grid:last-child:nth-child(3),.builder-weapon-controls .builder-fixed{grid-column:auto}.builder-radio-grid>div{grid-template-columns:repeat(2,minmax(0,1fr))}.builder-radio-grid label>span{min-height:62px}.builder-weapon-controls>.segmented-choice>div{height:44px}.range-value-inputs{grid-template-columns:minmax(0,1fr) 84px}}@media(max-width:430px){.builder-radio-grid>div{grid-template-columns:1fr}.range-value-inputs{grid-template-columns:1fr}.range-value-number{height:40px!important}} +.builder-scaling-note.provisional{border-left-color:var(--quality-high-end);background:#18150d;color:#a89568}.range-value-control{min-width:0;display:grid;gap:6px;padding:8px;border:1px solid var(--shd-border);background:#0d1011}.range-value-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.range-value-head b{color:#7f8789;font-size:7px;letter-spacing:.7px}.range-value-head small{color:#667072;font-size:6px;letter-spacing:.4px}.range-value-inputs{display:grid;grid-template-columns:minmax(90px,1fr) 76px;align-items:center;gap:8px}.range-value-slider{width:100%;height:22px!important;margin:0!important;padding:0!important;accent-color:var(--shd-orange);cursor:pointer}.range-value-number{width:100%;height:34px!important;margin:0!important;padding:0 7px!important}.range-value-control:focus-within{border-color:#8a672b;background:#121516}.range-value-slider:active{filter:brightness(.86)}.builder-equipment-controls{display:grid;grid-template-columns:minmax(190px,.7fr) minmax(0,1.3fr);align-items:start;gap:7px;margin-top:8px}.builder-equipment-controls>.builder-radio-grid:last-child:nth-child(3){grid-column:1/3}.builder-equipment-controls>.builder-radio-grid.icon-only{align-self:start;margin:0}.builder-weapon-controls{display:grid;grid-template-columns:minmax(190px,.85fr) minmax(120px,.55fr) minmax(230px,1fr);align-items:end;gap:7px;margin-top:8px}.builder-weapon-controls>.segmented-choice>div{height:50px}.builder-radio-grid{min-width:0;margin:8px 0 0;padding:0;border:0}.builder-radio-grid legend{margin-bottom:5px;color:var(--shd-text-muted);font-size:7px;font-weight:900;letter-spacing:.8px}.builder-radio-search{height:34px!important;margin:0 0 5px!important;padding:0 9px!important;font-size:7px!important}.builder-radio-grid>div{display:grid;grid-template-columns:repeat(var(--builder-radio-columns),minmax(0,1fr));gap:4px}.builder-radio-grid>div>label{min-width:0;cursor:pointer}.builder-radio-grid input[type=radio]{position:absolute;width:1px;height:1px;margin:0;opacity:0;pointer-events:none}.builder-radio-grid label>span{min-height:58px;display:grid;grid-template-columns:34px minmax(0,1fr);grid-template-rows:1fr 1fr;align-items:center;gap:1px 7px;padding:6px;border:1px solid #303536;background:#0d1011;color:#747c7e;transition:var(--shd-transition)}.builder-radio-grid img,.builder-radio-grid i{grid-row:1/3;width:32px;height:32px;object-fit:contain}.builder-radio-grid i{display:grid;place-items:center;border:1px solid #343a3b;color:#9a702e;font-size:7px;font-style:normal}.builder-radio-grid b{align-self:end;overflow:hidden;color:#b9bfc0;font-size:7px;line-height:1.25;text-overflow:ellipsis;white-space:nowrap}.builder-radio-grid small{align-self:start;overflow:hidden;color:#626a6c;font-size:5px;text-overflow:ellipsis;white-space:nowrap}.builder-radio-grid.icon-only>div{width:max-content;max-width:100%;grid-template-columns:repeat(var(--builder-radio-columns),58px)}.builder-radio-grid.icon-only label>span{min-height:0;aspect-ratio:1;display:grid;grid-template-columns:1fr;grid-template-rows:1fr;place-items:center;gap:0;padding:4px}.builder-radio-grid.icon-only img,.builder-radio-grid.icon-only i{grid-column:1;grid-row:1;width:100%;height:100%;max-width:none;max-height:none}.builder-radio-grid.icon-only i{font-size:10px}.builder-radio-grid.icon-only b,.builder-radio-grid.icon-only small{display:none}.builder-radio-grid label:hover>span{border-color:#765b2e;background:#16191a}.builder-radio-grid label:active>span{transform:translateY(1px);filter:brightness(.88)}.builder-radio-grid input:checked+span{border-color:#9a6c27;background:#1d180e;color:var(--shd-orange);box-shadow:inset 0 -2px var(--shd-orange)}.builder-radio-grid input:focus-visible+span{outline:2px solid var(--shd-orange);outline-offset:2px}.builder-radio-grid>div>p{grid-column:1/-1;margin:0;padding:12px;border:1px solid var(--shd-border);color:#626a6c;font-size:7px;text-align:center}.builder-talent-grids{display:grid;gap:3px;margin-top:6px}.builder-rolls.required>div{grid-template-columns:minmax(0,1fr) minmax(180px,.65fr)}@media(max-width:650px){.builder-equipment-controls,.builder-weapon-controls{grid-template-columns:1fr}.builder-equipment-controls>.builder-radio-grid:last-child:nth-child(3),.builder-weapon-controls .builder-fixed{grid-column:auto}.builder-radio-grid>div{grid-template-columns:repeat(2,minmax(0,1fr))}.builder-radio-grid.icon-only>div{grid-template-columns:repeat(var(--builder-radio-columns),58px)}.builder-radio-grid label>span{min-height:62px}.builder-radio-grid.icon-only label>span{min-height:0}.builder-weapon-controls>.segmented-choice>div{height:44px}.range-value-inputs{grid-template-columns:minmax(0,1fr) 84px}}@media(max-width:430px){.builder-radio-grid>div{grid-template-columns:1fr}.builder-radio-grid.icon-only>div{grid-template-columns:repeat(2,58px)}.range-value-inputs{grid-template-columns:1fr}.range-value-number{height:40px!important}} +.builder-core-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px}.builder-range-grid.weapon-damage{grid-template-columns:minmax(220px,1fr)}.builder-section-title>b.restricted{color:var(--shd-danger)}.global-configuration-editor{margin-top:10px}.global-configuration-editor .editor-title>b{color:var(--shd-orange);font-size:7px;letter-spacing:1px}.configuration-note{margin:10px 0;padding:9px;border-left:2px solid var(--shd-info);background:#101719;color:#8daeb8;font-size:8px;line-height:1.5}.core-configuration-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.core-configuration-grid fieldset{display:grid;grid-template-columns:64px 1fr 1fr;align-items:end;gap:7px;margin:0;padding:9px;border:1px solid var(--shd-border);background:#0d1011}.core-configuration-grid legend{padding:0 7px;color:var(--shd-orange);font-size:9px;font-weight:900}.core-configuration-grid .field{margin:0}.core-config-icons{height:36px;display:flex;align-items:center;justify-content:center}.core-config-icons .game-icon{width:24px;height:24px;margin-left:-5px}.core-config-icons .game-icon:first-child{margin-left:0}.core-configuration-grid small{grid-column:1/-1;color:var(--shd-text-dim);font-size:6px;letter-spacing:.8px;text-align:right}@media(max-width:850px){.core-configuration-grid{grid-template-columns:1fr}}@media(max-width:650px){.builder-core-grid{grid-template-columns:1fr}} /* Builder workbench */ .loadout-workspace{display:grid;grid-template-columns:minmax(230px,280px) minmax(420px,1fr) minmax(290px,350px);gap:10px;align-items:start}.loadout-slots,.loadout-editor,.loadout-summary{border:1px solid var(--shd-border);background:#111415}.loadout-slots>header,.summary-section>header{display:flex;justify-content:space-between;align-items:center;padding:9px 10px;border-bottom:1px solid var(--shd-border);background:#0d1011;color:#6d7577;font-size:7px;letter-spacing:1px}.loadout-slots>header b,.summary-section>header b{color:var(--shd-orange)}.loadout-slot-group{display:grid;gap:4px;padding:5px;border-bottom:1px solid #292e2f}.loadout-slot-group.weapons{grid-template-columns:1fr 1fr}.loadout-slot-group.gear{grid-template-columns:1fr 1fr}.loadout-slot-group.systems{grid-template-columns:1fr 1fr}.loadout-slot-group button{min-width:0;min-height:65px;display:grid;align-content:center;gap:3px;padding:8px;border:1px solid #2b3031;border-left:2px solid #3b4142;background:#101314;color:#c5c9ca;text-align:left;cursor:pointer}.loadout-slot-group button:hover,.loadout-slot-group button:focus-visible{border-color:#8b682b;background:#171b1c}.loadout-slot-group button:active{transform:translateY(1px);filter:brightness(.88)}.loadout-slot-group button.active{border-left-color:var(--shd-orange);background:#1b170f}.loadout-slot-group small{color:#646c6e;font-size:6px;letter-spacing:.8px}.loadout-slot-group strong{overflow:hidden;font-size:8px;text-overflow:ellipsis;white-space:nowrap}.loadout-slot-group span{overflow:hidden;color:#7c8486;font-size:6px;text-overflow:ellipsis;white-space:nowrap}.loadout-editor{min-height:680px;padding:12px;border-top:2px solid var(--shd-orange)}.loadout-editor-head{display:flex;justify-content:space-between;align-items:end;padding-bottom:9px;border-bottom:1px solid #303536}.loadout-editor-head span{color:#687072;font-size:6px;letter-spacing:1px}.loadout-editor-head h2{margin:3px 0 0;font-size:19px}.loadout-editor-head>b{color:var(--shd-orange);font-size:7px;letter-spacing:.8px}.builder-picker{position:relative;margin-top:9px}.builder-picker>span{display:block;margin-bottom:5px;color:#737b7d;font-size:7px;font-weight:800;letter-spacing:.8px}.builder-picker-current{width:100%;min-height:48px;display:grid;grid-template-columns:34px minmax(0,1fr) auto;grid-template-rows:1fr 1fr;align-items:center;column-gap:8px;padding:6px 9px;border:1px solid #343a3b;background:#0e1112;color:#d7d9d5;text-align:left}.builder-picker-current img,.builder-picker-current>i,.builder-picker-panel button img,.builder-picker-panel button>i{grid-row:1/3;width:30px;height:30px;object-fit:contain}.builder-picker-current>i,.builder-picker-panel button>i{display:grid;place-items:center;color:var(--shd-orange);font-style:normal}.builder-picker-current b{align-self:end;font-size:9px}.builder-picker-current small{align-self:start;color:#666e70;font-size:6px}.builder-picker-current em{grid-column:3;grid-row:1/3;color:var(--shd-orange);font-size:14px;font-style:normal}.builder-picker-panel{position:absolute;z-index:30;top:100%;left:0;right:0;padding:6px;border:1px solid #5a4a2d;background:#0b0e0f;box-shadow:0 12px 25px #000b}.builder-picker-panel>input{height:36px;margin:0 0 5px;padding:0 9px;font-size:8px}.builder-picker-panel>div{max-height:250px;overflow:auto}.builder-picker-panel>div>button{width:100%;min-height:42px;display:grid;grid-template-columns:30px minmax(0,1fr) auto;align-items:center;gap:7px;padding:5px 7px;border:0;border-top:1px solid #272c2d;background:#111415;color:#c4c8c9;text-align:left}.builder-picker-panel>div>button:hover,.builder-picker-panel>div>button.selected{background:#1b170f}.builder-picker-panel>div>button:disabled{opacity:.35}.builder-picker-panel button span{display:grid;gap:2px}.builder-picker-panel button b{font-size:8px}.builder-picker-panel button small,.builder-picker-panel button em{color:#687072;font-size:6px;font-style:normal}.builder-picker-panel button.selected em{color:var(--shd-orange)}.builder-picker-panel p{padding:12px;color:#626a6c;font-size:7px}.builder-field-grid,.builder-range-grid{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-top:8px}.builder-field-grid>label,.builder-range-grid>label,.builder-chip-rolls label,.builder-rolls label{color:#737b7d;font-size:7px;font-weight:800;letter-spacing:.7px}.builder-field-grid select,.builder-range-grid input,.builder-chip-rolls input,.builder-rolls input{height:38px;margin-top:5px;padding:0 8px;font-size:9px}.builder-range-grid label>span{float:right;color:#5f6769;font-size:6px}.builder-range-grid.weapon{grid-template-columns:repeat(2,1fr)}.builder-fixed{display:flex;align-items:end;gap:5px;padding-bottom:1px}.builder-fixed span{flex:1;padding:9px 5px;border:1px solid #303536;color:#8e9698;font-size:6px;text-align:center}.builder-section-title{display:flex;justify-content:space-between;align-items:end;margin:13px 0 5px}.builder-section-title>div{display:grid;gap:3px}.builder-section-title span{font-size:8px;font-weight:900;letter-spacing:.8px}.builder-section-title small{color:#626a6c;font-size:6px}.builder-section-title button{border:0;background:transparent;color:var(--shd-orange);font-size:7px}.builder-section-title>b{color:var(--shd-orange);font-size:7px}.builder-rolls{display:grid;gap:5px}.builder-rolls>div,.builder-chip-rolls>div{display:grid;grid-template-columns:minmax(0,1fr) 105px 28px;align-items:end;gap:6px;padding:6px;border:1px solid #2d3233;background:#0f1213}.builder-rolls .builder-picker,.builder-chip-rolls .builder-picker{margin:0}.builder-rolls>div>button{height:38px;border:1px solid #563734;background:#1d1212;color:#da746c}.builder-chip-rolls{display:grid;gap:6px;margin-top:8px}.builder-chip-rolls>div{grid-template-columns:minmax(0,1fr) 105px}.builder-augment-list{display:grid;grid-template-columns:1fr 1fr;gap:5px}.builder-augment-list label{min-height:45px;display:grid;grid-template-columns:18px 1fr auto;align-items:center;gap:7px;padding:7px;border:1px solid #303536;background:#0f1213;cursor:pointer}.builder-augment-list input{width:auto;margin:0;accent-color:var(--shd-orange)}.builder-augment-list span{display:grid;gap:3px}.builder-augment-list b{font-size:7px}.builder-augment-list small{color:#687072;font-size:6px}.builder-augment-list em{color:#a77b31;font-size:6px;font-style:normal}.builder-inherited{display:grid;grid-template-columns:repeat(3,1fr);margin-top:8px;border:1px solid #303536}.builder-inherited span{display:grid;gap:4px;padding:8px;border-left:1px solid #303536}.builder-inherited span:first-child{border-left:0}.builder-inherited small{color:#687072;font-size:6px}.builder-inherited b{color:var(--shd-orange);font-size:9px}.loadout-summary{position:sticky;top:88px;border-top:2px solid var(--shd-orange)}.summary-head{display:flex;justify-content:space-between;align-items:end;padding:12px}.summary-head h2{margin:4px 0 0;font-size:20px}.summary-head>b{color:var(--shd-orange);font-size:22px}.summary-core{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid #303536;border-bottom:1px solid #303536}.summary-core>div{display:flex;align-items:center;gap:5px;padding:9px 6px;border-left:1px solid #303536}.summary-core>div:first-child{border-left:0}.summary-core .game-icon{width:22px;height:22px}.summary-core span{display:grid;color:#687072;font-size:5px}.summary-core b{color:#d6d9da;font-size:9px}.summary-scroll{max-height:calc(100vh - 340px);overflow:auto}.summary-section>header{position:sticky;top:0;z-index:2}.summary-stat,.summary-bonus{display:flex;justify-content:space-between;gap:8px;padding:8px 10px;border-top:1px solid #252a2b}.summary-stat>span,.summary-bonus>span{display:grid;gap:3px;font-size:8px}.summary-stat small,.summary-bonus small{color:#626a6c;font-size:5px}.summary-stat>b{color:#d9dbdc;font-size:9px}.summary-bonus em{color:var(--shd-success);font-size:7px;font-style:normal;text-align:right}.summary-section>p{margin:0;padding:12px;color:#626a6c;font-size:7px;line-height:1.5}.summary-talent{display:grid;grid-template-columns:18px 1fr;gap:7px;padding:9px 10px;border-top:1px solid #252a2b;cursor:pointer}.summary-talent input{width:auto;margin:2px 0 0;accent-color:var(--shd-orange)}.summary-talent span{display:grid;gap:3px}.summary-talent b{font-size:8px}.summary-talent small{color:#8a682f;font-size:5px}.summary-talent em{color:#727a7c;font:7px/1.45 Georgia,serif}.loadout-summary .notice{margin:0;padding:11px;border-top:1px solid #303536;border-left:0}.builder-empty-data{display:flex;gap:12px;margin-bottom:10px;padding:12px;border:1px solid #65403a;border-left:3px solid var(--shd-danger);background:#1c1111}.builder-empty-data strong{font-size:8px}.builder-empty-data span{color:#a87870;font-size:8px}@media(max-width:1200px){.loadout-workspace{grid-template-columns:240px minmax(390px,1fr)}.loadout-summary{position:static;grid-column:1/3}.summary-scroll{max-height:none;display:grid;grid-template-columns:repeat(3,1fr)}.summary-section{border-left:1px solid #303536}.summary-section:first-child{border-left:0}}@media(max-width:800px){.loadout-workspace{grid-template-columns:1fr}.loadout-slots,.loadout-editor,.loadout-summary{grid-column:auto}.loadout-editor{min-height:0}.summary-scroll{display:block}.summary-section{border-left:0}.loadout-summary{order:3}.loadout-slot-group.gear{grid-template-columns:repeat(3,1fr)}}@media(max-width:560px){.loadout-slot-group.gear,.loadout-slot-group.systems{grid-template-columns:1fr 1fr}.builder-field-grid,.builder-range-grid,.builder-range-grid.weapon,.builder-augment-list,.builder-inherited{grid-template-columns:1fr}.builder-inherited span{border-left:0;border-top:1px solid #303536}.builder-rolls>div,.builder-chip-rolls>div{grid-template-columns:1fr}.builder-rolls>div>button{min-height:44px}.builder-picker-current{min-height:52px}.builder-picker-panel>div>button{min-height:46px}} diff --git a/src/lib/builder/loadout.ts b/src/lib/builder/loadout.ts index 304e67a..2e205b6 100644 --- a/src/lib/builder/loadout.ts +++ b/src/lib/builder/loadout.ts @@ -1,5 +1,5 @@ export type Roll = { statId: string; value: number }; -export type GearSelection = { itemId: string; level: number; brandId: string; talentId: string; health: number; armor: number; rolls: [Roll, Roll]; augmentSlots: number[] }; +export type GearSelection = { itemId: string; level: number; brandId: string; talentId: string; health: number; armor: number; firepower: number; toughness: number; engineering: number; rolls: [Roll, Roll]; augmentSlots: number[] }; export type WeaponSelection = { itemId: string; level: number; tier: number; damage: number; firepower: number; toughness: number; engineering: number; talent1Id: string; talent2Id: string; rolls: [Roll, Roll] }; export type ChipSelection = { setId: string; quality: 'Superior' | 'High-end'; primary: Roll; secondary: [Roll, Roll] }; export type ProtocolSelection = { itemId: string; level: number; talentUpgrades: number }; @@ -15,7 +15,7 @@ export type LoadoutDraft = { export const gearSlots = ['Holster', 'Gloves', 'Backpack', 'Knee Pads', 'Body Armor', 'Mask'] as const; export const blankRoll = (): Roll => ({ statId: '', value: 0 }); -export const blankGear = (): GearSelection => ({ itemId: '', level: 50, brandId: '', talentId: '', health: 0, armor: 0, rolls: [blankRoll(), blankRoll()], augmentSlots: [] }); +export const blankGear = (): GearSelection => ({ itemId: '', level: 50, brandId: '', talentId: '', health: 0, armor: 0, firepower: 0, toughness: 0, engineering: 0, rolls: [blankRoll(), blankRoll()], augmentSlots: [] }); export const blankWeapon = (): WeaponSelection => ({ itemId: '', level: 50, tier: 0, damage: 0, firepower: 0, toughness: 0, engineering: 0, talent1Id: '', talent2Id: '', rolls: [blankRoll(), blankRoll()] }); export const blankChip = (): ChipSelection => ({ setId: '', quality: 'Superior', primary: blankRoll(), secondary: [blankRoll(), blankRoll()] }); export const createDraft = (): LoadoutDraft => ({ diff --git a/src/lib/components/BuilderRadioGrid.svelte b/src/lib/components/BuilderRadioGrid.svelte index b60c55d..4e3b049 100644 --- a/src/lib/components/BuilderRadioGrid.svelte +++ b/src/lib/components/BuilderRadioGrid.svelte @@ -6,6 +6,7 @@ value = $bindable(''), columns = Math.min(items.length, 4), searchable = false, + iconOnly = false, required = false, onchange }: { @@ -15,6 +16,7 @@ value?: string; columns?: number; searchable?: boolean; + iconOnly?: boolean; required?: boolean; onchange?: (id: string) => void; } = $props(); @@ -30,7 +32,7 @@ } -
+
{label} {#if searchable} @@ -38,7 +40,7 @@
{#each visible as item}