}
+ */
+const fetchPresets = async () => {
+ const response = await fetch('option-presets');
+ presets = await response.json();
+ const presetSelect = document.getElementById('game-options-preset');
+ presetSelect.removeAttribute('disabled');
+
+ const game = document.getElementById('player-options').getAttribute('data-game');
+ const presetToApply = localStorage.getItem(`${game}-preset`);
+ const playerName = localStorage.getItem(`${game}-player`);
+ if (presetToApply) {
+ localStorage.removeItem(`${game}-preset`);
+ presetSelect.value = presetToApply;
+ applyPresets(presetToApply);
+ }
+
+ if (playerName) {
+ document.getElementById('player-name').value = playerName;
+ localStorage.removeItem(`${game}-player`);
+ }
+};
+
+/**
+ * Clear the localStorage for this game and set a preset to be loaded upon page reload
+ * @param evt
+ */
+const choosePreset = (evt) => {
+ if (evt.target.value === 'custom') { return; }
+
+ const game = document.getElementById('player-options').getAttribute('data-game');
+ localStorage.removeItem(game);
+
+ localStorage.setItem(`${game}-player`, document.getElementById('player-name').value);
+ if (evt.target.value !== 'default') {
+ localStorage.setItem(`${game}-preset`, evt.target.value);
+ }
+
+ document.querySelectorAll('#options-form input, #options-form select').forEach((input) => {
+ if (input.id === 'player-name') { return; }
+ input.removeAttribute('value');
+ });
+
+ window.location.replace(window.location.href);
+};
+
+const applyPresets = (presetName) => {
+ // Ignore the "default" preset, because it gets set automatically by Jinja
+ if (presetName === 'default') {
+ saveSettings();
+ return;
+ }
+
+ if (!presets[presetName]) {
+ console.error(`Unknown preset ${presetName} chosen`);
+ return;
+ }
+
+ const preset = presets[presetName];
+ Object.keys(preset).forEach((optionName) => {
+ const optionValue = preset[optionName];
+
+ // Handle List and Set options
+ if (Array.isArray(optionValue)) {
+ document.querySelectorAll(`input[type=checkbox][name=${optionName}]`).forEach((checkbox) => {
+ if (optionValue.includes(checkbox.value)) {
+ checkbox.setAttribute('checked', '1');
+ } else {
+ checkbox.removeAttribute('checked');
+ }
+ });
+ return;
+ }
+
+ // Handle Dict options
+ if (typeof(optionValue) === 'object' && optionValue !== null) {
+ const itemNames = Object.keys(optionValue);
+ document.querySelectorAll(`input[type=number][data-option-name=${optionName}]`).forEach((input) => {
+ const itemName = input.getAttribute('data-item-name');
+ input.value = (itemNames.includes(itemName)) ? optionValue[itemName] : 0
+ });
+ return;
+ }
+
+ // Identify all possible elements
+ const normalInput = document.getElementById(optionName);
+ const customInput = document.getElementById(`${optionName}-custom`);
+ const rangeValue = document.getElementById(`${optionName}-value`);
+ const randomizeInput = document.getElementById(`random-${optionName}`);
+ const namedRangeSelect = document.getElementById(`${optionName}-select`);
+
+ // It is possible for named ranges to use name of a value rather than the value itself. This is accounted for here
+ let trueValue = optionValue;
+ if (namedRangeSelect) {
+ namedRangeSelect.querySelectorAll('option').forEach((opt) => {
+ if (opt.innerText.startsWith(optionValue)) {
+ trueValue = opt.value;
+ }
+ });
+ namedRangeSelect.value = trueValue;
+ }
+
+ // Handle options whose presets are "random"
+ if (optionValue === 'random') {
+ normalInput.setAttribute('disabled', '1');
+ randomizeInput.setAttribute('checked', '1');
+ if (customInput) {
+ customInput.setAttribute('disabled', '1');
+ }
+ if (rangeValue) {
+ rangeValue.innerText = normalInput.value;
+ }
+ if (namedRangeSelect) {
+ namedRangeSelect.setAttribute('disabled', '1');
+ }
+ return;
+ }
+
+ // Handle normal (text, number, select, etc.) and custom inputs (custom inputs exist with TextChoice only)
+ normalInput.value = trueValue;
+ normalInput.removeAttribute('disabled');
+ randomizeInput.removeAttribute('checked');
+ if (customInput) {
+ document.getElementById(`${optionName}-custom`).removeAttribute('disabled');
+ }
+ if (rangeValue) {
+ rangeValue.innerText = trueValue;
+ }
+ });
+
+ saveSettings();
+};
+
+const showUserMessage = (text) => {
+ const userMessage = document.getElementById('user-message');
+ userMessage.innerText = text;
+ userMessage.addEventListener('click', hideUserMessage);
+ userMessage.style.display = 'block';
+};
+
+const hideUserMessage = () => {
+ const userMessage = document.getElementById('user-message');
+ userMessage.removeEventListener('click', hideUserMessage);
+ userMessage.style.display = 'none';
+};
diff --git a/WebHostLib/static/assets/sc2wolTracker.js b/WebHostLib/static/assets/sc2Tracker.js
similarity index 85%
rename from WebHostLib/static/assets/sc2wolTracker.js
rename to WebHostLib/static/assets/sc2Tracker.js
index a698214b8dd6..30d4acd60b7e 100644
--- a/WebHostLib/static/assets/sc2wolTracker.js
+++ b/WebHostLib/static/assets/sc2Tracker.js
@@ -25,16 +25,16 @@ window.addEventListener('load', () => {
// Collapsible advancement sections
const categories = document.getElementsByClassName("location-category");
- for (let i = 0; i < categories.length; i++) {
- let hide_id = categories[i].id.split('-')[0];
- if (hide_id == 'Total') {
+ for (let category of categories) {
+ let hide_id = category.id.split('_')[0];
+ if (hide_id === 'Total') {
continue;
}
- categories[i].addEventListener('click', function() {
+ category.addEventListener('click', function() {
// Toggle the advancement list
document.getElementById(hide_id).classList.toggle("hide");
// Change text of the header
- const tab_header = document.getElementById(hide_id+'-header').children[0];
+ const tab_header = document.getElementById(hide_id+'_header').children[0];
const orig_text = tab_header.innerHTML;
let new_text;
if (orig_text.includes("â–¼")) {
diff --git a/WebHostLib/static/assets/supportedGames.js b/WebHostLib/static/assets/supportedGames.js
index 56eb15b5e580..b692db9283d2 100644
--- a/WebHostLib/static/assets/supportedGames.js
+++ b/WebHostLib/static/assets/supportedGames.js
@@ -1,18 +1,16 @@
window.addEventListener('load', () => {
// Add toggle listener to all elements with .collapse-toggle
- const toggleButtons = document.querySelectorAll('.collapse-toggle');
- toggleButtons.forEach((e) => e.addEventListener('click', toggleCollapse));
+ const toggleButtons = document.querySelectorAll('details');
// Handle game filter input
const gameSearch = document.getElementById('game-search');
gameSearch.value = '';
gameSearch.addEventListener('input', (evt) => {
if (!evt.target.value.trim()) {
- // If input is empty, display all collapsed games
+ // If input is empty, display all games as collapsed
return toggleButtons.forEach((header) => {
header.style.display = null;
- header.firstElementChild.innerText = 'â–¶';
- header.nextElementSibling.classList.add('collapsed');
+ header.removeAttribute('open');
});
}
@@ -21,12 +19,10 @@ window.addEventListener('load', () => {
// If the game name includes the search string, display the game. If not, hide it
if (header.getAttribute('data-game').toLowerCase().includes(evt.target.value.toLowerCase())) {
header.style.display = null;
- header.firstElementChild.innerText = 'â–¼';
- header.nextElementSibling.classList.remove('collapsed');
+ header.setAttribute('open', '1');
} else {
header.style.display = 'none';
- header.firstElementChild.innerText = 'â–¶';
- header.nextElementSibling.classList.add('collapsed');
+ header.removeAttribute('open');
}
});
});
@@ -35,30 +31,14 @@ window.addEventListener('load', () => {
document.getElementById('collapse-all').addEventListener('click', collapseAll);
});
-const toggleCollapse = (evt) => {
- const gameArrow = evt.target.firstElementChild;
- const gameInfo = evt.target.nextElementSibling;
- if (gameInfo.classList.contains('collapsed')) {
- gameArrow.innerText = 'â–¼';
- gameInfo.classList.remove('collapsed');
- } else {
- gameArrow.innerText = 'â–¶';
- gameInfo.classList.add('collapsed');
- }
-};
-
const expandAll = () => {
- document.querySelectorAll('.collapse-toggle').forEach((header) => {
- if (header.style.display === 'none') { return; }
- header.firstElementChild.innerText = 'â–¼';
- header.nextElementSibling.classList.remove('collapsed');
+ document.querySelectorAll('details').forEach((detail) => {
+ detail.setAttribute('open', '1');
});
};
const collapseAll = () => {
- document.querySelectorAll('.collapse-toggle').forEach((header) => {
- if (header.style.display === 'none') { return; }
- header.firstElementChild.innerText = 'â–¶';
- header.nextElementSibling.classList.add('collapsed');
+ document.querySelectorAll('details').forEach((detail) => {
+ detail.removeAttribute('open');
});
};
diff --git a/WebHostLib/static/assets/trackerCommon.js b/WebHostLib/static/assets/trackerCommon.js
index b8e089ece5d3..6324837b2816 100644
--- a/WebHostLib/static/assets/trackerCommon.js
+++ b/WebHostLib/static/assets/trackerCommon.js
@@ -27,7 +27,7 @@ const adjustTableHeight = () => {
* @returns {string}
*/
const secondsToHours = (seconds) => {
- let hours = Math.floor(seconds / 3600);
+ let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds - (hours * 3600)) / 60).toString().padStart(2, '0');
return `${hours}:${minutes}`;
};
@@ -38,18 +38,18 @@ window.addEventListener('load', () => {
info: false,
dom: "t",
stateSave: true,
- stateSaveCallback: function(settings, data) {
+ stateSaveCallback: function (settings, data) {
delete data.search;
localStorage.setItem(`DataTables_${settings.sInstance}_/tracker`, JSON.stringify(data));
},
- stateLoadCallback: function(settings) {
+ stateLoadCallback: function (settings) {
return JSON.parse(localStorage.getItem(`DataTables_${settings.sInstance}_/tracker`));
},
- footerCallback: function(tfoot, data, start, end, display) {
+ footerCallback: function (tfoot, data, start, end, display) {
if (tfoot) {
const activityData = this.api().column('lastActivity:name').data().toArray().filter(x => !isNaN(x));
Array.from(tfoot?.children).find(td => td.classList.contains('last-activity')).innerText =
- (activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None';
+ (activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None';
}
},
columnDefs: [
@@ -123,49 +123,64 @@ window.addEventListener('load', () => {
event.preventDefault();
}
});
- const tracker = document.getElementById('tracker-wrapper').getAttribute('data-tracker');
- const target_second = document.getElementById('tracker-wrapper').getAttribute('data-second') + 3;
+ const target_second = parseInt(document.getElementById('tracker-wrapper').getAttribute('data-second')) + 3;
+ console.log("Target second of refresh: " + target_second);
- function getSleepTimeSeconds(){
+ function getSleepTimeSeconds() {
// -40 % 60 is -40, which is absolutely wrong and should burn
var sleepSeconds = (((target_second - new Date().getSeconds()) % 60) + 60) % 60;
return sleepSeconds || 60;
}
+ let update_on_view = false;
const update = () => {
- const target = $("");
- console.log("Updating Tracker...");
- target.load(location.href, function (response, status) {
- if (status === "success") {
- target.find(".table").each(function (i, new_table) {
- const new_trs = $(new_table).find("tbody>tr");
- const footer_tr = $(new_table).find("tfoot>tr");
- const old_table = tables.eq(i);
- const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop();
- const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft();
- old_table.clear();
- if (footer_tr.length) {
- $(old_table.table).find("tfoot").html(footer_tr);
- }
- old_table.rows.add(new_trs);
- old_table.draw();
- $(old_table.settings()[0].nScrollBody).scrollTop(topscroll);
- $(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll);
- });
- $("#multi-stream-link").replaceWith(target.find("#multi-stream-link"));
- } else {
- console.log("Failed to connect to Server, in order to update Table Data.");
- console.log(response);
- }
- })
- setTimeout(update, getSleepTimeSeconds()*1000);
+ if (document.hidden) {
+ console.log("Document reporting as not visible, not updating Tracker...");
+ update_on_view = true;
+ } else {
+ update_on_view = false;
+ const target = $("");
+ console.log("Updating Tracker...");
+ target.load(location.href, function (response, status) {
+ if (status === "success") {
+ target.find(".table").each(function (i, new_table) {
+ const new_trs = $(new_table).find("tbody>tr");
+ const footer_tr = $(new_table).find("tfoot>tr");
+ const old_table = tables.eq(i);
+ const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop();
+ const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft();
+ old_table.clear();
+ if (footer_tr.length) {
+ $(old_table.table).find("tfoot").html(footer_tr);
+ }
+ old_table.rows.add(new_trs);
+ old_table.draw();
+ $(old_table.settings()[0].nScrollBody).scrollTop(topscroll);
+ $(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll);
+ });
+ $("#multi-stream-link").replaceWith(target.find("#multi-stream-link"));
+ } else {
+ console.log("Failed to connect to Server, in order to update Table Data.");
+ console.log(response);
+ }
+ })
+ }
+ updater = setTimeout(update, getSleepTimeSeconds() * 1000);
}
- setTimeout(update, getSleepTimeSeconds()*1000);
+ let updater = setTimeout(update, getSleepTimeSeconds() * 1000);
window.addEventListener('resize', () => {
adjustTableHeight();
tables.draw();
});
+ window.addEventListener('visibilitychange', () => {
+ if (!document.hidden && update_on_view) {
+ console.log("Page became visible, tracker should be refreshed.");
+ clearTimeout(updater);
+ update();
+ }
+ });
+
adjustTableHeight();
});
diff --git a/WebHostLib/static/assets/weighted-options.js b/WebHostLib/static/assets/weighted-options.js
deleted file mode 100644
index a2fedb5383b7..000000000000
--- a/WebHostLib/static/assets/weighted-options.js
+++ /dev/null
@@ -1,1190 +0,0 @@
-window.addEventListener('load', () => {
- fetchSettingData().then((data) => {
- let settingHash = localStorage.getItem('weighted-settings-hash');
- if (!settingHash) {
- // If no hash data has been set before, set it now
- settingHash = md5(JSON.stringify(data));
- localStorage.setItem('weighted-settings-hash', settingHash);
- localStorage.removeItem('weighted-settings');
- }
-
- if (settingHash !== md5(JSON.stringify(data))) {
- const userMessage = document.getElementById('user-message');
- userMessage.innerText = "Your settings are out of date! Click here to update them! Be aware this will reset " +
- "them all to default.";
- userMessage.classList.add('visible');
- userMessage.addEventListener('click', resetSettings);
- }
-
- // Page setup
- const settings = new WeightedSettings(data);
- settings.buildUI();
- settings.updateVisibleGames();
- adjustHeaderWidth();
-
- // Event listeners
- document.getElementById('export-options').addEventListener('click', () => settings.export());
- document.getElementById('generate-race').addEventListener('click', () => settings.generateGame(true));
- document.getElementById('generate-game').addEventListener('click', () => settings.generateGame());
-
- // Name input field
- const nameInput = document.getElementById('player-name');
- nameInput.setAttribute('data-type', 'data');
- nameInput.setAttribute('data-setting', 'name');
- nameInput.addEventListener('keyup', (evt) => settings.updateBaseSetting(evt));
- nameInput.value = settings.current.name;
- });
-});
-
-const resetSettings = () => {
- localStorage.removeItem('weighted-settings');
- localStorage.removeItem('weighted-settings-hash')
- window.location.reload();
-};
-
-const fetchSettingData = () => new Promise((resolve, reject) => {
- fetch(new Request(`${window.location.origin}/static/generated/weighted-options.json`)).then((response) => {
- try{ response.json().then((jsonObj) => resolve(jsonObj)); }
- catch(error){ reject(error); }
- });
-});
-
-/// The weighted settings across all games.
-class WeightedSettings {
- // The data from the server describing the types of settings available for
- // each game, as a JSON-safe blob.
- data;
-
- // The settings chosen by the user as they'd appear in the YAML file, stored
- // to and retrieved from local storage.
- current;
-
- // A record mapping game names to the associated GameSettings.
- games;
-
- constructor(data) {
- this.data = data;
- this.current = JSON.parse(localStorage.getItem('weighted-settings'));
- this.games = Object.keys(this.data.games).map((game) => new GameSettings(this, game));
- if (this.current) { return; }
-
- this.current = {};
-
- // Transfer base options directly
- for (let baseOption of Object.keys(this.data.baseOptions)){
- this.current[baseOption] = this.data.baseOptions[baseOption];
- }
-
- // Set options per game
- for (let game of Object.keys(this.data.games)) {
- // Initialize game object
- this.current[game] = {};
-
- // Transfer game settings
- for (let gameSetting of Object.keys(this.data.games[game].gameSettings)){
- this.current[game][gameSetting] = {};
-
- const setting = this.data.games[game].gameSettings[gameSetting];
- switch(setting.type){
- case 'select':
- setting.options.forEach((option) => {
- this.current[game][gameSetting][option.value] =
- (setting.hasOwnProperty('defaultValue') && setting.defaultValue === option.value) ? 25 : 0;
- });
- break;
- case 'range':
- case 'named_range':
- this.current[game][gameSetting]['random'] = 0;
- this.current[game][gameSetting]['random-low'] = 0;
- this.current[game][gameSetting]['random-middle'] = 0;
- this.current[game][gameSetting]['random-high'] = 0;
- if (setting.hasOwnProperty('defaultValue')) {
- this.current[game][gameSetting][setting.defaultValue] = 25;
- } else {
- this.current[game][gameSetting][setting.min] = 25;
- }
- break;
-
- case 'items-list':
- case 'locations-list':
- case 'custom-list':
- this.current[game][gameSetting] = setting.defaultValue;
- break;
-
- default:
- console.error(`Unknown setting type for ${game} setting ${gameSetting}: ${setting.type}`);
- }
- }
-
- this.current[game].start_inventory = {};
- this.current[game].exclude_locations = [];
- this.current[game].priority_locations = [];
- this.current[game].local_items = [];
- this.current[game].non_local_items = [];
- this.current[game].start_hints = [];
- this.current[game].start_location_hints = [];
- }
-
- this.save();
- }
-
- // Saves the current settings to local storage.
- save() {
- localStorage.setItem('weighted-settings', JSON.stringify(this.current));
- }
-
- buildUI() {
- // Build the game-choice div
- this.#buildGameChoice();
-
- const gamesWrapper = document.getElementById('games-wrapper');
- this.games.forEach((game) => {
- gamesWrapper.appendChild(game.buildUI());
- });
- }
-
- #buildGameChoice() {
- const gameChoiceDiv = document.getElementById('game-choice');
- const h2 = document.createElement('h2');
- h2.innerText = 'Game Select';
- gameChoiceDiv.appendChild(h2);
-
- const gameSelectDescription = document.createElement('p');
- gameSelectDescription.classList.add('setting-description');
- gameSelectDescription.innerText = 'Choose which games you might be required to play.';
- gameChoiceDiv.appendChild(gameSelectDescription);
-
- const hintText = document.createElement('p');
- hintText.classList.add('hint-text');
- hintText.innerText = 'If a game\'s value is greater than zero, you can click it\'s name to jump ' +
- 'to that section.'
- gameChoiceDiv.appendChild(hintText);
-
- // Build the game choice table
- const table = document.createElement('table');
- const tbody = document.createElement('tbody');
-
- Object.keys(this.data.games).forEach((game) => {
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- const span = document.createElement('span');
- span.innerText = game;
- span.setAttribute('id', `${game}-game-option`)
- tdLeft.appendChild(span);
- tr.appendChild(tdLeft);
-
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.setAttribute('data-type', 'weight');
- range.setAttribute('data-setting', 'game');
- range.setAttribute('data-option', game);
- range.value = this.current.game[game];
- range.addEventListener('change', (evt) => {
- this.updateBaseSetting(evt);
- this.updateVisibleGames(); // Show or hide games based on the new settings
- });
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
-
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `game-${game}`)
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
- tbody.appendChild(tr);
- });
-
- table.appendChild(tbody);
- gameChoiceDiv.appendChild(table);
- }
-
- // Verifies that `this.settings` meets all the requirements for world
- // generation, normalizes it for serialization, and returns the result.
- #validateSettings() {
- const settings = structuredClone(this.current);
- const userMessage = document.getElementById('user-message');
- let errorMessage = null;
-
- // User must choose a name for their file
- if (
- !settings.name ||
- settings.name.toString().trim().length === 0 ||
- settings.name.toString().toLowerCase().trim() === 'player'
- ) {
- userMessage.innerText = 'You forgot to set your player name at the top of the page!';
- userMessage.classList.add('visible');
- userMessage.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- return;
- }
-
- // Clean up the settings output
- Object.keys(settings.game).forEach((game) => {
- // Remove any disabled games
- if (settings.game[game] === 0) {
- delete settings.game[game];
- delete settings[game];
- return;
- }
-
- Object.keys(settings[game]).forEach((setting) => {
- // Remove any disabled options
- Object.keys(settings[game][setting]).forEach((option) => {
- if (settings[game][setting][option] === 0) {
- delete settings[game][setting][option];
- }
- });
-
- if (
- Object.keys(settings[game][setting]).length === 0 &&
- !Array.isArray(settings[game][setting]) &&
- setting !== 'start_inventory'
- ) {
- errorMessage = `${game} // ${setting} has no values above zero!`;
- }
-
- // Remove weights from options with only one possibility
- if (
- Object.keys(settings[game][setting]).length === 1 &&
- !Array.isArray(settings[game][setting]) &&
- setting !== 'start_inventory'
- ) {
- settings[game][setting] = Object.keys(settings[game][setting])[0];
- }
-
- // Remove empty arrays
- else if (
- ['exclude_locations', 'priority_locations', 'local_items',
- 'non_local_items', 'start_hints', 'start_location_hints'].includes(setting) &&
- settings[game][setting].length === 0
- ) {
- delete settings[game][setting];
- }
-
- // Remove empty start inventory
- else if (
- setting === 'start_inventory' &&
- Object.keys(settings[game]['start_inventory']).length === 0
- ) {
- delete settings[game]['start_inventory'];
- }
- });
- });
-
- if (Object.keys(settings.game).length === 0) {
- errorMessage = 'You have not chosen a game to play!';
- }
-
- // Remove weights if there is only one game
- else if (Object.keys(settings.game).length === 1) {
- settings.game = Object.keys(settings.game)[0];
- }
-
- // If an error occurred, alert the user and do not export the file
- if (errorMessage) {
- userMessage.innerText = errorMessage;
- userMessage.classList.add('visible');
- userMessage.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- return;
- }
-
- // If no error occurred, hide the user message if it is visible
- userMessage.classList.remove('visible');
- return settings;
- }
-
- updateVisibleGames() {
- Object.entries(this.current.game).forEach(([game, weight]) => {
- const gameDiv = document.getElementById(`${game}-div`);
- const gameOption = document.getElementById(`${game}-game-option`);
- if (parseInt(weight, 10) > 0) {
- gameDiv.classList.remove('invisible');
- gameOption.classList.add('jump-link');
- gameOption.addEventListener('click', () => {
- const gameDiv = document.getElementById(`${game}-div`);
- if (gameDiv.classList.contains('invisible')) { return; }
- gameDiv.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- });
- } else {
- gameDiv.classList.add('invisible');
- gameOption.classList.remove('jump-link');
- }
- });
- }
-
- updateBaseSetting(event) {
- const setting = event.target.getAttribute('data-setting');
- const option = event.target.getAttribute('data-option');
- const type = event.target.getAttribute('data-type');
-
- switch(type){
- case 'weight':
- this.current[setting][option] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
- document.getElementById(`${setting}-${option}`).innerText = event.target.value;
- break;
- case 'data':
- this.current[setting] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
- break;
- }
-
- this.save();
- }
-
- export() {
- const settings = this.#validateSettings();
- if (!settings) { return; }
-
- const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`);
- download(`${document.getElementById('player-name').value}.yaml`, yamlText);
- }
-
- generateGame(raceMode = false) {
- const settings = this.#validateSettings();
- if (!settings) { return; }
-
- axios.post('/api/generate', {
- weights: { player: JSON.stringify(settings) },
- presetData: { player: JSON.stringify(settings) },
- playerCount: 1,
- spoiler: 3,
- race: raceMode ? '1' : '0',
- }).then((response) => {
- window.location.href = response.data.url;
- }).catch((error) => {
- const userMessage = document.getElementById('user-message');
- userMessage.innerText = 'Something went wrong and your game could not be generated.';
- if (error.response.data.text) {
- userMessage.innerText += ' ' + error.response.data.text;
- }
- userMessage.classList.add('visible');
- userMessage.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- console.error(error);
- });
- }
-}
-
-// Settings for an individual game.
-class GameSettings {
- // The WeightedSettings that contains this game's settings. Used to save
- // settings after editing.
- #allSettings;
-
- // The name of this game.
- name;
-
- // The data from the server describing the types of settings available for
- // this game, as a JSON-safe blob.
- get data() {
- return this.#allSettings.data.games[this.name];
- }
-
- // The settings chosen by the user as they'd appear in the YAML file, stored
- // to and retrieved from local storage.
- get current() {
- return this.#allSettings.current[this.name];
- }
-
- constructor(allSettings, name) {
- this.#allSettings = allSettings;
- this.name = name;
- }
-
- // Builds and returns the settings UI for this game.
- buildUI() {
- // Create game div, invisible by default
- const gameDiv = document.createElement('div');
- gameDiv.setAttribute('id', `${this.name}-div`);
- gameDiv.classList.add('game-div');
- gameDiv.classList.add('invisible');
-
- const gameHeader = document.createElement('h2');
- gameHeader.innerText = this.name;
- gameDiv.appendChild(gameHeader);
-
- const collapseButton = document.createElement('a');
- collapseButton.innerText = '(Collapse)';
- gameDiv.appendChild(collapseButton);
-
- const expandButton = document.createElement('a');
- expandButton.innerText = '(Expand)';
- expandButton.classList.add('invisible');
- gameDiv.appendChild(expandButton);
-
- // Sort items and locations alphabetically.
- this.data.gameItems.sort();
- this.data.gameLocations.sort();
-
- const weightedSettingsDiv = this.#buildWeightedSettingsDiv();
- gameDiv.appendChild(weightedSettingsDiv);
-
- const itemPoolDiv = this.#buildItemPoolDiv();
- gameDiv.appendChild(itemPoolDiv);
-
- const hintsDiv = this.#buildHintsDiv();
- gameDiv.appendChild(hintsDiv);
-
- const locationsDiv = this.#buildPriorityExclusionDiv();
- gameDiv.appendChild(locationsDiv);
-
- collapseButton.addEventListener('click', () => {
- collapseButton.classList.add('invisible');
- weightedSettingsDiv.classList.add('invisible');
- itemPoolDiv.classList.add('invisible');
- hintsDiv.classList.add('invisible');
- locationsDiv.classList.add('invisible');
- expandButton.classList.remove('invisible');
- });
-
- expandButton.addEventListener('click', () => {
- collapseButton.classList.remove('invisible');
- weightedSettingsDiv.classList.remove('invisible');
- itemPoolDiv.classList.remove('invisible');
- hintsDiv.classList.remove('invisible');
- locationsDiv.classList.remove('invisible');
- expandButton.classList.add('invisible');
- });
-
- return gameDiv;
- }
-
- #buildWeightedSettingsDiv() {
- const settingsWrapper = document.createElement('div');
- settingsWrapper.classList.add('settings-wrapper');
-
- Object.keys(this.data.gameSettings).forEach((settingName) => {
- const setting = this.data.gameSettings[settingName];
- const settingWrapper = document.createElement('div');
- settingWrapper.classList.add('setting-wrapper');
-
- const settingNameHeader = document.createElement('h4');
- settingNameHeader.innerText = setting.displayName;
- settingWrapper.appendChild(settingNameHeader);
-
- const settingDescription = document.createElement('p');
- settingDescription.classList.add('setting-description');
- settingDescription.innerText = setting.description.replace(/(\n)/g, ' ');
- settingWrapper.appendChild(settingDescription);
-
- switch(setting.type){
- case 'select':
- const optionTable = document.createElement('table');
- const tbody = document.createElement('tbody');
-
- // Add a weight range for each option
- setting.options.forEach((option) => {
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- tdLeft.innerText = option.name;
- tr.appendChild(tdLeft);
-
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('data-game', this.name);
- range.setAttribute('data-setting', settingName);
- range.setAttribute('data-option', option.value);
- range.setAttribute('data-type', setting.type);
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
- range.value = this.current[settingName][option.value];
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
-
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${this.name}-${settingName}-${option.value}`);
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
-
- tbody.appendChild(tr);
- });
-
- optionTable.appendChild(tbody);
- settingWrapper.appendChild(optionTable);
- break;
-
- case 'range':
- case 'named_range':
- const rangeTable = document.createElement('table');
- const rangeTbody = document.createElement('tbody');
-
- const hintText = document.createElement('p');
- hintText.classList.add('hint-text');
- hintText.innerHTML = 'This is a range option. You may enter a valid numerical value in the text box ' +
- `below, then press the "Add" button to add a weight for it.
Accepted values:
` +
- `Normal range: ${setting.min} - ${setting.max}`;
-
- const acceptedValuesOutsideRange = [];
- if (setting.hasOwnProperty('value_names')) {
- Object.keys(setting.value_names).forEach((specialName) => {
- if (
- (setting.value_names[specialName] < setting.min) ||
- (setting.value_names[specialName] > setting.max)
- ) {
- hintText.innerHTML += `
${specialName}: ${setting.value_names[specialName]}`;
- acceptedValuesOutsideRange.push(setting.value_names[specialName]);
- }
- });
-
- hintText.innerHTML += '
Certain values have special meaning:';
- Object.keys(setting.value_names).forEach((specialName) => {
- hintText.innerHTML += `
${specialName}: ${setting.value_names[specialName]}`;
- });
- }
-
- settingWrapper.appendChild(hintText);
-
- const addOptionDiv = document.createElement('div');
- addOptionDiv.classList.add('add-option-div');
- const optionInput = document.createElement('input');
- optionInput.setAttribute('id', `${this.name}-${settingName}-option`);
- let placeholderText = `${setting.min} - ${setting.max}`;
- acceptedValuesOutsideRange.forEach((aVal) => placeholderText += `, ${aVal}`);
- optionInput.setAttribute('placeholder', placeholderText);
- addOptionDiv.appendChild(optionInput);
- const addOptionButton = document.createElement('button');
- addOptionButton.innerText = 'Add';
- addOptionDiv.appendChild(addOptionButton);
- settingWrapper.appendChild(addOptionDiv);
- optionInput.addEventListener('keydown', (evt) => {
- if (evt.key === 'Enter') { addOptionButton.dispatchEvent(new Event('click')); }
- });
-
- addOptionButton.addEventListener('click', () => {
- const optionInput = document.getElementById(`${this.name}-${settingName}-option`);
- let option = optionInput.value;
- if (!option || !option.trim()) { return; }
- option = parseInt(option, 10);
-
- let optionAcceptable = false;
- if ((option > setting.min) && (option < setting.max)) {
- optionAcceptable = true;
- }
- if (setting.hasOwnProperty('value_names') && Object.values(setting.value_names).includes(option)){
- optionAcceptable = true;
- }
- if (!optionAcceptable) { return; }
-
- optionInput.value = '';
- if (document.getElementById(`${this.name}-${settingName}-${option}-range`)) { return; }
-
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- tdLeft.innerText = option;
- if (
- setting.hasOwnProperty('value_names') &&
- Object.values(setting.value_names).includes(parseInt(option, 10))
- ) {
- const optionName = Object.keys(setting.value_names).find(
- (key) => setting.value_names[key] === parseInt(option, 10)
- );
- tdLeft.innerText += ` [${optionName}]`;
- }
- tr.appendChild(tdLeft);
-
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
- range.setAttribute('data-game', this.name);
- range.setAttribute('data-setting', settingName);
- range.setAttribute('data-option', option);
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
- range.value = this.current[settingName][parseInt(option, 10)];
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
-
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
-
- const tdDelete = document.createElement('td');
- tdDelete.classList.add('td-delete');
- const deleteButton = document.createElement('span');
- deleteButton.classList.add('range-option-delete');
- deleteButton.innerText = 'âŒ';
- deleteButton.addEventListener('click', () => {
- range.value = 0;
- range.dispatchEvent(new Event('change'));
- rangeTbody.removeChild(tr);
- });
- tdDelete.appendChild(deleteButton);
- tr.appendChild(tdDelete);
-
- rangeTbody.appendChild(tr);
-
- // Save new option to settings
- range.dispatchEvent(new Event('change'));
- });
-
- Object.keys(this.current[settingName]).forEach((option) => {
- // These options are statically generated below, and should always appear even if they are deleted
- // from localStorage
- if (['random', 'random-low', 'random-middle', 'random-high'].includes(option)) { return; }
-
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- tdLeft.innerText = option;
- if (
- setting.hasOwnProperty('value_names') &&
- Object.values(setting.value_names).includes(parseInt(option, 10))
- ) {
- const optionName = Object.keys(setting.value_names).find(
- (key) => setting.value_names[key] === parseInt(option, 10)
- );
- tdLeft.innerText += ` [${optionName}]`;
- }
- tr.appendChild(tdLeft);
-
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
- range.setAttribute('data-game', this.name);
- range.setAttribute('data-setting', settingName);
- range.setAttribute('data-option', option);
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
- range.value = this.current[settingName][parseInt(option, 10)];
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
-
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
-
- const tdDelete = document.createElement('td');
- tdDelete.classList.add('td-delete');
- const deleteButton = document.createElement('span');
- deleteButton.classList.add('range-option-delete');
- deleteButton.innerText = 'âŒ';
- deleteButton.addEventListener('click', () => {
- range.value = 0;
- const changeEvent = new Event('change');
- changeEvent.action = 'rangeDelete';
- range.dispatchEvent(changeEvent);
- rangeTbody.removeChild(tr);
- });
- tdDelete.appendChild(deleteButton);
- tr.appendChild(tdDelete);
-
- rangeTbody.appendChild(tr);
- });
-
- ['random', 'random-low', 'random-middle', 'random-high'].forEach((option) => {
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- switch(option){
- case 'random':
- tdLeft.innerText = 'Random';
- break;
- case 'random-low':
- tdLeft.innerText = "Random (Low)";
- break;
- case 'random-middle':
- tdLeft.innerText = 'Random (Middle)';
- break;
- case 'random-high':
- tdLeft.innerText = "Random (High)";
- break;
- }
- tr.appendChild(tdLeft);
-
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
- range.setAttribute('data-game', this.name);
- range.setAttribute('data-setting', settingName);
- range.setAttribute('data-option', option);
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
- range.value = this.current[settingName][option];
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
-
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
- rangeTbody.appendChild(tr);
- });
-
- rangeTable.appendChild(rangeTbody);
- settingWrapper.appendChild(rangeTable);
- break;
-
- case 'items-list':
- const itemsList = this.#buildItemsDiv(settingName);
- settingWrapper.appendChild(itemsList);
- break;
-
- case 'locations-list':
- const locationsList = this.#buildLocationsDiv(settingName);
- settingWrapper.appendChild(locationsList);
- break;
-
- case 'custom-list':
- const customList = this.#buildListDiv(settingName, this.data.gameSettings[settingName].options);
- settingWrapper.appendChild(customList);
- break;
-
- default:
- console.error(`Unknown setting type for ${this.name} setting ${settingName}: ${setting.type}`);
- return;
- }
-
- settingsWrapper.appendChild(settingWrapper);
- });
-
- return settingsWrapper;
- }
-
- #buildItemPoolDiv() {
- const itemsDiv = document.createElement('div');
- itemsDiv.classList.add('items-div');
-
- const itemsDivHeader = document.createElement('h3');
- itemsDivHeader.innerText = 'Item Pool';
- itemsDiv.appendChild(itemsDivHeader);
-
- const itemsDescription = document.createElement('p');
- itemsDescription.classList.add('setting-description');
- itemsDescription.innerText = 'Choose if you would like to start with items, or control if they are placed in ' +
- 'your seed or someone else\'s.';
- itemsDiv.appendChild(itemsDescription);
-
- const itemsHint = document.createElement('p');
- itemsHint.classList.add('hint-text');
- itemsHint.innerText = 'Drag and drop items from one box to another.';
- itemsDiv.appendChild(itemsHint);
-
- const itemsWrapper = document.createElement('div');
- itemsWrapper.classList.add('items-wrapper');
-
- const itemDragoverHandler = (evt) => evt.preventDefault();
- const itemDropHandler = (evt) => this.#itemDropHandler(evt);
-
- // Create container divs for each category
- const availableItemsWrapper = document.createElement('div');
- availableItemsWrapper.classList.add('item-set-wrapper');
- availableItemsWrapper.innerText = 'Available Items';
- const availableItems = document.createElement('div');
- availableItems.classList.add('item-container');
- availableItems.setAttribute('id', `${this.name}-available_items`);
- availableItems.addEventListener('dragover', itemDragoverHandler);
- availableItems.addEventListener('drop', itemDropHandler);
-
- const startInventoryWrapper = document.createElement('div');
- startInventoryWrapper.classList.add('item-set-wrapper');
- startInventoryWrapper.innerText = 'Start Inventory';
- const startInventory = document.createElement('div');
- startInventory.classList.add('item-container');
- startInventory.setAttribute('id', `${this.name}-start_inventory`);
- startInventory.setAttribute('data-setting', 'start_inventory');
- startInventory.addEventListener('dragover', itemDragoverHandler);
- startInventory.addEventListener('drop', itemDropHandler);
-
- const localItemsWrapper = document.createElement('div');
- localItemsWrapper.classList.add('item-set-wrapper');
- localItemsWrapper.innerText = 'Local Items';
- const localItems = document.createElement('div');
- localItems.classList.add('item-container');
- localItems.setAttribute('id', `${this.name}-local_items`);
- localItems.setAttribute('data-setting', 'local_items')
- localItems.addEventListener('dragover', itemDragoverHandler);
- localItems.addEventListener('drop', itemDropHandler);
-
- const nonLocalItemsWrapper = document.createElement('div');
- nonLocalItemsWrapper.classList.add('item-set-wrapper');
- nonLocalItemsWrapper.innerText = 'Non-Local Items';
- const nonLocalItems = document.createElement('div');
- nonLocalItems.classList.add('item-container');
- nonLocalItems.setAttribute('id', `${this.name}-non_local_items`);
- nonLocalItems.setAttribute('data-setting', 'non_local_items');
- nonLocalItems.addEventListener('dragover', itemDragoverHandler);
- nonLocalItems.addEventListener('drop', itemDropHandler);
-
- // Populate the divs
- this.data.gameItems.forEach((item) => {
- if (Object.keys(this.current.start_inventory).includes(item)){
- const itemDiv = this.#buildItemQtyDiv(item);
- itemDiv.setAttribute('data-setting', 'start_inventory');
- startInventory.appendChild(itemDiv);
- } else if (this.current.local_items.includes(item)) {
- const itemDiv = this.#buildItemDiv(item);
- itemDiv.setAttribute('data-setting', 'local_items');
- localItems.appendChild(itemDiv);
- } else if (this.current.non_local_items.includes(item)) {
- const itemDiv = this.#buildItemDiv(item);
- itemDiv.setAttribute('data-setting', 'non_local_items');
- nonLocalItems.appendChild(itemDiv);
- } else {
- const itemDiv = this.#buildItemDiv(item);
- availableItems.appendChild(itemDiv);
- }
- });
-
- availableItemsWrapper.appendChild(availableItems);
- startInventoryWrapper.appendChild(startInventory);
- localItemsWrapper.appendChild(localItems);
- nonLocalItemsWrapper.appendChild(nonLocalItems);
- itemsWrapper.appendChild(availableItemsWrapper);
- itemsWrapper.appendChild(startInventoryWrapper);
- itemsWrapper.appendChild(localItemsWrapper);
- itemsWrapper.appendChild(nonLocalItemsWrapper);
- itemsDiv.appendChild(itemsWrapper);
- return itemsDiv;
- }
-
- #buildItemDiv(item) {
- const itemDiv = document.createElement('div');
- itemDiv.classList.add('item-div');
- itemDiv.setAttribute('id', `${this.name}-${item}`);
- itemDiv.setAttribute('data-game', this.name);
- itemDiv.setAttribute('data-item', item);
- itemDiv.setAttribute('draggable', 'true');
- itemDiv.innerText = item;
- itemDiv.addEventListener('dragstart', (evt) => {
- evt.dataTransfer.setData('text/plain', itemDiv.getAttribute('id'));
- });
- return itemDiv;
- }
-
- #buildItemQtyDiv(item) {
- const itemQtyDiv = document.createElement('div');
- itemQtyDiv.classList.add('item-qty-div');
- itemQtyDiv.setAttribute('id', `${this.name}-${item}`);
- itemQtyDiv.setAttribute('data-game', this.name);
- itemQtyDiv.setAttribute('data-item', item);
- itemQtyDiv.setAttribute('draggable', 'true');
- itemQtyDiv.innerText = item;
-
- const inputWrapper = document.createElement('div');
- inputWrapper.classList.add('item-qty-input-wrapper')
-
- const itemQty = document.createElement('input');
- itemQty.setAttribute('value', this.current.start_inventory.hasOwnProperty(item) ?
- this.current.start_inventory[item] : '1');
- itemQty.setAttribute('data-game', this.name);
- itemQty.setAttribute('data-setting', 'start_inventory');
- itemQty.setAttribute('data-option', item);
- itemQty.setAttribute('maxlength', '3');
- itemQty.addEventListener('keyup', (evt) => {
- evt.target.value = isNaN(parseInt(evt.target.value)) ? 0 : parseInt(evt.target.value);
- this.#updateItemSetting(evt);
- });
- inputWrapper.appendChild(itemQty);
- itemQtyDiv.appendChild(inputWrapper);
-
- itemQtyDiv.addEventListener('dragstart', (evt) => {
- evt.dataTransfer.setData('text/plain', itemQtyDiv.getAttribute('id'));
- });
- return itemQtyDiv;
- }
-
- #itemDropHandler(evt) {
- evt.preventDefault();
- const sourceId = evt.dataTransfer.getData('text/plain');
- const sourceDiv = document.getElementById(sourceId);
-
- const item = sourceDiv.getAttribute('data-item');
-
- const oldSetting = sourceDiv.hasAttribute('data-setting') ? sourceDiv.getAttribute('data-setting') : null;
- const newSetting = evt.target.hasAttribute('data-setting') ? evt.target.getAttribute('data-setting') : null;
-
- const itemDiv = newSetting === 'start_inventory' ? this.#buildItemQtyDiv(item) : this.#buildItemDiv(item);
-
- if (oldSetting) {
- if (oldSetting === 'start_inventory') {
- if (this.current[oldSetting].hasOwnProperty(item)) {
- delete this.current[oldSetting][item];
- }
- } else {
- if (this.current[oldSetting].includes(item)) {
- this.current[oldSetting].splice(this.current[oldSetting].indexOf(item), 1);
- }
- }
- }
-
- if (newSetting) {
- itemDiv.setAttribute('data-setting', newSetting);
- document.getElementById(`${this.name}-${newSetting}`).appendChild(itemDiv);
- if (newSetting === 'start_inventory') {
- this.current[newSetting][item] = 1;
- } else {
- if (!this.current[newSetting].includes(item)){
- this.current[newSetting].push(item);
- }
- }
- } else {
- // No setting was assigned, this item has been removed from the settings
- document.getElementById(`${this.name}-available_items`).appendChild(itemDiv);
- }
-
- // Remove the source drag object
- sourceDiv.parentElement.removeChild(sourceDiv);
-
- // Save the updated settings
- this.save();
- }
-
- #buildHintsDiv() {
- const hintsDiv = document.createElement('div');
- hintsDiv.classList.add('hints-div');
- const hintsHeader = document.createElement('h3');
- hintsHeader.innerText = 'Item & Location Hints';
- hintsDiv.appendChild(hintsHeader);
- const hintsDescription = document.createElement('p');
- hintsDescription.classList.add('setting-description');
- hintsDescription.innerText = 'Choose any items or locations to begin the game with the knowledge of where those ' +
- ' items are, or what those locations contain.';
- hintsDiv.appendChild(hintsDescription);
-
- const itemHintsContainer = document.createElement('div');
- itemHintsContainer.classList.add('hints-container');
-
- // Item Hints
- const itemHintsWrapper = document.createElement('div');
- itemHintsWrapper.classList.add('hints-wrapper');
- itemHintsWrapper.innerText = 'Starting Item Hints';
-
- const itemHintsDiv = this.#buildItemsDiv('start_hints');
- itemHintsWrapper.appendChild(itemHintsDiv);
- itemHintsContainer.appendChild(itemHintsWrapper);
-
- // Starting Location Hints
- const locationHintsWrapper = document.createElement('div');
- locationHintsWrapper.classList.add('hints-wrapper');
- locationHintsWrapper.innerText = 'Starting Location Hints';
-
- const locationHintsDiv = this.#buildLocationsDiv('start_location_hints');
- locationHintsWrapper.appendChild(locationHintsDiv);
- itemHintsContainer.appendChild(locationHintsWrapper);
-
- hintsDiv.appendChild(itemHintsContainer);
- return hintsDiv;
- }
-
- #buildPriorityExclusionDiv() {
- const locationsDiv = document.createElement('div');
- locationsDiv.classList.add('locations-div');
- const locationsHeader = document.createElement('h3');
- locationsHeader.innerText = 'Priority & Exclusion Locations';
- locationsDiv.appendChild(locationsHeader);
- const locationsDescription = document.createElement('p');
- locationsDescription.classList.add('setting-description');
- locationsDescription.innerText = 'Priority locations guarantee a progression item will be placed there while ' +
- 'excluded locations will not contain progression or useful items.';
- locationsDiv.appendChild(locationsDescription);
-
- const locationsContainer = document.createElement('div');
- locationsContainer.classList.add('locations-container');
-
- // Priority Locations
- const priorityLocationsWrapper = document.createElement('div');
- priorityLocationsWrapper.classList.add('locations-wrapper');
- priorityLocationsWrapper.innerText = 'Priority Locations';
-
- const priorityLocationsDiv = this.#buildLocationsDiv('priority_locations');
- priorityLocationsWrapper.appendChild(priorityLocationsDiv);
- locationsContainer.appendChild(priorityLocationsWrapper);
-
- // Exclude Locations
- const excludeLocationsWrapper = document.createElement('div');
- excludeLocationsWrapper.classList.add('locations-wrapper');
- excludeLocationsWrapper.innerText = 'Exclude Locations';
-
- const excludeLocationsDiv = this.#buildLocationsDiv('exclude_locations');
- excludeLocationsWrapper.appendChild(excludeLocationsDiv);
- locationsContainer.appendChild(excludeLocationsWrapper);
-
- locationsDiv.appendChild(locationsContainer);
- return locationsDiv;
- }
-
- // Builds a div for a setting whose value is a list of locations.
- #buildLocationsDiv(setting) {
- return this.#buildListDiv(setting, this.data.gameLocations, {
- groups: this.data.gameLocationGroups,
- descriptions: this.data.gameLocationDescriptions,
- });
- }
-
- // Builds a div for a setting whose value is a list of items.
- #buildItemsDiv(setting) {
- return this.#buildListDiv(setting, this.data.gameItems, {
- groups: this.data.gameItemGroups,
- descriptions: this.data.gameItemDescriptions
- });
- }
-
- // Builds a div for a setting named `setting` with a list value that can
- // contain `items`.
- //
- // The `groups` option can be a list of additional options for this list
- // (usually `item_name_groups` or `location_name_groups`) that are displayed
- // in a special section at the top of the list.
- //
- // The `descriptions` option can be a map from item names or group names to
- // descriptions for the user's benefit.
- #buildListDiv(setting, items, {groups = [], descriptions = {}} = {}) {
- const div = document.createElement('div');
- div.classList.add('simple-list');
-
- groups.forEach((group) => {
- const row = this.#addListRow(setting, group, descriptions[group]);
- div.appendChild(row);
- });
-
- if (groups.length > 0) {
- div.appendChild(document.createElement('hr'));
- }
-
- items.forEach((item) => {
- const row = this.#addListRow(setting, item, descriptions[item]);
- div.appendChild(row);
- });
-
- return div;
- }
-
- // Builds and returns a row for a list of checkboxes.
- //
- // If `help` is passed, it's displayed as a help tooltip for this list item.
- #addListRow(setting, item, help = undefined) {
- const row = document.createElement('div');
- row.classList.add('list-row');
-
- const label = document.createElement('label');
- label.setAttribute('for', `${this.name}-${setting}-${item}`);
-
- const checkbox = document.createElement('input');
- checkbox.setAttribute('type', 'checkbox');
- checkbox.setAttribute('id', `${this.name}-${setting}-${item}`);
- checkbox.setAttribute('data-game', this.name);
- checkbox.setAttribute('data-setting', setting);
- checkbox.setAttribute('data-option', item);
- if (this.current[setting].includes(item)) {
- checkbox.setAttribute('checked', '1');
- }
- checkbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
- label.appendChild(checkbox);
-
- const name = document.createElement('span');
- name.innerText = item;
-
- if (help) {
- const helpSpan = document.createElement('span');
- helpSpan.classList.add('interactive');
- helpSpan.setAttribute('data-tooltip', help);
- helpSpan.innerText = '(?)';
- name.innerText += ' ';
- name.appendChild(helpSpan);
-
- // Put the first 7 tooltips below their rows. CSS tooltips in scrolling
- // containers can't be visible outside those containers, so this helps
- // ensure they won't be pushed out the top.
- if (helpSpan.parentNode.childNodes.length < 7) {
- helpSpan.classList.add('tooltip-bottom');
- }
- }
-
- label.appendChild(name);
-
- row.appendChild(label);
- return row;
- }
-
- #updateRangeSetting(evt) {
- const setting = evt.target.getAttribute('data-setting');
- const option = evt.target.getAttribute('data-option');
- document.getElementById(`${this.name}-${setting}-${option}`).innerText = evt.target.value;
- if (evt.action && evt.action === 'rangeDelete') {
- delete this.current[setting][option];
- } else {
- this.current[setting][option] = parseInt(evt.target.value, 10);
- }
- this.save();
- }
-
- #updateListSetting(evt) {
- const setting = evt.target.getAttribute('data-setting');
- const option = evt.target.getAttribute('data-option');
-
- if (evt.target.checked) {
- // If the option is to be enabled and it is already enabled, do nothing
- if (this.current[setting].includes(option)) { return; }
-
- this.current[setting].push(option);
- } else {
- // If the option is to be disabled and it is already disabled, do nothing
- if (!this.current[setting].includes(option)) { return; }
-
- this.current[setting].splice(this.current[setting].indexOf(option), 1);
- }
- this.save();
- }
-
- #updateItemSetting(evt) {
- const setting = evt.target.getAttribute('data-setting');
- const option = evt.target.getAttribute('data-option');
- if (setting === 'start_inventory') {
- this.current[setting][option] = evt.target.value.trim() ? parseInt(evt.target.value) : 0;
- } else {
- this.current[setting][option] = isNaN(evt.target.value) ?
- evt.target.value : parseInt(evt.target.value, 10);
- }
- this.save();
- }
-
- // Saves the current settings to local storage.
- save() {
- this.#allSettings.save();
- }
-}
-
-/** Create an anchor and trigger a download of a text file. */
-const download = (filename, text) => {
- const downloadLink = document.createElement('a');
- downloadLink.setAttribute('href','data:text/yaml;charset=utf-8,'+ encodeURIComponent(text))
- downloadLink.setAttribute('download', filename);
- downloadLink.style.display = 'none';
- document.body.appendChild(downloadLink);
- downloadLink.click();
- document.body.removeChild(downloadLink);
-};
diff --git a/WebHostLib/static/assets/weightedOptions.js b/WebHostLib/static/assets/weightedOptions.js
new file mode 100644
index 000000000000..0417ab174b0e
--- /dev/null
+++ b/WebHostLib/static/assets/weightedOptions.js
@@ -0,0 +1,223 @@
+let deletedOptions = {};
+
+window.addEventListener('load', () => {
+ const worldName = document.querySelector('#weighted-options').getAttribute('data-game');
+
+ // Generic change listener. Detecting unique qualities and acting on them here reduces initial JS initialisation time
+ // and handles dynamically created elements
+ document.addEventListener('change', (evt) => {
+ // Handle updates to range inputs
+ if (evt.target.type === 'range') {
+ // Update span containing range value. All ranges have a corresponding `{rangeId}-value` span
+ document.getElementById(`${evt.target.id}-value`).innerText = evt.target.value;
+
+ // If the changed option was the name of a game, determine whether to show or hide that game's div
+ if (evt.target.id.startsWith('game||')) {
+ const gameName = evt.target.id.split('||')[1];
+ const gameDiv = document.getElementById(`${gameName}-container`);
+ if (evt.target.value > 0) {
+ gameDiv.classList.remove('hidden');
+ } else {
+ gameDiv.classList.add('hidden');
+ }
+ }
+ }
+ });
+
+ // Generic click listener
+ document.addEventListener('click', (evt) => {
+ // Handle creating new rows for Range options
+ if (evt.target.classList.contains('add-range-option-button')) {
+ const optionName = evt.target.getAttribute('data-option');
+ addRangeRow(optionName);
+ }
+
+ // Handle deleting range rows
+ if (evt.target.classList.contains('range-option-delete')) {
+ const targetRow = document.querySelector(`tr[data-row="${evt.target.getAttribute('data-target')}"]`);
+ setDeletedOption(
+ targetRow.getAttribute('data-option-name'),
+ targetRow.getAttribute('data-value'),
+ );
+ targetRow.parentElement.removeChild(targetRow);
+ }
+ });
+
+ // Listen for enter presses on inputs intended to add range rows
+ document.addEventListener('keydown', (evt) => {
+ if (evt.key === 'Enter') {
+ evt.preventDefault();
+ }
+
+ if (evt.key === 'Enter' && evt.target.classList.contains('range-option-value')) {
+ const optionName = evt.target.getAttribute('data-option');
+ addRangeRow(optionName);
+ }
+ });
+
+ // Detect form submission
+ document.getElementById('weighted-options-form').addEventListener('submit', (evt) => {
+ // Save data to localStorage
+ const weightedOptions = {};
+ document.querySelectorAll('input[name]').forEach((input) => {
+ const keys = input.getAttribute('name').split('||');
+
+ // Determine keys
+ const optionName = keys[0] ?? null;
+ const subOption = keys[1] ?? null;
+
+ // Ensure keys exist
+ if (!weightedOptions[optionName]) { weightedOptions[optionName] = {}; }
+ if (subOption && !weightedOptions[optionName][subOption]) {
+ weightedOptions[optionName][subOption] = null;
+ }
+
+ if (subOption) { return weightedOptions[optionName][subOption] = determineValue(input); }
+ if (optionName) { return weightedOptions[optionName] = determineValue(input); }
+ });
+
+ localStorage.setItem(`${worldName}-weights`, JSON.stringify(weightedOptions));
+ localStorage.setItem(`${worldName}-deletedOptions`, JSON.stringify(deletedOptions));
+ });
+
+ // Remove all deleted values as specified by localStorage
+ deletedOptions = JSON.parse(localStorage.getItem(`${worldName}-deletedOptions`) || '{}');
+ Object.keys(deletedOptions).forEach((optionName) => {
+ deletedOptions[optionName].forEach((value) => {
+ const targetRow = document.querySelector(`tr[data-row="${value}-row"]`);
+ targetRow.parentElement.removeChild(targetRow);
+ });
+ });
+
+ // Populate all settings from localStorage on page initialisation
+ const previousSettingsJson = localStorage.getItem(`${worldName}-weights`);
+ if (previousSettingsJson) {
+ const previousSettings = JSON.parse(previousSettingsJson);
+ Object.keys(previousSettings).forEach((option) => {
+ if (typeof previousSettings[option] === 'string') {
+ return document.querySelector(`input[name="${option}"]`).value = previousSettings[option];
+ }
+
+ Object.keys(previousSettings[option]).forEach((value) => {
+ const input = document.querySelector(`input[name="${option}||${value}"]`);
+ if (!input?.type) {
+ return console.error(`Unable to populate option with name ${option}||${value}.`);
+ }
+
+ switch (input.type) {
+ case 'checkbox':
+ input.checked = (parseInt(previousSettings[option][value], 10) === 1);
+ break;
+ case 'range':
+ input.value = parseInt(previousSettings[option][value], 10);
+ break;
+ case 'number':
+ input.value = previousSettings[option][value].toString();
+ break;
+ default:
+ console.error(`Found unsupported input type: ${input.type}`);
+ }
+ });
+ });
+ }
+});
+
+const addRangeRow = (optionName) => {
+ const inputQuery = `input[type=number][data-option="${optionName}"].range-option-value`;
+ const inputTarget = document.querySelector(inputQuery);
+ const newValue = inputTarget.value;
+ if (!/^-?\d+$/.test(newValue)) {
+ alert('Range values must be a positive or negative integer!');
+ return;
+ }
+ inputTarget.value = '';
+ const tBody = document.querySelector(`table[data-option="${optionName}"].range-rows tbody`);
+ const tr = document.createElement('tr');
+ tr.setAttribute('data-row', `${optionName}-${newValue}-row`);
+ tr.setAttribute('data-option-name', optionName);
+ tr.setAttribute('data-value', newValue);
+ const tdLeft = document.createElement('td');
+ tdLeft.classList.add('td-left');
+ const label = document.createElement('label');
+ label.setAttribute('for', `${optionName}||${newValue}`);
+ label.innerText = newValue.toString();
+ tdLeft.appendChild(label);
+ tr.appendChild(tdLeft);
+ const tdMiddle = document.createElement('td');
+ tdMiddle.classList.add('td-middle');
+ const range = document.createElement('input');
+ range.setAttribute('type', 'range');
+ range.setAttribute('min', '0');
+ range.setAttribute('max', '50');
+ range.setAttribute('value', '0');
+ range.setAttribute('id', `${optionName}||${newValue}`);
+ range.setAttribute('name', `${optionName}||${newValue}`);
+ tdMiddle.appendChild(range);
+ tr.appendChild(tdMiddle);
+ const tdRight = document.createElement('td');
+ tdRight.classList.add('td-right');
+ const valueSpan = document.createElement('span');
+ valueSpan.setAttribute('id', `${optionName}||${newValue}-value`);
+ valueSpan.innerText = '0';
+ tdRight.appendChild(valueSpan);
+ tr.appendChild(tdRight);
+ const tdDelete = document.createElement('td');
+ const deleteSpan = document.createElement('span');
+ deleteSpan.classList.add('range-option-delete');
+ deleteSpan.classList.add('js-required');
+ deleteSpan.setAttribute('data-target', `${optionName}-${newValue}-row`);
+ deleteSpan.innerText = 'âŒ';
+ tdDelete.appendChild(deleteSpan);
+ tr.appendChild(tdDelete);
+ tBody.appendChild(tr);
+
+ // Remove this option from the set of deleted options if it exists
+ unsetDeletedOption(optionName, newValue);
+};
+
+/**
+ * Determines the value of an input element, or returns a 1 or 0 if the element is a checkbox
+ *
+ * @param {object} input - The input element.
+ * @returns {number} The value of the input element.
+ */
+const determineValue = (input) => {
+ switch (input.type) {
+ case 'checkbox':
+ return (input.checked ? 1 : 0);
+ case 'range':
+ return parseInt(input.value, 10);
+ default:
+ return input.value;
+ }
+};
+
+/**
+ * Sets the deleted option value for a given world and option name.
+ * If the world or option does not exist, it creates the necessary entries.
+ *
+ * @param {string} optionName - The name of the option.
+ * @param {*} value - The value to be set for the deleted option.
+ * @returns {void}
+ */
+const setDeletedOption = (optionName, value) => {
+ deletedOptions[optionName] = deletedOptions[optionName] || [];
+ deletedOptions[optionName].push(`${optionName}-${value}`);
+};
+
+/**
+ * Removes a specific value from the deletedOptions object.
+ *
+ * @param {string} optionName - The name of the option.
+ * @param {*} value - The value to be removed
+ * @returns {void}
+ */
+const unsetDeletedOption = (optionName, value) => {
+ if (!deletedOptions.hasOwnProperty(optionName)) { return; }
+ if (deletedOptions[optionName].includes(`${optionName}-${value}`)) {
+ deletedOptions[optionName].splice(deletedOptions[optionName].indexOf(`${optionName}-${value}`), 1);
+ }
+ if (deletedOptions[optionName].length === 0) {
+ delete deletedOptions[optionName];
+ }
+};
diff --git a/WebHostLib/static/robots_file.txt b/WebHostLib/static/robots_file.txt
new file mode 100644
index 000000000000..770ae26c1985
--- /dev/null
+++ b/WebHostLib/static/robots_file.txt
@@ -0,0 +1,20 @@
+User-agent: Googlebot
+Disallow: /
+
+User-agent: APIs-Google
+Disallow: /
+
+User-agent: AdsBot-Google-Mobile
+Disallow: /
+
+User-agent: AdsBot-Google-Mobile
+Disallow: /
+
+User-agent: Mediapartners-Google
+Disallow: /
+
+User-agent: Google-Safety
+Disallow: /
+
+User-agent: *
+Disallow: /
diff --git a/WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png b/WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png
deleted file mode 100644
index 8fb366b93ff0..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L2.png b/WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L2.png
deleted file mode 100644
index 336dc5f77af2..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L2.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/advanceballistics.png b/WebHostLib/static/static/icons/sc2/advanceballistics.png
deleted file mode 100644
index 1bf7df9fb74c..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/advanceballistics.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/autoturretblackops.png b/WebHostLib/static/static/icons/sc2/autoturretblackops.png
deleted file mode 100644
index 552707831a00..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/autoturretblackops.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/biomechanicaldrone.png b/WebHostLib/static/static/icons/sc2/biomechanicaldrone.png
deleted file mode 100644
index e7ebf4031619..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/biomechanicaldrone.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/burstcapacitors.png b/WebHostLib/static/static/icons/sc2/burstcapacitors.png
deleted file mode 100644
index 3af9b20a1698..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/burstcapacitors.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png b/WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png
deleted file mode 100644
index d1c0c6c9a010..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/cyclone.png b/WebHostLib/static/static/icons/sc2/cyclone.png
deleted file mode 100644
index d2016116ea3b..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/cyclone.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/cyclonerangeupgrade.png b/WebHostLib/static/static/icons/sc2/cyclonerangeupgrade.png
deleted file mode 100644
index 351be570d11b..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/cyclonerangeupgrade.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/drillingclaws.png b/WebHostLib/static/static/icons/sc2/drillingclaws.png
deleted file mode 100644
index 2b067a6e44d4..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/drillingclaws.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/emergencythrusters.png b/WebHostLib/static/static/icons/sc2/emergencythrusters.png
deleted file mode 100644
index 159fba37c903..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/emergencythrusters.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/hellionbattlemode.png b/WebHostLib/static/static/icons/sc2/hellionbattlemode.png
deleted file mode 100644
index 56bfd98c924c..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/hellionbattlemode.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/high-explosive-spidermine.png b/WebHostLib/static/static/icons/sc2/high-explosive-spidermine.png
deleted file mode 100644
index 40a5991ebb80..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/high-explosive-spidermine.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/hyperflightrotors.png b/WebHostLib/static/static/icons/sc2/hyperflightrotors.png
deleted file mode 100644
index 375325845876..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/hyperflightrotors.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/hyperfluxor.png b/WebHostLib/static/static/icons/sc2/hyperfluxor.png
deleted file mode 100644
index cdd95bb515be..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/hyperfluxor.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/impalerrounds.png b/WebHostLib/static/static/icons/sc2/impalerrounds.png
deleted file mode 100644
index b00e0c475827..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/impalerrounds.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/improvedburstlaser.png b/WebHostLib/static/static/icons/sc2/improvedburstlaser.png
deleted file mode 100644
index 8a48e38e874d..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/improvedburstlaser.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/improvedsiegemode.png b/WebHostLib/static/static/icons/sc2/improvedsiegemode.png
deleted file mode 100644
index f19dad952bb5..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/improvedsiegemode.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/interferencematrix.png b/WebHostLib/static/static/icons/sc2/interferencematrix.png
deleted file mode 100644
index ced928aa57a9..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/interferencematrix.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/internalizedtechmodule.png b/WebHostLib/static/static/icons/sc2/internalizedtechmodule.png
deleted file mode 100644
index e97f3db0d29a..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/internalizedtechmodule.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/jotunboosters.png b/WebHostLib/static/static/icons/sc2/jotunboosters.png
deleted file mode 100644
index 25720306e5c2..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/jotunboosters.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/jumpjets.png b/WebHostLib/static/static/icons/sc2/jumpjets.png
deleted file mode 100644
index dfdfef4052ca..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/jumpjets.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/lasertargetingsystem.png b/WebHostLib/static/static/icons/sc2/lasertargetingsystem.png
deleted file mode 100644
index c57899b270ff..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/lasertargetingsystem.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/liberator.png b/WebHostLib/static/static/icons/sc2/liberator.png
deleted file mode 100644
index 31507be5fe68..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/liberator.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/lockdown.png b/WebHostLib/static/static/icons/sc2/lockdown.png
deleted file mode 100644
index a2e7f5dc3e3f..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/lockdown.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/magfieldaccelerator.png b/WebHostLib/static/static/icons/sc2/magfieldaccelerator.png
deleted file mode 100644
index 0272b4b73892..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/magfieldaccelerator.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/magrailmunitions.png b/WebHostLib/static/static/icons/sc2/magrailmunitions.png
deleted file mode 100644
index ec303498ccdb..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/magrailmunitions.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/medivacemergencythrusters.png b/WebHostLib/static/static/icons/sc2/medivacemergencythrusters.png
deleted file mode 100644
index 1c7ce9d6ab1a..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/medivacemergencythrusters.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/neosteelfortifiedarmor.png b/WebHostLib/static/static/icons/sc2/neosteelfortifiedarmor.png
deleted file mode 100644
index 04d68d35dc46..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/neosteelfortifiedarmor.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/opticalflare.png b/WebHostLib/static/static/icons/sc2/opticalflare.png
deleted file mode 100644
index f888fd518b99..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/opticalflare.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/optimizedlogistics.png b/WebHostLib/static/static/icons/sc2/optimizedlogistics.png
deleted file mode 100644
index dcf5fd72da86..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/optimizedlogistics.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/reapercombatdrugs.png b/WebHostLib/static/static/icons/sc2/reapercombatdrugs.png
deleted file mode 100644
index b9f2f055c265..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/reapercombatdrugs.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/restoration.png b/WebHostLib/static/static/icons/sc2/restoration.png
deleted file mode 100644
index f5c94e1aeefd..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/restoration.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/ripwavemissiles.png b/WebHostLib/static/static/icons/sc2/ripwavemissiles.png
deleted file mode 100644
index f68e82039765..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/ripwavemissiles.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/shreddermissile.png b/WebHostLib/static/static/icons/sc2/shreddermissile.png
deleted file mode 100644
index 40899095fe3a..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/shreddermissile.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/siegetank-spidermines.png b/WebHostLib/static/static/icons/sc2/siegetank-spidermines.png
deleted file mode 100644
index 1b9f8cf06097..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/siegetank-spidermines.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/siegetankrange.png b/WebHostLib/static/static/icons/sc2/siegetankrange.png
deleted file mode 100644
index 5aef00a656c9..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/siegetankrange.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/specialordance.png b/WebHostLib/static/static/icons/sc2/specialordance.png
deleted file mode 100644
index 4f7410d7ca9e..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/specialordance.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/spidermine.png b/WebHostLib/static/static/icons/sc2/spidermine.png
deleted file mode 100644
index bb39cf0bf8ce..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/spidermine.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/staticempblast.png b/WebHostLib/static/static/icons/sc2/staticempblast.png
deleted file mode 100644
index 38f361510775..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/staticempblast.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/superstimpack.png b/WebHostLib/static/static/icons/sc2/superstimpack.png
deleted file mode 100644
index 0fba8ce5749a..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/superstimpack.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/targetingoptics.png b/WebHostLib/static/static/icons/sc2/targetingoptics.png
deleted file mode 100644
index 057a40f08e30..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/targetingoptics.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/terran-cloak-color.png b/WebHostLib/static/static/icons/sc2/terran-cloak-color.png
deleted file mode 100644
index 44d1bb9541fb..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/terran-cloak-color.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/terran-emp-color.png b/WebHostLib/static/static/icons/sc2/terran-emp-color.png
deleted file mode 100644
index 972b828c75e2..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/terran-emp-color.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/terrandefendermodestructureattack.png b/WebHostLib/static/static/icons/sc2/terrandefendermodestructureattack.png
deleted file mode 100644
index 9d5982655183..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/terrandefendermodestructureattack.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/thorsiegemode.png b/WebHostLib/static/static/icons/sc2/thorsiegemode.png
deleted file mode 100644
index a298fb57de5a..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/thorsiegemode.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/transformationservos.png b/WebHostLib/static/static/icons/sc2/transformationservos.png
deleted file mode 100644
index f7f0524ac15c..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/transformationservos.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/valkyrie.png b/WebHostLib/static/static/icons/sc2/valkyrie.png
deleted file mode 100644
index 9cbf339b10db..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/valkyrie.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/warpjump.png b/WebHostLib/static/static/icons/sc2/warpjump.png
deleted file mode 100644
index ff0a7b1af4aa..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/warpjump.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/widowmine-attackrange.png b/WebHostLib/static/static/icons/sc2/widowmine-attackrange.png
deleted file mode 100644
index 8f5e09c6a593..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/widowmine-attackrange.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png b/WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png
deleted file mode 100644
index 7097db05e6c0..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/widowmine.png b/WebHostLib/static/static/icons/sc2/widowmine.png
deleted file mode 100644
index 802c49a83d88..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/widowmine.png and /dev/null differ
diff --git a/WebHostLib/static/static/icons/sc2/widowminehidden.png b/WebHostLib/static/static/icons/sc2/widowminehidden.png
deleted file mode 100644
index e568742e8a50..000000000000
Binary files a/WebHostLib/static/static/icons/sc2/widowminehidden.png and /dev/null differ
diff --git a/WebHostLib/static/styles/globalStyles.css b/WebHostLib/static/styles/globalStyles.css
index a787b0c6570a..1a0144830e7c 100644
--- a/WebHostLib/static/styles/globalStyles.css
+++ b/WebHostLib/static/styles/globalStyles.css
@@ -44,7 +44,7 @@ a{
font-family: LexendDeca-Regular, sans-serif;
}
-button{
+button, input[type=submit]{
font-weight: 500;
font-size: 0.9rem;
padding: 10px 17px 11px 16px; /* top right bottom left */
@@ -57,7 +57,7 @@ button{
cursor: pointer;
}
-button:active{
+button:active, input[type=submit]:active{
border-right: 1px solid rgba(0, 0, 0, 0.5);
border-bottom: 1px solid rgba(0, 0, 0, 0.5);
padding-right: 16px;
@@ -66,11 +66,11 @@ button:active{
margin-bottom: 2px;
}
-button.button-grass{
+button.button-grass, input[type=submit].button-grass{
border: 1px solid black;
}
-button.button-dirt{
+button.button-dirt, input[type=submit].button-dirt{
border: 1px solid black;
}
@@ -111,4 +111,4 @@ h5, h6{
.interactive{
color: #ffef00;
-}
\ No newline at end of file
+}
diff --git a/WebHostLib/static/styles/lttp-tracker.css b/WebHostLib/static/styles/lttp-tracker.css
deleted file mode 100644
index 899a8f695925..000000000000
--- a/WebHostLib/static/styles/lttp-tracker.css
+++ /dev/null
@@ -1,75 +0,0 @@
-#player-tracker-wrapper{
- margin: 0;
- font-family: LexendDeca-Light, sans-serif;
- color: white;
- font-size: 14px;
-}
-
-#inventory-table{
- border-top: 2px solid #000000;
- border-left: 2px solid #000000;
- border-right: 2px solid #000000;
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- padding: 3px 3px 10px;
- width: 284px;
- background-color: #42b149;
-}
-
-#inventory-table td{
- width: 40px;
- height: 40px;
- text-align: center;
- vertical-align: middle;
-}
-
-#inventory-table img{
- height: 100%;
- max-width: 40px;
- max-height: 40px;
- filter: grayscale(100%) contrast(75%) brightness(75%);
-}
-
-#inventory-table img.acquired{
- filter: none;
-}
-
-#inventory-table img.powder-fix{
- width: 35px;
- height: 35px;
-}
-
-#location-table{
- width: 284px;
- border-left: 2px solid #000000;
- border-right: 2px solid #000000;
- border-bottom: 2px solid #000000;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- background-color: #42b149;
- padding: 0 3px 3px;
-}
-
-#location-table th{
- vertical-align: middle;
- text-align: center;
- padding-right: 10px;
-}
-
-#location-table td{
- padding-top: 2px;
- padding-bottom: 2px;
- padding-right: 5px;
- line-height: 20px;
-}
-
-#location-table td.counter{
- padding-right: 8px;
- text-align: right;
-}
-
-#location-table img{
- height: 100%;
- max-width: 30px;
- max-height: 30px;
-}
diff --git a/WebHostLib/static/styles/markdown.css b/WebHostLib/static/styles/markdown.css
index dce135588e5f..e0165b7489ef 100644
--- a/WebHostLib/static/styles/markdown.css
+++ b/WebHostLib/static/styles/markdown.css
@@ -23,7 +23,7 @@
.markdown a{}
-.markdown h1{
+.markdown h1, .markdown details summary.h1{
font-size: 52px;
font-weight: normal;
font-family: LondrinaSolid-Regular, sans-serif;
@@ -33,7 +33,7 @@
text-shadow: 1px 1px 4px #000000;
}
-.markdown h2{
+.markdown h2, .markdown details summary.h2{
font-size: 38px;
font-weight: normal;
font-family: LondrinaSolid-Light, sans-serif;
@@ -45,7 +45,7 @@
text-shadow: 1px 1px 2px #000000;
}
-.markdown h3{
+.markdown h3, .markdown details summary.h3{
font-size: 26px;
font-family: LexendDeca-Regular, sans-serif;
text-transform: none;
@@ -55,7 +55,7 @@
margin-bottom: 0.5rem;
}
-.markdown h4{
+.markdown h4, .markdown details summary.h4{
font-family: LexendDeca-Regular, sans-serif;
text-transform: none;
font-size: 24px;
@@ -63,21 +63,21 @@
margin-bottom: 24px;
}
-.markdown h5{
+.markdown h5, .markdown details summary.h5{
font-family: LexendDeca-Regular, sans-serif;
text-transform: none;
font-size: 22px;
cursor: pointer;
}
-.markdown h6{
+.markdown h6, .markdown details summary.h6{
font-family: LexendDeca-Regular, sans-serif;
text-transform: none;
font-size: 20px;
cursor: pointer;;
}
-.markdown h4, .markdown h5,.markdown h6{
+.markdown h4, .markdown h5, .markdown h6{
margin-bottom: 0.5rem;
}
diff --git a/WebHostLib/static/styles/player-options.css b/WebHostLib/static/styles/player-options.css
deleted file mode 100644
index cc2d5e2de5ce..000000000000
--- a/WebHostLib/static/styles/player-options.css
+++ /dev/null
@@ -1,244 +0,0 @@
-html{
- background-image: url('../static/backgrounds/grass.png');
- background-repeat: repeat;
- background-size: 650px 650px;
-}
-
-#player-options{
- box-sizing: border-box;
- max-width: 1024px;
- margin-left: auto;
- margin-right: auto;
- background-color: rgba(0, 0, 0, 0.15);
- border-radius: 8px;
- padding: 1rem;
- color: #eeffeb;
-}
-
-#player-options #player-options-button-row{
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- margin-top: 15px;
-}
-
-#player-options code{
- background-color: #d9cd8e;
- border-radius: 4px;
- padding-left: 0.25rem;
- padding-right: 0.25rem;
- color: #000000;
-}
-
-#player-options #user-message{
- display: none;
- width: calc(100% - 8px);
- background-color: #ffe86b;
- border-radius: 4px;
- color: #000000;
- padding: 4px;
- text-align: center;
-}
-
-#player-options #user-message.visible{
- display: block;
- cursor: pointer;
-}
-
-#player-options h1{
- font-size: 2.5rem;
- font-weight: normal;
- width: 100%;
- margin-bottom: 0.5rem;
- text-shadow: 1px 1px 4px #000000;
-}
-
-#player-options h2{
- font-size: 40px;
- font-weight: normal;
- width: 100%;
- margin-bottom: 0.5rem;
- text-transform: lowercase;
- text-shadow: 1px 1px 2px #000000;
-}
-
-#player-options h3, #player-options h4, #player-options h5, #player-options h6{
- text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
-}
-
-#player-options input:not([type]){
- border: 1px solid #000000;
- padding: 3px;
- border-radius: 3px;
- min-width: 150px;
-}
-
-#player-options input:not([type]):focus{
- border: 1px solid #ffffff;
-}
-
-#player-options select{
- border: 1px solid #000000;
- padding: 3px;
- border-radius: 3px;
- min-width: 150px;
- background-color: #ffffff;
-}
-
-#player-options #game-options, #player-options #rom-options{
- display: flex;
- flex-direction: row;
-}
-
-#player-options #meta-options {
- display: flex;
- justify-content: space-between;
- gap: 20px;
- padding: 3px;
-}
-
-#player-options div {
- display: flex;
- flex-grow: 1;
-}
-
-#player-options #meta-options label {
- display: inline-block;
- min-width: 180px;
- flex-grow: 1;
-}
-
-#player-options #meta-options input,
-#player-options #meta-options select {
- box-sizing: border-box;
- min-width: 150px;
- width: 50%;
-}
-
-#player-options .left, #player-options .right{
- flex-grow: 1;
-}
-
-#player-options .left{
- margin-right: 10px;
-}
-
-#player-options .right{
- margin-left: 10px;
-}
-
-#player-options table{
- margin-bottom: 30px;
- width: 100%;
-}
-
-#player-options table .select-container{
- display: flex;
- flex-direction: row;
-}
-
-#player-options table .select-container select{
- min-width: 200px;
- flex-grow: 1;
-}
-
-#player-options table select:disabled{
- background-color: lightgray;
-}
-
-#player-options table .range-container{
- display: flex;
- flex-direction: row;
-}
-
-#player-options table .range-container input[type=range]{
- flex-grow: 1;
-}
-
-#player-options table .range-value{
- min-width: 20px;
- margin-left: 0.25rem;
-}
-
-#player-options table .named-range-container{
- display: flex;
- flex-direction: column;
-}
-
-#player-options table .named-range-wrapper{
- display: flex;
- flex-direction: row;
- margin-top: 0.25rem;
-}
-
-#player-options table .named-range-wrapper input[type=range]{
- flex-grow: 1;
-}
-
-#player-options table .randomize-button {
- max-height: 24px;
- line-height: 16px;
- padding: 2px 8px;
- margin: 0 0 0 0.25rem;
- font-size: 12px;
- border: 1px solid black;
- border-radius: 3px;
-}
-
-#player-options table .randomize-button.active {
- background-color: #ffef00; /* Same as .interactive in globalStyles.css */
-}
-
-#player-options table .randomize-button[data-tooltip]::after {
- left: unset;
- right: 0;
-}
-
-#player-options table label{
- display: block;
- min-width: 200px;
- margin-right: 4px;
- cursor: default;
-}
-
-#player-options th, #player-options td{
- border: none;
- padding: 3px;
- font-size: 17px;
- vertical-align: top;
-}
-
-@media all and (max-width: 1024px) {
- #player-options {
- border-radius: 0;
- }
-
- #player-options #meta-options {
- flex-direction: column;
- justify-content: flex-start;
- gap: 6px;
- }
-
- #player-options #game-options{
- justify-content: flex-start;
- flex-wrap: wrap;
- }
-
- #player-options .left,
- #player-options .right {
- margin: 0;
- }
-
- #game-options table {
- margin-bottom: 0;
- }
-
- #game-options table label{
- display: block;
- min-width: 200px;
- }
-
- #game-options table tr td {
- width: 50%;
- }
-}
diff --git a/WebHostLib/static/styles/playerOptions/playerOptions.css b/WebHostLib/static/styles/playerOptions/playerOptions.css
new file mode 100644
index 000000000000..56c9263d3330
--- /dev/null
+++ b/WebHostLib/static/styles/playerOptions/playerOptions.css
@@ -0,0 +1,310 @@
+@import "../markdown.css";
+html {
+ background-image: url("../../static/backgrounds/grass.png");
+ background-repeat: repeat;
+ background-size: 650px 650px;
+ overflow-x: hidden;
+}
+
+#player-options {
+ box-sizing: border-box;
+ max-width: 1024px;
+ margin-left: auto;
+ margin-right: auto;
+ background-color: rgba(0, 0, 0, 0.15);
+ border-radius: 8px;
+ padding: 1rem;
+ color: #eeffeb;
+ word-break: break-word;
+}
+#player-options #player-options-header h1 {
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+#player-options #player-options-header h1:nth-child(2) {
+ font-size: 1.4rem;
+ margin-top: -8px;
+ margin-bottom: 0.5rem;
+}
+#player-options .js-warning-banner {
+ width: calc(100% - 1rem);
+ padding: 0.5rem;
+ border-radius: 4px;
+ background-color: #f3f309;
+ color: #000000;
+ margin-bottom: 0.5rem;
+ text-align: center;
+}
+#player-options .group-container {
+ padding: 0;
+ margin: 0;
+}
+#player-options .group-container h2 {
+ user-select: none;
+ cursor: unset;
+}
+#player-options .group-container h2 label {
+ cursor: pointer;
+}
+#player-options #player-options-button-row {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin-top: 15px;
+}
+#player-options #user-message {
+ display: none;
+ width: calc(100% - 8px);
+ background-color: #ffe86b;
+ border-radius: 4px;
+ color: #000000;
+ padding: 4px;
+ text-align: center;
+ cursor: pointer;
+}
+#player-options h1 {
+ font-size: 2.5rem;
+ font-weight: normal;
+ width: 100%;
+ margin-bottom: 0.5rem;
+ text-shadow: 1px 1px 4px #000000;
+}
+#player-options h2 {
+ font-size: 40px;
+ font-weight: normal;
+ width: 100%;
+ margin-bottom: 0.5rem;
+ text-transform: lowercase;
+ text-shadow: 1px 1px 2px #000000;
+}
+#player-options h3, #player-options h4, #player-options h5, #player-options h6 {
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
+}
+#player-options input:not([type]) {
+ border: 1px solid #000000;
+ padding: 3px;
+ border-radius: 3px;
+ min-width: 150px;
+}
+#player-options input:not([type]):focus {
+ border: 1px solid #ffffff;
+}
+#player-options select {
+ border: 1px solid #000000;
+ padding: 3px;
+ border-radius: 3px;
+ min-width: 150px;
+ background-color: #ffffff;
+ text-overflow: ellipsis;
+}
+#player-options .game-options {
+ display: flex;
+ flex-direction: row;
+}
+#player-options .game-options .left, #player-options .game-options .right {
+ display: grid;
+ grid-template-columns: 12rem auto;
+ grid-row-gap: 0.5rem;
+ grid-auto-rows: min-content;
+ align-items: start;
+ min-width: 480px;
+ width: 50%;
+}
+#player-options #meta-options {
+ display: flex;
+ justify-content: space-between;
+ gap: 20px;
+ padding: 3px;
+}
+#player-options #meta-options input, #player-options #meta-options select {
+ box-sizing: border-box;
+ width: 200px;
+}
+#player-options .left, #player-options .right {
+ flex-grow: 1;
+ margin-bottom: 0.5rem;
+}
+#player-options .left {
+ margin-right: 20px;
+}
+#player-options .select-container {
+ display: flex;
+ flex-direction: row;
+ max-width: 270px;
+}
+#player-options .select-container select {
+ min-width: 200px;
+ flex-grow: 1;
+}
+#player-options .select-container select:disabled {
+ background-color: lightgray;
+}
+#player-options .range-container {
+ display: flex;
+ flex-direction: row;
+ max-width: 270px;
+}
+#player-options .range-container input[type=range] {
+ flex-grow: 1;
+}
+#player-options .range-container .range-value {
+ min-width: 20px;
+ margin-left: 0.25rem;
+}
+#player-options .named-range-container {
+ display: flex;
+ flex-direction: column;
+ max-width: 270px;
+}
+#player-options .named-range-container .named-range-wrapper {
+ display: flex;
+ flex-direction: row;
+ margin-top: 0.25rem;
+}
+#player-options .named-range-container .named-range-wrapper input[type=range] {
+ flex-grow: 1;
+}
+#player-options .free-text-container {
+ display: flex;
+ flex-direction: column;
+ max-width: 270px;
+}
+#player-options .free-text-container input[type=text] {
+ flex-grow: 1;
+}
+#player-options .text-choice-container {
+ display: flex;
+ flex-direction: column;
+ max-width: 270px;
+}
+#player-options .text-choice-container .text-choice-wrapper {
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 0.25rem;
+}
+#player-options .text-choice-container .text-choice-wrapper select {
+ flex-grow: 1;
+}
+#player-options .option-container {
+ display: flex;
+ flex-direction: column;
+ background-color: rgba(0, 0, 0, 0.25);
+ border: 1px solid rgba(20, 20, 20, 0.25);
+ border-radius: 3px;
+ color: #ffffff;
+ max-height: 10rem;
+ min-width: 14.5rem;
+ overflow-y: auto;
+ padding-right: 0.25rem;
+ padding-left: 0.25rem;
+}
+#player-options .option-container .option-divider {
+ width: 100%;
+ height: 2px;
+ background-color: rgba(20, 20, 20, 0.25);
+ margin-top: 0.125rem;
+ margin-bottom: 0.125rem;
+}
+#player-options .option-container .option-entry {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ margin-bottom: 0.125rem;
+ margin-top: 0.125rem;
+ user-select: none;
+}
+#player-options .option-container .option-entry:hover {
+ background-color: rgba(20, 20, 20, 0.25);
+}
+#player-options .option-container .option-entry input[type=checkbox] {
+ margin-right: 0.25rem;
+}
+#player-options .option-container .option-entry input[type=number] {
+ max-width: 1.5rem;
+ max-height: 1rem;
+ margin-left: 0.125rem;
+ text-align: center;
+ /* Hide arrows on input[type=number] fields */
+ -moz-appearance: textfield;
+}
+#player-options .option-container .option-entry input[type=number]::-webkit-outer-spin-button, #player-options .option-container .option-entry input[type=number]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+#player-options .option-container .option-entry label {
+ flex-grow: 1;
+ margin-right: 0;
+ min-width: unset;
+ display: unset;
+}
+#player-options .randomize-button {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ height: 22px;
+ max-width: 30px;
+ margin: 0 0 0 0.25rem;
+ font-size: 14px;
+ border: 1px solid black;
+ border-radius: 3px;
+ background-color: #d3d3d3;
+ user-select: none;
+}
+#player-options .randomize-button:hover {
+ background-color: #c0c0c0;
+ cursor: pointer;
+}
+#player-options .randomize-button label {
+ line-height: 22px;
+ padding-left: 5px;
+ padding-right: 2px;
+ margin-right: 4px;
+ width: 100%;
+ height: 100%;
+ min-width: unset;
+}
+#player-options .randomize-button label:hover {
+ cursor: pointer;
+}
+#player-options .randomize-button input[type=checkbox] {
+ display: none;
+}
+#player-options .randomize-button:has(input[type=checkbox]:checked) {
+ background-color: #ffef00; /* Same as .interactive in globalStyles.css */
+}
+#player-options .randomize-button:has(input[type=checkbox]:checked):hover {
+ background-color: #eedd27;
+}
+#player-options .randomize-button[data-tooltip]::after {
+ left: unset;
+ right: 0;
+}
+#player-options label {
+ display: block;
+ margin-right: 4px;
+ cursor: default;
+ word-break: break-word;
+}
+#player-options th, #player-options td {
+ border: none;
+ padding: 3px;
+ font-size: 17px;
+ vertical-align: top;
+}
+
+@media all and (max-width: 1024px) {
+ #player-options {
+ border-radius: 0;
+ }
+ #player-options #meta-options {
+ flex-direction: column;
+ justify-content: flex-start;
+ gap: 6px;
+ }
+ #player-options .game-options {
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ }
+}
+
+/*# sourceMappingURL=playerOptions.css.map */
diff --git a/WebHostLib/static/styles/playerOptions/playerOptions.css.map b/WebHostLib/static/styles/playerOptions/playerOptions.css.map
new file mode 100644
index 000000000000..6797b88c7bfe
--- /dev/null
+++ b/WebHostLib/static/styles/playerOptions/playerOptions.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["playerOptions.scss"],"names":[],"mappings":"AAAQ;AAER;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGI;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;;AAEA;EACI;;AAKZ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;;AAEA;EACI;EACA;;AAEA;EACI;;AAKZ;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAEA;EACI;;AAKZ;EACI;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAEA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;AAEA;EACA;;AACA;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACI;;AAIR;EACI;;AAGJ;EACI;;AAEA;EACI;;AAIR;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;;AAIR;EACI;IACI;;EAEA;IACI;IACA;IACA;;EAGJ;IACI;IACA","file":"playerOptions.css"}
\ No newline at end of file
diff --git a/WebHostLib/static/styles/playerOptions/playerOptions.scss b/WebHostLib/static/styles/playerOptions/playerOptions.scss
new file mode 100644
index 000000000000..06bde759d263
--- /dev/null
+++ b/WebHostLib/static/styles/playerOptions/playerOptions.scss
@@ -0,0 +1,364 @@
+@import "../markdown.css";
+
+html{
+ background-image: url('../../static/backgrounds/grass.png');
+ background-repeat: repeat;
+ background-size: 650px 650px;
+ overflow-x: hidden;
+}
+
+#player-options{
+ box-sizing: border-box;
+ max-width: 1024px;
+ margin-left: auto;
+ margin-right: auto;
+ background-color: rgba(0, 0, 0, 0.15);
+ border-radius: 8px;
+ padding: 1rem;
+ color: #eeffeb;
+ word-break: break-word;
+
+ #player-options-header{
+ h1{
+ margin-bottom: 0;
+ padding-bottom: 0;
+ }
+
+ h1:nth-child(2){
+ font-size: 1.4rem;
+ margin-top: -8px;
+ margin-bottom: 0.5rem;
+ }
+ }
+
+ .js-warning-banner{
+ width: calc(100% - 1rem);
+ padding: 0.5rem;
+ border-radius: 4px;
+ background-color: #f3f309;
+ color: #000000;
+ margin-bottom: 0.5rem;
+ text-align: center;
+ }
+
+ .group-container{
+ padding: 0;
+ margin: 0;
+
+ h2{
+ user-select: none;
+ cursor: unset;
+
+ label{
+ cursor: pointer;
+ }
+ }
+ }
+
+ #player-options-button-row{
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin-top: 15px;
+ }
+
+ #user-message{
+ display: none;
+ width: calc(100% - 8px);
+ background-color: #ffe86b;
+ border-radius: 4px;
+ color: #000000;
+ padding: 4px;
+ text-align: center;
+ cursor: pointer;
+ }
+
+ h1{
+ font-size: 2.5rem;
+ font-weight: normal;
+ width: 100%;
+ margin-bottom: 0.5rem;
+ text-shadow: 1px 1px 4px #000000;
+ }
+
+ h2{
+ font-size: 40px;
+ font-weight: normal;
+ width: 100%;
+ margin-bottom: 0.5rem;
+ text-transform: lowercase;
+ text-shadow: 1px 1px 2px #000000;
+ }
+
+ h3, h4, h5, h6{
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
+ }
+
+ input:not([type]){
+ border: 1px solid #000000;
+ padding: 3px;
+ border-radius: 3px;
+ min-width: 150px;
+
+ &:focus{
+ border: 1px solid #ffffff;
+ }
+ }
+
+ select{
+ border: 1px solid #000000;
+ padding: 3px;
+ border-radius: 3px;
+ min-width: 150px;
+ background-color: #ffffff;
+ text-overflow: ellipsis;
+ }
+
+ .game-options{
+ display: flex;
+ flex-direction: row;
+
+ .left, .right{
+ display: grid;
+ grid-template-columns: 12rem auto;
+ grid-row-gap: 0.5rem;
+ grid-auto-rows: min-content;
+ align-items: start;
+ min-width: 480px;
+ width: 50%;
+ }
+ }
+
+ #meta-options{
+ display: flex;
+ justify-content: space-between;
+ gap: 20px;
+ padding: 3px;
+
+ input, select{
+ box-sizing: border-box;
+ width: 200px;
+ }
+ }
+
+ .left, .right{
+ flex-grow: 1;
+ margin-bottom: 0.5rem;
+ }
+
+ .left{
+ margin-right: 20px;
+ }
+
+ .select-container{
+ display: flex;
+ flex-direction: row;
+ max-width: 270px;
+
+ select{
+ min-width: 200px;
+ flex-grow: 1;
+
+ &:disabled{
+ background-color: lightgray;
+ }
+ }
+ }
+
+ .range-container{
+ display: flex;
+ flex-direction: row;
+ max-width: 270px;
+
+ input[type=range]{
+ flex-grow: 1;
+ }
+
+ .range-value{
+ min-width: 20px;
+ margin-left: 0.25rem;
+ }
+ }
+
+ .named-range-container{
+ display: flex;
+ flex-direction: column;
+ max-width: 270px;
+
+ .named-range-wrapper{
+ display: flex;
+ flex-direction: row;
+ margin-top: 0.25rem;
+
+ input[type=range]{
+ flex-grow: 1;
+ }
+ }
+ }
+
+ .free-text-container{
+ display: flex;
+ flex-direction: column;
+ max-width: 270px;
+
+ input[type=text]{
+ flex-grow: 1;
+ }
+ }
+
+ .text-choice-container{
+ display: flex;
+ flex-direction: column;
+ max-width: 270px;
+
+ .text-choice-wrapper{
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 0.25rem;
+
+ select{
+ flex-grow: 1;
+ }
+ }
+ }
+
+ .option-container{
+ display: flex;
+ flex-direction: column;
+ background-color: rgba(0, 0, 0, 0.25);
+ border: 1px solid rgba(20, 20, 20, 0.25);
+ border-radius: 3px;
+ color: #ffffff;
+ max-height: 10rem;
+ min-width: 14.5rem;
+ overflow-y: auto;
+ padding-right: 0.25rem;
+ padding-left: 0.25rem;
+
+ .option-divider{
+ width: 100%;
+ height: 2px;
+ background-color: rgba(20, 20, 20, 0.25);
+ margin-top: 0.125rem;
+ margin-bottom: 0.125rem;
+ }
+
+ .option-entry{
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ margin-bottom: 0.125rem;
+ margin-top: 0.125rem;
+ user-select: none;
+
+ &:hover{
+ background-color: rgba(20, 20, 20, 0.25);
+ }
+
+ input[type=checkbox]{
+ margin-right: 0.25rem;
+ }
+
+ input[type=number]{
+ max-width: 1.5rem;
+ max-height: 1rem;
+ margin-left: 0.125rem;
+ text-align: center;
+
+ /* Hide arrows on input[type=number] fields */
+ -moz-appearance: textfield;
+ &::-webkit-outer-spin-button, &::-webkit-inner-spin-button{
+ -webkit-appearance: none;
+ margin: 0;
+ }
+ }
+
+ label{
+ flex-grow: 1;
+ margin-right: 0;
+ min-width: unset;
+ display: unset;
+ }
+ }
+ }
+
+ .randomize-button{
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ height: 22px;
+ max-width: 30px;
+ margin: 0 0 0 0.25rem;
+ font-size: 14px;
+ border: 1px solid black;
+ border-radius: 3px;
+ background-color: #d3d3d3;
+ user-select: none;
+
+ &:hover{
+ background-color: #c0c0c0;
+ cursor: pointer;
+ }
+
+ label{
+ line-height: 22px;
+ padding-left: 5px;
+ padding-right: 2px;
+ margin-right: 4px;
+ width: 100%;
+ height: 100%;
+ min-width: unset;
+ &:hover{
+ cursor: pointer;
+ }
+ }
+
+ input[type=checkbox]{
+ display: none;
+ }
+
+ &:has(input[type=checkbox]:checked){
+ background-color: #ffef00; /* Same as .interactive in globalStyles.css */
+
+ &:hover{
+ background-color: #eedd27;
+ }
+ }
+
+ &[data-tooltip]::after{
+ left: unset;
+ right: 0;
+ }
+ }
+
+ label{
+ display: block;
+ margin-right: 4px;
+ cursor: default;
+ word-break: break-word;
+ }
+
+ th, td{
+ border: none;
+ padding: 3px;
+ font-size: 17px;
+ vertical-align: top;
+ }
+}
+
+@media all and (max-width: 1024px) {
+ #player-options {
+ border-radius: 0;
+
+ #meta-options {
+ flex-direction: column;
+ justify-content: flex-start;
+ gap: 6px;
+ }
+
+ .game-options{
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ }
+ }
+}
diff --git a/WebHostLib/static/styles/sc2Tracker.css b/WebHostLib/static/styles/sc2Tracker.css
new file mode 100644
index 000000000000..29a719a110c8
--- /dev/null
+++ b/WebHostLib/static/styles/sc2Tracker.css
@@ -0,0 +1,160 @@
+#player-tracker-wrapper{
+ margin: 0;
+}
+
+#tracker-table td {
+ vertical-align: top;
+}
+
+.inventory-table-area{
+ border: 2px solid #000000;
+ border-radius: 4px;
+ padding: 3px 10px 3px 10px;
+}
+
+.inventory-table-area:has(.inventory-table-terran) {
+ width: 690px;
+ background-color: #525494;
+}
+
+.inventory-table-area:has(.inventory-table-zerg) {
+ width: 360px;
+ background-color: #9d60d2;
+}
+
+.inventory-table-area:has(.inventory-table-protoss) {
+ width: 400px;
+ background-color: #d2b260;
+}
+
+#tracker-table .inventory-table td{
+ width: 40px;
+ height: 40px;
+ text-align: center;
+ vertical-align: middle;
+}
+
+.inventory-table td.title{
+ padding-top: 10px;
+ height: 20px;
+ font-family: "JuraBook", monospace;
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.inventory-table img{
+ height: 100%;
+ max-width: 40px;
+ max-height: 40px;
+ border: 1px solid #000000;
+ filter: grayscale(100%) contrast(75%) brightness(20%);
+ background-color: black;
+}
+
+.inventory-table img.acquired{
+ filter: none;
+ background-color: black;
+}
+
+.inventory-table .tint-terran img.acquired {
+ filter: sepia(100%) saturate(300%) brightness(130%) hue-rotate(120deg)
+}
+
+.inventory-table .tint-protoss img.acquired {
+ filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(180deg)
+}
+
+.inventory-table .tint-level-1 img.acquired {
+ filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg)
+}
+
+.inventory-table .tint-level-2 img.acquired {
+ filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) hue-rotate(120deg)
+}
+
+.inventory-table .tint-level-3 img.acquired {
+ filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) hue-rotate(240deg)
+}
+
+.inventory-table div.counted-item {
+ position: relative;
+}
+
+.inventory-table div.item-count {
+ width: 160px;
+ text-align: left;
+ color: black;
+ font-family: "JuraBook", monospace;
+ font-weight: bold;
+}
+
+#location-table{
+ border: 2px solid #000000;
+ border-radius: 4px;
+ background-color: #87b678;
+ padding: 10px 3px 3px;
+ font-family: "JuraBook", monospace;
+ font-size: 16px;
+ font-weight: bold;
+ cursor: default;
+}
+
+#location-table table{
+ width: 100%;
+}
+
+#location-table th{
+ vertical-align: middle;
+ text-align: left;
+ padding-right: 10px;
+}
+
+#location-table td{
+ padding-top: 2px;
+ padding-bottom: 2px;
+ line-height: 20px;
+}
+
+#location-table td.counter {
+ text-align: right;
+ font-size: 14px;
+}
+
+#location-table td.toggle-arrow {
+ text-align: right;
+}
+
+#location-table tr#Total-header {
+ font-weight: bold;
+}
+
+#location-table img{
+ height: 100%;
+ max-width: 30px;
+ max-height: 30px;
+}
+
+#location-table tbody.locations {
+ font-size: 16px;
+}
+
+#location-table td.location-name {
+ padding-left: 16px;
+}
+
+#location-table td:has(.location-column) {
+ vertical-align: top;
+}
+
+#location-table .location-column {
+ width: 100%;
+ height: 100%;
+}
+
+#location-table .location-column .spacer {
+ min-height: 24px;
+}
+
+.hide {
+ display: none;
+}
diff --git a/WebHostLib/static/styles/sc2wolTracker.css b/WebHostLib/static/styles/sc2wolTracker.css
deleted file mode 100644
index a7d8bd28c4f8..000000000000
--- a/WebHostLib/static/styles/sc2wolTracker.css
+++ /dev/null
@@ -1,112 +0,0 @@
-#player-tracker-wrapper{
- margin: 0;
-}
-
-#inventory-table{
- border-top: 2px solid #000000;
- border-left: 2px solid #000000;
- border-right: 2px solid #000000;
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- padding: 3px 3px 10px;
- width: 710px;
- background-color: #525494;
-}
-
-#inventory-table td{
- width: 40px;
- height: 40px;
- text-align: center;
- vertical-align: middle;
-}
-
-#inventory-table td.title{
- padding-top: 10px;
- height: 20px;
- font-family: "JuraBook", monospace;
- font-size: 16px;
- font-weight: bold;
-}
-
-#inventory-table img{
- height: 100%;
- max-width: 40px;
- max-height: 40px;
- border: 1px solid #000000;
- filter: grayscale(100%) contrast(75%) brightness(20%);
- background-color: black;
-}
-
-#inventory-table img.acquired{
- filter: none;
- background-color: black;
-}
-
-#inventory-table div.counted-item {
- position: relative;
-}
-
-#inventory-table div.item-count {
- text-align: left;
- color: black;
- font-family: "JuraBook", monospace;
- font-weight: bold;
-}
-
-#location-table{
- width: 710px;
- border-left: 2px solid #000000;
- border-right: 2px solid #000000;
- border-bottom: 2px solid #000000;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- background-color: #525494;
- padding: 10px 3px 3px;
- font-family: "JuraBook", monospace;
- font-size: 16px;
- font-weight: bold;
- cursor: default;
-}
-
-#location-table th{
- vertical-align: middle;
- text-align: left;
- padding-right: 10px;
-}
-
-#location-table td{
- padding-top: 2px;
- padding-bottom: 2px;
- line-height: 20px;
-}
-
-#location-table td.counter {
- text-align: right;
- font-size: 14px;
-}
-
-#location-table td.toggle-arrow {
- text-align: right;
-}
-
-#location-table tr#Total-header {
- font-weight: bold;
-}
-
-#location-table img{
- height: 100%;
- max-width: 30px;
- max-height: 30px;
-}
-
-#location-table tbody.locations {
- font-size: 16px;
-}
-
-#location-table td.location-name {
- padding-left: 16px;
-}
-
-.hide {
- display: none;
-}
diff --git a/WebHostLib/static/styles/supportedGames.css b/WebHostLib/static/styles/supportedGames.css
index 7396daa95404..ab12f320716b 100644
--- a/WebHostLib/static/styles/supportedGames.css
+++ b/WebHostLib/static/styles/supportedGames.css
@@ -8,30 +8,15 @@
cursor: unset;
}
-#games h1{
+#games h1, #games details summary.h1{
font-size: 60px;
cursor: unset;
}
-#games h2{
+#games h2, #games details summary.h2{
color: #93dcff;
margin-bottom: 2px;
-}
-
-#games .collapse-toggle{
- cursor: pointer;
-}
-
-#games h2 .collapse-arrow{
- font-size: 20px;
- display: inline-block; /* make vertical-align work */
- padding-bottom: 9px;
- vertical-align: middle;
- padding-right: 8px;
-}
-
-#games p.collapsed{
- display: none;
+ text-transform: none;
}
#games a{
diff --git a/WebHostLib/static/styles/tooltip.css b/WebHostLib/static/styles/tooltip.css
index 7cd8463f64a4..dc9026ce6c3d 100644
--- a/WebHostLib/static/styles/tooltip.css
+++ b/WebHostLib/static/styles/tooltip.css
@@ -12,12 +12,12 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
*/
/* Base styles for the element that has a tooltip */
-[data-tooltip], .tooltip {
+[data-tooltip], .tooltip-container {
position: relative;
}
/* Base styles for the entire tooltip */
-[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after {
+[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
position: absolute;
visibility: hidden;
opacity: 0;
@@ -39,13 +39,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
pointer-events: none;
}
-[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip:hover:before, .tooltip:hover:after{
+[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip-container:hover:before,
+.tooltip-container:hover .tooltip {
visibility: visible;
opacity: 1;
+ word-break: break-word;
}
/** Directional arrow styles */
-.tooltip:before, [data-tooltip]:before {
+[data-tooltip]:before, .tooltip-container:before {
z-index: 10000;
border: 6px solid transparent;
background: transparent;
@@ -53,7 +55,7 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
}
/** Content styles */
-.tooltip:after, [data-tooltip]:after {
+[data-tooltip]:after, .tooltip {
width: 260px;
z-index: 10000;
padding: 8px;
@@ -62,24 +64,26 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
background-color: hsla(0, 0%, 20%, 0.9);
color: #fff;
content: attr(data-tooltip);
- white-space: pre-wrap;
font-size: 14px;
line-height: 1.2;
}
-[data-tooltip]:before, [data-tooltip]:after{
+[data-tooltip]:after {
+ white-space: pre-wrap;
+}
+
+[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
visibility: hidden;
opacity: 0;
pointer-events: none;
}
-[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after,
-.tooltip-top:before, .tooltip-top:after {
+[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
bottom: 100%;
left: 50%;
}
-[data-tooltip]:before, .tooltip:before, .tooltip-top:before {
+[data-tooltip]:before, .tooltip-container:before {
margin-left: -6px;
margin-bottom: -12px;
border-top-color: #000;
@@ -87,19 +91,19 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
}
/** Horizontally align tooltips on the top and bottom */
-[data-tooltip]:after, .tooltip:after, .tooltip-top:after {
+[data-tooltip]:after, .tooltip {
margin-left: -80px;
}
-[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip:hover:before, .tooltip:hover:after,
-.tooltip-top:hover:before, .tooltip-top:hover:after {
+[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip-container:hover:before,
+.tooltip-container:hover .tooltip {
-webkit-transform: translateY(-12px);
-moz-transform: translateY(-12px);
transform: translateY(-12px);
}
/** Tooltips on the left */
-.tooltip-left:before, .tooltip-left:after {
+.tooltip-left:before, [data-tooltip].tooltip-left:after, .tooltip-left .tooltip {
right: 100%;
bottom: 50%;
left: auto;
@@ -114,14 +118,14 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
border-left-color: hsla(0, 0%, 20%, 0.9);
}
-.tooltip-left:hover:before, .tooltip-left:hover:after {
+.tooltip-left:hover:before, [data-tooltip].tooltip-left:hover:after, .tooltip-left:hover .tooltip {
-webkit-transform: translateX(-12px);
-moz-transform: translateX(-12px);
transform: translateX(-12px);
}
/** Tooltips on the bottom */
-.tooltip-bottom:before, .tooltip-bottom:after {
+.tooltip-bottom:before, [data-tooltip].tooltip-bottom:after, .tooltip-bottom .tooltip {
top: 100%;
bottom: auto;
left: 50%;
@@ -135,14 +139,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
border-bottom-color: hsla(0, 0%, 20%, 0.9);
}
-.tooltip-bottom:hover:before, .tooltip-bottom:hover:after {
+.tooltip-bottom:hover:before, [data-tooltip].tooltip-bottom:hover:after,
+.tooltip-bottom:hover .tooltip {
-webkit-transform: translateY(12px);
-moz-transform: translateY(12px);
transform: translateY(12px);
}
/** Tooltips on the right */
-.tooltip-right:before, .tooltip-right:after {
+.tooltip-right:before, [data-tooltip].tooltip-right:after, .tooltip-right .tooltip {
bottom: 50%;
left: 100%;
}
@@ -155,7 +160,8 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
border-right-color: hsla(0, 0%, 20%, 0.9);
}
-.tooltip-right:hover:before, .tooltip-right:hover:after {
+.tooltip-right:hover:before, [data-tooltip].tooltip-right:hover:after,
+.tooltip-right:hover .tooltip {
-webkit-transform: translateX(12px);
-moz-transform: translateX(12px);
transform: translateX(12px);
@@ -167,7 +173,16 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
}
/** Center content vertically for tooltips ont he left and right */
-.tooltip-left:after, .tooltip-right:after {
+[data-tooltip].tooltip-left:after, [data-tooltip].tooltip-right:after,
+.tooltip-left .tooltip, .tooltip-right .tooltip {
margin-left: 0;
margin-bottom: -16px;
}
+
+.tooltip ul, .tooltip ol {
+ padding-left: 1rem;
+}
+
+.tooltip :last-child {
+ margin-bottom: 0;
+}
diff --git a/WebHostLib/static/styles/tracker__ALinkToThePast.css b/WebHostLib/static/styles/tracker__ALinkToThePast.css
new file mode 100644
index 000000000000..db5dfcbdfed7
--- /dev/null
+++ b/WebHostLib/static/styles/tracker__ALinkToThePast.css
@@ -0,0 +1,142 @@
+@import url('https://fonts.googleapis.com/css2?family=Lexend+Deca:wght@100..900&display=swap');
+
+.tracker-container {
+ width: 440px;
+ box-sizing: border-box;
+ font-family: "Lexend Deca", Arial, Helvetica, sans-serif;
+ border: 2px solid black;
+ border-radius: 4px;
+ resize: both;
+
+ background-color: #42b149;
+ color: white;
+}
+
+.hidden {
+ visibility: hidden;
+}
+
+/** Inventory Grid ****************************************************************************************************/
+.inventory-grid {
+ display: grid;
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+ padding: 1rem;
+ gap: 1rem;
+}
+
+.inventory-grid .item {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ height: 48px;
+}
+
+.inventory-grid .dual-item {
+ display: flex;
+ justify-content: center;
+}
+
+.inventory-grid .missing {
+ /* Missing items will be in full grayscale to signify "uncollected". */
+ filter: grayscale(100%) contrast(75%) brightness(75%);
+}
+
+.inventory-grid .item img,
+.inventory-grid .dual-item img {
+ display: flex;
+ align-items: center;
+ text-align: center;
+ font-size: 0.8rem;
+ text-shadow: 0 1px 2px black;
+ font-weight: bold;
+ image-rendering: crisp-edges;
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.inventory-grid .dual-item img {
+ height: 48px;
+ margin: 0 -4px;
+}
+
+.inventory-grid .dual-item img:first-child {
+ align-self: flex-end;
+}
+
+.inventory-grid .item .quantity {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ text-align: right;
+ font-weight: 600;
+ font-size: 1.75rem;
+ line-height: 1.75rem;
+ text-shadow:
+ -1px -1px 0 #000,
+ 1px -1px 0 #000,
+ -1px 1px 0 #000,
+ 1px 1px 0 #000;
+ user-select: none;
+}
+
+/** Regions List ******************************************************************************************************/
+.regions-list {
+ padding: 1rem;
+}
+
+.regions-list summary {
+ list-style: none;
+ display: flex;
+ gap: 0.5rem;
+ cursor: pointer;
+}
+
+.regions-list summary::before {
+ content: "⯈";
+ width: 1em;
+ flex-shrink: 0;
+}
+
+.regions-list details {
+ font-weight: 300;
+}
+
+.regions-list details[open] > summary::before {
+ content: "⯆";
+}
+
+.regions-list .region {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 20fr 8fr 2fr 2fr;
+ align-items: center;
+ gap: 4px;
+ text-align: center;
+ font-weight: 300;
+ box-sizing: border-box;
+}
+
+.regions-list .region :first-child {
+ text-align: left;
+ font-weight: 500;
+}
+
+.regions-list .region.region-header {
+ margin-left: 24px;
+ width: calc(100% - 24px);
+ padding: 2px;
+}
+
+.regions-list .location-rows {
+ border-top: 1px solid white;
+ display: grid;
+ grid-template-columns: auto 32px;
+ font-weight: 300;
+ padding: 2px 8px;
+ margin-top: 4px;
+ font-size: 0.8rem;
+}
+
+.regions-list .location-rows :nth-child(even) {
+ text-align: right;
+}
diff --git a/WebHostLib/static/styles/weighted-options.css b/WebHostLib/static/styles/weighted-options.css
deleted file mode 100644
index 8a66ca237015..000000000000
--- a/WebHostLib/static/styles/weighted-options.css
+++ /dev/null
@@ -1,315 +0,0 @@
-html{
- background-image: url('../static/backgrounds/grass.png');
- background-repeat: repeat;
- background-size: 650px 650px;
- scroll-padding-top: 90px;
-}
-
-#weighted-settings{
- max-width: 1000px;
- margin-left: auto;
- margin-right: auto;
- background-color: rgba(0, 0, 0, 0.15);
- border-radius: 8px;
- padding: 1rem;
- color: #eeffeb;
-}
-
-#weighted-settings #games-wrapper{
- width: 100%;
-}
-
-#weighted-settings .setting-wrapper{
- width: 100%;
- margin-bottom: 2rem;
-}
-
-#weighted-settings .setting-wrapper .add-option-div{
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- margin-bottom: 1rem;
-}
-
-#weighted-settings .setting-wrapper .add-option-div button{
- width: auto;
- height: auto;
- margin: 0 0 0 0.15rem;
- padding: 0 0.25rem;
- border-radius: 4px;
- cursor: default;
-}
-
-#weighted-settings .setting-wrapper .add-option-div button:active{
- margin-bottom: 1px;
-}
-
-#weighted-settings p.setting-description{
- margin: 0 0 1rem;
-}
-
-#weighted-settings p.hint-text{
- margin: 0 0 1rem;
- font-style: italic;
-}
-
-#weighted-settings .jump-link{
- color: #ffef00;
- cursor: pointer;
- text-decoration: underline;
-}
-
-#weighted-settings table{
- width: 100%;
-}
-
-#weighted-settings table th, #weighted-settings table td{
- border: none;
-}
-
-#weighted-settings table td{
- padding: 5px;
-}
-
-#weighted-settings table .td-left{
- font-family: LexendDeca-Regular, sans-serif;
- padding-right: 1rem;
- width: 200px;
-}
-
-#weighted-settings table .td-middle{
- display: flex;
- flex-direction: column;
- justify-content: space-evenly;
- padding-right: 1rem;
-}
-
-#weighted-settings table .td-right{
- width: 4rem;
- text-align: right;
-}
-
-#weighted-settings table .td-delete{
- width: 50px;
- text-align: right;
-}
-
-#weighted-settings table .range-option-delete{
- cursor: pointer;
-}
-
-#weighted-settings .items-wrapper{
- display: flex;
- flex-direction: row;
- justify-content: space-between;
-}
-
-#weighted-settings .items-div h3{
- margin-bottom: 0.5rem;
-}
-
-#weighted-settings .items-wrapper .item-set-wrapper{
- width: 24%;
- font-weight: bold;
-}
-
-#weighted-settings .item-container{
- border: 1px solid #ffffff;
- border-radius: 2px;
- width: 100%;
- height: 300px;
- overflow-y: auto;
- overflow-x: hidden;
- margin-top: 0.125rem;
- font-weight: normal;
-}
-
-#weighted-settings .item-container .item-div{
- padding: 0.125rem 0.5rem;
- cursor: pointer;
-}
-
-#weighted-settings .item-container .item-div:hover{
- background-color: rgba(0, 0, 0, 0.1);
-}
-
-#weighted-settings .item-container .item-qty-div{
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- padding: 0.125rem 0.5rem;
- cursor: pointer;
-}
-
-#weighted-settings .item-container .item-qty-div .item-qty-input-wrapper{
- display: flex;
- flex-direction: column;
- justify-content: space-around;
-}
-
-#weighted-settings .item-container .item-qty-div input{
- min-width: unset;
- width: 1.5rem;
- text-align: center;
-}
-
-#weighted-settings .item-container .item-qty-div:hover{
- background-color: rgba(0, 0, 0, 0.1);
-}
-
-#weighted-settings .hints-div, #weighted-settings .locations-div{
- margin-top: 2rem;
-}
-
-#weighted-settings .hints-div h3, #weighted-settings .locations-div h3{
- margin-bottom: 0.5rem;
-}
-
-#weighted-settings .hints-container, #weighted-settings .locations-container{
- display: flex;
- flex-direction: row;
- justify-content: space-between;
-}
-
-#weighted-settings .hints-wrapper, #weighted-settings .locations-wrapper{
- width: calc(50% - 0.5rem);
- font-weight: bold;
-}
-
-#weighted-settings .hints-wrapper .simple-list, #weighted-settings .locations-wrapper .simple-list{
- margin-top: 0.25rem;
- height: 300px;
- font-weight: normal;
-}
-
-#weighted-settings #weighted-settings-button-row{
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- margin-top: 15px;
-}
-
-#weighted-settings code{
- background-color: #d9cd8e;
- border-radius: 4px;
- padding-left: 0.25rem;
- padding-right: 0.25rem;
- color: #000000;
-}
-
-#weighted-settings #user-message{
- display: none;
- width: calc(100% - 8px);
- background-color: #ffe86b;
- border-radius: 4px;
- color: #000000;
- padding: 4px;
- text-align: center;
-}
-
-#weighted-settings #user-message.visible{
- display: block;
- cursor: pointer;
-}
-
-#weighted-settings h1{
- font-size: 2.5rem;
- font-weight: normal;
- border-bottom: 1px solid #ffffff;
- width: 100%;
- margin-bottom: 0.5rem;
- color: #ffffff;
- text-shadow: 1px 1px 4px #000000;
-}
-
-#weighted-settings h2{
- font-size: 2rem;
- font-weight: normal;
- border-bottom: 1px solid #ffffff;
- width: 100%;
- margin-bottom: 0.5rem;
- color: #ffe993;
- text-transform: none;
- text-shadow: 1px 1px 2px #000000;
-}
-
-#weighted-settings h3, #weighted-settings h4, #weighted-settings h5, #weighted-settings h6{
- color: #ffffff;
- text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
- text-transform: none;
-}
-
-#weighted-settings a{
- color: #ffef00;
- cursor: pointer;
-}
-
-#weighted-settings input:not([type]){
- border: 1px solid #000000;
- padding: 3px;
- border-radius: 3px;
- min-width: 150px;
-}
-
-#weighted-settings input:not([type]):focus{
- border: 1px solid #ffffff;
-}
-
-#weighted-settings select{
- border: 1px solid #000000;
- padding: 3px;
- border-radius: 3px;
- min-width: 150px;
- background-color: #ffffff;
-}
-
-#weighted-settings .game-options, #weighted-settings .rom-options{
- display: flex;
- flex-direction: column;
-}
-
-#weighted-settings .simple-list{
- display: flex;
- flex-direction: column;
-
- max-height: 300px;
- overflow-y: auto;
- border: 1px solid #ffffff;
- border-radius: 4px;
-}
-
-#weighted-settings .simple-list .list-row label{
- display: block;
- width: calc(100% - 0.5rem);
- padding: 0.0625rem 0.25rem;
-}
-
-#weighted-settings .simple-list .list-row label:hover{
- background-color: rgba(0, 0, 0, 0.1);
-}
-
-#weighted-settings .simple-list .list-row label input[type=checkbox]{
- margin-right: 0.5rem;
-}
-
-#weighted-settings .simple-list hr{
- width: calc(100% - 2px);
- margin: 2px auto;
- border-bottom: 1px solid rgb(255 255 255 / 0.6);
-}
-
-#weighted-settings .invisible{
- display: none;
-}
-
-@media all and (max-width: 1000px), all and (orientation: portrait){
- #weighted-settings .game-options{
- justify-content: flex-start;
- flex-wrap: wrap;
- }
-
- #game-options table label{
- display: block;
- min-width: 200px;
- }
-}
diff --git a/WebHostLib/static/styles/weightedOptions/weightedOptions.css b/WebHostLib/static/styles/weightedOptions/weightedOptions.css
new file mode 100644
index 000000000000..3cfc6d24992d
--- /dev/null
+++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.css
@@ -0,0 +1,232 @@
+html {
+ background-image: url("../../static/backgrounds/grass.png");
+ background-repeat: repeat;
+ background-size: 650px 650px;
+ scroll-padding-top: 90px;
+}
+
+#weighted-options {
+ max-width: 1000px;
+ margin-left: auto;
+ margin-right: auto;
+ background-color: rgba(0, 0, 0, 0.15);
+ border-radius: 8px;
+ padding: 1rem;
+ color: #eeffeb;
+}
+#weighted-options #weighted-options-header h1 {
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+#weighted-options #weighted-options-header h1:nth-child(2) {
+ font-size: 1.4rem;
+ margin-top: -8px;
+ margin-bottom: 0.5rem;
+}
+#weighted-options .js-warning-banner {
+ width: calc(100% - 1rem);
+ padding: 0.5rem;
+ border-radius: 4px;
+ background-color: #f3f309;
+ color: #000000;
+ margin-bottom: 0.5rem;
+ text-align: center;
+}
+#weighted-options .option-wrapper {
+ width: 100%;
+ margin-bottom: 2rem;
+}
+#weighted-options .option-wrapper .add-option-div {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ margin-bottom: 1rem;
+}
+#weighted-options .option-wrapper .add-option-div button {
+ width: auto;
+ height: auto;
+ margin: 0 0 0 0.15rem;
+ padding: 0 0.25rem;
+ border-radius: 4px;
+ cursor: default;
+}
+#weighted-options .option-wrapper .add-option-div button:active {
+ margin-bottom: 1px;
+}
+#weighted-options p.option-description {
+ margin: 0 0 1rem;
+}
+#weighted-options p.hint-text {
+ margin: 0 0 1rem;
+ font-style: italic;
+}
+#weighted-options table {
+ width: 100%;
+ margin-top: 0.5rem;
+ margin-bottom: 1.5rem;
+}
+#weighted-options table th, #weighted-options table td {
+ border: none;
+}
+#weighted-options table td {
+ padding: 5px;
+}
+#weighted-options table .td-left {
+ font-family: LexendDeca-Regular, sans-serif;
+ padding-right: 1rem;
+ width: 200px;
+}
+#weighted-options table .td-middle {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly;
+ padding-right: 1rem;
+}
+#weighted-options table .td-right {
+ width: 4rem;
+ text-align: right;
+}
+#weighted-options table .td-delete {
+ width: 50px;
+ text-align: right;
+}
+#weighted-options table .range-option-delete {
+ cursor: pointer;
+}
+#weighted-options #weighted-options-button-row {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin-top: 15px;
+}
+#weighted-options #user-message {
+ display: none;
+ width: calc(100% - 8px);
+ background-color: #ffe86b;
+ border-radius: 4px;
+ color: #000000;
+ padding: 4px;
+ text-align: center;
+}
+#weighted-options #user-message.visible {
+ display: block;
+ cursor: pointer;
+}
+#weighted-options h1 {
+ font-size: 2.5rem;
+ font-weight: normal;
+ width: 100%;
+ margin-bottom: 0.5rem;
+ color: #ffffff;
+ text-shadow: 1px 1px 4px #000000;
+}
+#weighted-options h2, #weighted-options details summary.h2 {
+ font-size: 2rem;
+ font-weight: normal;
+ border-bottom: 1px solid #ffffff;
+ width: 100%;
+ margin-bottom: 0.5rem;
+ color: #ffe993;
+ text-transform: none;
+ text-shadow: 1px 1px 2px #000000;
+}
+#weighted-options h3, #weighted-options h4, #weighted-options h5, #weighted-options h6 {
+ color: #ffffff;
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
+ text-transform: none;
+ cursor: unset;
+}
+#weighted-options h3.option-group-header {
+ margin-top: 0.75rem;
+ font-weight: bold;
+}
+#weighted-options a {
+ color: #ffef00;
+ cursor: pointer;
+}
+#weighted-options input:not([type]) {
+ border: 1px solid #000000;
+ padding: 3px;
+ border-radius: 3px;
+ min-width: 150px;
+}
+#weighted-options input:not([type]):focus {
+ border: 1px solid #ffffff;
+}
+#weighted-options .invisible {
+ display: none;
+}
+#weighted-options .unsupported-option {
+ margin-top: 0.5rem;
+}
+#weighted-options .set-container, #weighted-options .dict-container, #weighted-options .list-container {
+ display: flex;
+ flex-direction: column;
+ background-color: rgba(0, 0, 0, 0.25);
+ border: 1px solid rgba(20, 20, 20, 0.25);
+ border-radius: 3px;
+ color: #ffffff;
+ max-height: 15rem;
+ min-width: 14.5rem;
+ overflow-y: auto;
+ padding-right: 0.25rem;
+ padding-left: 0.25rem;
+ margin-top: 0.5rem;
+}
+#weighted-options .set-container .divider, #weighted-options .dict-container .divider, #weighted-options .list-container .divider {
+ width: 100%;
+ height: 2px;
+ background-color: rgba(20, 20, 20, 0.25);
+ margin-top: 0.125rem;
+ margin-bottom: 0.125rem;
+}
+#weighted-options .set-container .set-entry, #weighted-options .set-container .dict-entry, #weighted-options .set-container .list-entry, #weighted-options .dict-container .set-entry, #weighted-options .dict-container .dict-entry, #weighted-options .dict-container .list-entry, #weighted-options .list-container .set-entry, #weighted-options .list-container .dict-entry, #weighted-options .list-container .list-entry {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ padding-bottom: 0.25rem;
+ padding-top: 0.25rem;
+ user-select: none;
+ line-height: 1rem;
+}
+#weighted-options .set-container .set-entry:hover, #weighted-options .set-container .dict-entry:hover, #weighted-options .set-container .list-entry:hover, #weighted-options .dict-container .set-entry:hover, #weighted-options .dict-container .dict-entry:hover, #weighted-options .dict-container .list-entry:hover, #weighted-options .list-container .set-entry:hover, #weighted-options .list-container .dict-entry:hover, #weighted-options .list-container .list-entry:hover {
+ background-color: rgba(20, 20, 20, 0.25);
+}
+#weighted-options .set-container .set-entry input[type=checkbox], #weighted-options .set-container .dict-entry input[type=checkbox], #weighted-options .set-container .list-entry input[type=checkbox], #weighted-options .dict-container .set-entry input[type=checkbox], #weighted-options .dict-container .dict-entry input[type=checkbox], #weighted-options .dict-container .list-entry input[type=checkbox], #weighted-options .list-container .set-entry input[type=checkbox], #weighted-options .list-container .dict-entry input[type=checkbox], #weighted-options .list-container .list-entry input[type=checkbox] {
+ margin-right: 0.25rem;
+}
+#weighted-options .set-container .set-entry input[type=number], #weighted-options .set-container .dict-entry input[type=number], #weighted-options .set-container .list-entry input[type=number], #weighted-options .dict-container .set-entry input[type=number], #weighted-options .dict-container .dict-entry input[type=number], #weighted-options .dict-container .list-entry input[type=number], #weighted-options .list-container .set-entry input[type=number], #weighted-options .list-container .dict-entry input[type=number], #weighted-options .list-container .list-entry input[type=number] {
+ max-width: 1.5rem;
+ max-height: 1rem;
+ margin-left: 0.125rem;
+ text-align: center;
+ /* Hide arrows on input[type=number] fields */
+ -moz-appearance: textfield;
+}
+#weighted-options .set-container .set-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .set-container .set-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .set-container .dict-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .set-container .dict-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .set-container .list-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .set-container .list-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .dict-container .set-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .dict-container .set-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .dict-container .dict-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .dict-container .dict-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .dict-container .list-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .dict-container .list-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .list-container .set-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .list-container .set-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .list-container .dict-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .list-container .dict-entry input[type=number]::-webkit-inner-spin-button, #weighted-options .list-container .list-entry input[type=number]::-webkit-outer-spin-button, #weighted-options .list-container .list-entry input[type=number]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+#weighted-options .set-container .set-entry label, #weighted-options .set-container .dict-entry label, #weighted-options .set-container .list-entry label, #weighted-options .dict-container .set-entry label, #weighted-options .dict-container .dict-entry label, #weighted-options .dict-container .list-entry label, #weighted-options .list-container .set-entry label, #weighted-options .list-container .dict-entry label, #weighted-options .list-container .list-entry label {
+ flex-grow: 1;
+ margin-right: 0;
+ min-width: unset;
+ display: unset;
+}
+
+.hidden {
+ display: none;
+}
+
+@media all and (max-width: 1000px), all and (orientation: portrait) {
+ #weighted-options .game-options {
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ }
+ #game-options table label {
+ display: block;
+ min-width: 200px;
+ }
+}
+
+/*# sourceMappingURL=weightedOptions.css.map */
diff --git a/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map b/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map
new file mode 100644
index 000000000000..7c57cde01506
--- /dev/null
+++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["weightedOptions.scss"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGI;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAOZ;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAIR;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIA;EACI;EACA;;AAIR;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;AAEA;EACA;;AACA;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;;AAMhB;EACI;;;AAGJ;EACI;IACI;IACA;;EAGJ;IACI;IACA","file":"weightedOptions.css"}
\ No newline at end of file
diff --git a/WebHostLib/static/styles/weightedOptions/weightedOptions.scss b/WebHostLib/static/styles/weightedOptions/weightedOptions.scss
new file mode 100644
index 000000000000..7ff3a2c3722e
--- /dev/null
+++ b/WebHostLib/static/styles/weightedOptions/weightedOptions.scss
@@ -0,0 +1,274 @@
+html{
+ background-image: url('../../static/backgrounds/grass.png');
+ background-repeat: repeat;
+ background-size: 650px 650px;
+ scroll-padding-top: 90px;
+}
+
+#weighted-options{
+ max-width: 1000px;
+ margin-left: auto;
+ margin-right: auto;
+ background-color: rgba(0, 0, 0, 0.15);
+ border-radius: 8px;
+ padding: 1rem;
+ color: #eeffeb;
+
+ #weighted-options-header{
+ h1{
+ margin-bottom: 0;
+ padding-bottom: 0;
+ }
+
+ h1:nth-child(2){
+ font-size: 1.4rem;
+ margin-top: -8px;
+ margin-bottom: 0.5rem;
+ }
+ }
+
+ .js-warning-banner{
+ width: calc(100% - 1rem);
+ padding: 0.5rem;
+ border-radius: 4px;
+ background-color: #f3f309;
+ color: #000000;
+ margin-bottom: 0.5rem;
+ text-align: center;
+ }
+
+ .option-wrapper{
+ width: 100%;
+ margin-bottom: 2rem;
+
+ .add-option-div{
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ margin-bottom: 1rem;
+
+ button{
+ width: auto;
+ height: auto;
+ margin: 0 0 0 0.15rem;
+ padding: 0 0.25rem;
+ border-radius: 4px;
+ cursor: default;
+
+ &:active{
+ margin-bottom: 1px;
+ }
+ }
+ }
+ }
+
+ p{
+ &.option-description{
+ margin: 0 0 1rem;
+ }
+
+ &.hint-text{
+ margin: 0 0 1rem;
+ font-style: italic;
+ };
+ }
+
+ table{
+ width: 100%;
+ margin-top: 0.5rem;
+ margin-bottom: 1.5rem;
+
+ th, td{
+ border: none;
+ }
+
+ td{
+ padding: 5px;
+ }
+
+ .td-left{
+ font-family: LexendDeca-Regular, sans-serif;
+ padding-right: 1rem;
+ width: 200px;
+ }
+
+ .td-middle{
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly;
+ padding-right: 1rem;
+ }
+
+ .td-right{
+ width: 4rem;
+ text-align: right;
+ }
+
+ .td-delete{
+ width: 50px;
+ text-align: right;
+ }
+
+ .range-option-delete{
+ cursor: pointer;
+ }
+ }
+
+ #weighted-options-button-row{
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin-top: 15px;
+ }
+
+ #user-message{
+ display: none;
+ width: calc(100% - 8px);
+ background-color: #ffe86b;
+ border-radius: 4px;
+ color: #000000;
+ padding: 4px;
+ text-align: center;
+
+ &.visible{
+ display: block;
+ cursor: pointer;
+ }
+ }
+
+ h1{
+ font-size: 2.5rem;
+ font-weight: normal;
+ width: 100%;
+ margin-bottom: 0.5rem;
+ color: #ffffff;
+ text-shadow: 1px 1px 4px #000000;
+ }
+
+ h2, details summary.h2{
+ font-size: 2rem;
+ font-weight: normal;
+ border-bottom: 1px solid #ffffff;
+ width: 100%;
+ margin-bottom: 0.5rem;
+ color: #ffe993;
+ text-transform: none;
+ text-shadow: 1px 1px 2px #000000;
+ }
+
+ h3, h4, h5, h6{
+ color: #ffffff;
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
+ text-transform: none;
+ cursor: unset;
+ }
+
+ h3{
+ &.option-group-header{
+ margin-top: 0.75rem;
+ font-weight: bold;
+ }
+ }
+
+ a{
+ color: #ffef00;
+ cursor: pointer;
+ }
+
+ input:not([type]){
+ border: 1px solid #000000;
+ padding: 3px;
+ border-radius: 3px;
+ min-width: 150px;
+
+ &:focus{
+ border: 1px solid #ffffff;
+ }
+ }
+
+ .invisible{
+ display: none;
+ }
+
+ .unsupported-option{
+ margin-top: 0.5rem;
+ }
+
+ .set-container, .dict-container, .list-container{
+ display: flex;
+ flex-direction: column;
+ background-color: rgba(0, 0, 0, 0.25);
+ border: 1px solid rgba(20, 20, 20, 0.25);
+ border-radius: 3px;
+ color: #ffffff;
+ max-height: 15rem;
+ min-width: 14.5rem;
+ overflow-y: auto;
+ padding-right: 0.25rem;
+ padding-left: 0.25rem;
+ margin-top: 0.5rem;
+
+ .divider{
+ width: 100%;
+ height: 2px;
+ background-color: rgba(20, 20, 20, 0.25);
+ margin-top: 0.125rem;
+ margin-bottom: 0.125rem;
+ }
+
+ .set-entry, .dict-entry, .list-entry{
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ padding-bottom: 0.25rem;
+ padding-top: 0.25rem;
+ user-select: none;
+ line-height: 1rem;
+
+ &:hover{
+ background-color: rgba(20, 20, 20, 0.25);
+ }
+
+ input[type=checkbox]{
+ margin-right: 0.25rem;
+ }
+
+ input[type=number]{
+ max-width: 1.5rem;
+ max-height: 1rem;
+ margin-left: 0.125rem;
+ text-align: center;
+
+ /* Hide arrows on input[type=number] fields */
+ -moz-appearance: textfield;
+ &::-webkit-outer-spin-button, &::-webkit-inner-spin-button{
+ -webkit-appearance: none;
+ margin: 0;
+ }
+ }
+
+ label{
+ flex-grow: 1;
+ margin-right: 0;
+ min-width: unset;
+ display: unset;
+ }
+ }
+ }
+}
+
+.hidden{
+ display: none;
+}
+
+@media all and (max-width: 1000px), all and (orientation: portrait){
+ #weighted-options .game-options{
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ }
+
+ #game-options table label{
+ display: block;
+ min-width: 200px;
+ }
+}
diff --git a/WebHostLib/templates/generate.html b/WebHostLib/templates/generate.html
index 33f8dbc09e6c..53d98dfae6ba 100644
--- a/WebHostLib/templates/generate.html
+++ b/WebHostLib/templates/generate.html
@@ -69,8 +69,8 @@ Generate Game{% if race %} (Race Mode){% endif %}
|
@@ -185,12 +185,12 @@ Generate Game{% if race %} (Race Mode){% endif %}
+
+
+
-
-
-
diff --git a/WebHostLib/templates/hostRoom.html b/WebHostLib/templates/hostRoom.html
index ba15d64acac1..fa8e26c2cbf8 100644
--- a/WebHostLib/templates/hostRoom.html
+++ b/WebHostLib/templates/hostRoom.html
@@ -3,6 +3,16 @@
{% block head %}
Multiworld {{ room.id|suuid }}
{% if should_refresh %}{% endif %}
+
+
+
+ {% if room.seed.slots|length < 2 %}
+
+ {% else %}
+
+ {% endif %}
{% endblock %}
@@ -14,7 +24,8 @@
{% endif %}
{% if room.tracker %}
- This room has a Multiworld Tracker enabled.
+ This room has a Multiworld Tracker
+ and a Sphere Tracker enabled.
{% endif %}
The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity.
@@ -33,7 +44,7 @@
{{ macros.list_patches_room(room) }}
{% if room.owner == session["_id"] %}
diff --git a/WebHostLib/templates/islandFooter.html b/WebHostLib/templates/islandFooter.html
index 7b89c4a9e079..08cf227990b8 100644
--- a/WebHostLib/templates/islandFooter.html
+++ b/WebHostLib/templates/islandFooter.html
@@ -1,6 +1,6 @@
{% block footer %}
|