More smoothness and persistence
This commit is contained in:
1 parent
f924016478
commit
aeb9173825
10 files changed
+2113
-1776
No files matched your search
+625
-598
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,87 @@
|
||||
import pathlib
|
||||
|
||||
def is_item_ignored(item_name, ignored_directories_list, ignored_extensions_list):
|
||||
if item_name in ignored_directories_list:
|
||||
return True
|
||||
for extension_string in ignored_extensions_list:
|
||||
if item_name.endswith(extension_string):
|
||||
return True
|
||||
return False
|
||||
|
||||
def format_tree_line(entry_name, prefix_string, is_last_element):
|
||||
connector_string = '└── ' if is_last_element else '├── '
|
||||
return prefix_string + connector_string + entry_name
|
||||
|
||||
def format_file_section(relative_path_string, file_content_string):
|
||||
header_string = '\n--- FILE START: ' + relative_path_string + ' ---\n'
|
||||
footer_string = '\n--- FILE END: ' + relative_path_string + ' ---\n'
|
||||
return header_string + file_content_string + footer_string
|
||||
|
||||
def build_final_output_string(tree_lines_list, code_contents_list):
|
||||
tree_string = '\n'.join(tree_lines_list)
|
||||
code_string = ''.join(code_contents_list)
|
||||
return 'PROJECT STRUCTURE:\n' + tree_string + '\n\n' + code_string
|
||||
|
||||
def collect_file_system_nodes(root_path, ignored_directories_list, ignored_extensions_list):
|
||||
valid_nodes_list = []
|
||||
try:
|
||||
directory_items_list = list(root_path.iterdir())
|
||||
except PermissionError:
|
||||
return valid_nodes_list
|
||||
|
||||
for item_path in directory_items_list:
|
||||
if not is_item_ignored(item_path.name, ignored_directories_list, ignored_extensions_list):
|
||||
valid_nodes_list.append(item_path)
|
||||
|
||||
valid_nodes_list.sort(key=lambda path_object: (not path_object.is_dir(), path_object.name.lower()))
|
||||
return valid_nodes_list
|
||||
|
||||
def read_file_content_safely(file_path_object):
|
||||
try:
|
||||
return file_path_object.read_text(encoding='utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return '[BINARY_OR_UNSUPPORTED_ENCODING]'
|
||||
except PermissionError:
|
||||
return '[PERMISSION_DENIED]'
|
||||
|
||||
def write_output_file(output_file_path, content_string):
|
||||
output_file_path.write_text(content_string, encoding='utf-8')
|
||||
|
||||
def process_directory_recursively(current_path, base_directory_path, prefix_string, ignored_directories_list, ignored_extensions_list, tree_lines_list, code_contents_list):
|
||||
nodes_list = collect_file_system_nodes(current_path, ignored_directories_list, ignored_extensions_list)
|
||||
total_nodes_count = len(nodes_list)
|
||||
|
||||
for index_number, node_path in enumerate(nodes_list):
|
||||
is_last_node = index_number == total_nodes_count - 1
|
||||
formatted_tree_line = format_tree_line(node_path.name, prefix_string, is_last_node)
|
||||
tree_lines_list.append(formatted_tree_line)
|
||||
|
||||
if node_path.is_dir():
|
||||
new_prefix_string = prefix_string + (' ' if is_last_node else '│ ')
|
||||
process_directory_recursively(node_path, base_directory_path, new_prefix_string, ignored_directories_list, ignored_extensions_list, tree_lines_list, code_contents_list)
|
||||
else:
|
||||
relative_file_path_string = str(node_path.relative_to(base_directory_path))
|
||||
file_content_string = read_file_content_safely(node_path)
|
||||
formatted_file_section = format_file_section(relative_file_path_string, file_content_string)
|
||||
code_contents_list.append(formatted_file_section)
|
||||
|
||||
def execute_bundler_script():
|
||||
target_output_file_name_string = 'output_fuer_ki.txt'
|
||||
ignored_directories_list = ['.git', '__pycache__', 'venv', 'env', '.venv', 'node_modules', '.idea', '.vscode', 'instance']
|
||||
ignored_extensions_list = ['.pyc', '.sqlite', '.db', '.png', '.jpg', '.jpeg', '.svg', '.ico', '.exe', '.pdf', '.zip', '.tar.gz', '.mp3', '.mp4', target_output_file_name_string]
|
||||
|
||||
current_working_directory_path = pathlib.Path.cwd()
|
||||
output_file_path = current_working_directory_path / target_output_file_name_string
|
||||
|
||||
tree_lines_list = [current_working_directory_path.name]
|
||||
code_contents_list = []
|
||||
|
||||
process_directory_recursively(current_working_directory_path, current_working_directory_path, '', ignored_directories_list, ignored_extensions_list, tree_lines_list, code_contents_list)
|
||||
|
||||
final_output_string = build_final_output_string(tree_lines_list, code_contents_list)
|
||||
write_output_file(output_file_path, final_output_string)
|
||||
|
||||
print('Erfolg! Die Datei ' + target_output_file_name_string + ' wurde generiert.')
|
||||
|
||||
if __name__ == '__main__':
|
||||
execute_bundler_script()
|
||||
Binary file not shown.
+384
-299
@@ -1,330 +1,415 @@
|
||||
let websocket_connection;
|
||||
let pending_administrator_form = null;
|
||||
let stopwatch_interval_identifier = null;
|
||||
const application_state = {
|
||||
websocket_connection: null,
|
||||
pending_administrator_form: null,
|
||||
stopwatch_interval_identifier: null
|
||||
};
|
||||
|
||||
function initialize_lucide_icons() {
|
||||
if (typeof lucide !== "undefined") {
|
||||
lucide.createIcons();
|
||||
}
|
||||
/* --- Deterministische Funktionen ("ehrliche" Funktionen) --- */
|
||||
|
||||
function calculate_time_difference(start_time_milliseconds, current_time_milliseconds) {
|
||||
return current_time_milliseconds - start_time_milliseconds;
|
||||
}
|
||||
|
||||
function update_game_stopwatch() {
|
||||
const stopwatch_element = document.getElementById("game-stopwatch");
|
||||
if (!stopwatch_element) {
|
||||
return;
|
||||
}
|
||||
function format_stopwatch_string(difference_in_milliseconds) {
|
||||
if (difference_in_milliseconds < 0) {
|
||||
return '00:00 Minuten';
|
||||
}
|
||||
|
||||
const start_time_string = stopwatch_element.getAttribute("data-start-time");
|
||||
if (!start_time_string) {
|
||||
return;
|
||||
}
|
||||
const total_seconds = Math.floor(difference_in_milliseconds / 1000);
|
||||
const days_passed = Math.floor(total_seconds / 86400);
|
||||
const hours_passed = Math.floor((total_seconds % 86400) / 3600);
|
||||
const minutes_passed = Math.floor((total_seconds % 3600) / 60);
|
||||
const seconds_passed = total_seconds % 60;
|
||||
|
||||
const start_date = new Date(start_time_string);
|
||||
if (isNaN(start_date.getTime())) {
|
||||
return;
|
||||
}
|
||||
let output_string = '';
|
||||
|
||||
const current_date = new Date();
|
||||
const difference_in_milliseconds = current_date.getTime() - start_date.getTime();
|
||||
if (days_passed === 1) {
|
||||
output_string += '1 Tag und ';
|
||||
} else if (days_passed > 1) {
|
||||
output_string += days_passed + ' Tage und ';
|
||||
}
|
||||
|
||||
if (difference_in_milliseconds < 0) {
|
||||
stopwatch_element.textContent = "00:00 Minuten";
|
||||
return;
|
||||
}
|
||||
const padded_minutes = String(minutes_passed).padStart(2, '0');
|
||||
const padded_seconds = String(seconds_passed).padStart(2, '0');
|
||||
|
||||
const total_seconds = Math.floor(difference_in_milliseconds / 1000);
|
||||
const days_passed = Math.floor(total_seconds / 86400);
|
||||
const hours_passed = Math.floor((total_seconds % 86400) / 3600);
|
||||
const minutes_passed = Math.floor((total_seconds % 3600) / 60);
|
||||
const seconds_passed = total_seconds % 60;
|
||||
if (hours_passed === 1) {
|
||||
output_string += '1:' + padded_minutes + ':' + padded_seconds + ' Stunde';
|
||||
} else if (hours_passed > 1) {
|
||||
output_string += hours_passed + ':' + padded_minutes + ':' + padded_seconds + ' Stunden';
|
||||
} else {
|
||||
output_string += padded_minutes + ':' + padded_seconds + ' Minuten';
|
||||
}
|
||||
|
||||
let output_string = "";
|
||||
|
||||
if (days_passed === 1) {
|
||||
output_string += "1 Tag und ";
|
||||
} else if (days_passed > 1) {
|
||||
output_string += days_passed + " Tage und ";
|
||||
}
|
||||
|
||||
const padded_minutes = String(minutes_passed).padStart(2, "0");
|
||||
const padded_seconds = String(seconds_passed).padStart(2, "0");
|
||||
|
||||
if (hours_passed === 1) {
|
||||
output_string += "1:" + padded_minutes + ":" + padded_seconds + " Stunde";
|
||||
} else if (hours_passed > 1) {
|
||||
output_string += hours_passed + ":" + padded_minutes + ":" + padded_seconds + " Stunden";
|
||||
} else {
|
||||
output_string += padded_minutes + ":" + padded_seconds + " Minuten";
|
||||
}
|
||||
|
||||
stopwatch_element.textContent = output_string;
|
||||
return output_string;
|
||||
}
|
||||
|
||||
function initialize_application_behavior() {
|
||||
initialize_lucide_icons();
|
||||
|
||||
if (stopwatch_interval_identifier) {
|
||||
clearInterval(stopwatch_interval_identifier);
|
||||
}
|
||||
stopwatch_interval_identifier = setInterval(update_game_stopwatch, 1000);
|
||||
update_game_stopwatch();
|
||||
|
||||
const tab_buttons = document.querySelectorAll(".tab-button");
|
||||
tab_buttons.forEach(function(current_button) {
|
||||
current_button.addEventListener("click", function() {
|
||||
const target_identifier = current_button.getAttribute("data-target");
|
||||
const parent_container = current_button.closest(".application-content");
|
||||
|
||||
parent_container.querySelectorAll(".tab-button").forEach(function(button_element) {
|
||||
button_element.classList.remove("active-tab");
|
||||
});
|
||||
parent_container.querySelectorAll(".tab-content").forEach(function(content_element) {
|
||||
content_element.classList.remove("active-content");
|
||||
});
|
||||
|
||||
current_button.classList.add("active-tab");
|
||||
const target_panel = document.getElementById(target_identifier);
|
||||
if (target_panel) {
|
||||
target_panel.classList.add("active-content");
|
||||
}
|
||||
});
|
||||
});
|
||||
function synchronize_element_nodes(current_node, updated_node) {
|
||||
if (current_node.nodeType !== updated_node.nodeType) {
|
||||
current_node.replaceWith(updated_node.cloneNode(true));
|
||||
return;
|
||||
}
|
||||
if (current_node.nodeType === Node.TEXT_NODE) {
|
||||
if (current_node.nodeValue !== updated_node.nodeValue) {
|
||||
current_node.nodeValue = updated_node.nodeValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (current_node.nodeType !== Node.ELEMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const player_forms = document.querySelectorAll(".asynchronous-form");
|
||||
player_forms.forEach(function(current_form) {
|
||||
current_form.addEventListener("submit", function(form_submission_event) {
|
||||
form_submission_event.preventDefault();
|
||||
execute_form_submission(current_form);
|
||||
});
|
||||
});
|
||||
if (current_node.tagName !== updated_node.tagName) {
|
||||
current_node.replaceWith(updated_node.cloneNode(true));
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_node.namespaceURI !== updated_node.namespaceURI) {
|
||||
current_node.replaceWith(updated_node.cloneNode(true));
|
||||
return;
|
||||
}
|
||||
|
||||
const administrator_forms = document.querySelectorAll(".admin-asynchronous-form");
|
||||
administrator_forms.forEach(function(current_form) {
|
||||
current_form.addEventListener("submit", function(form_submission_event) {
|
||||
form_submission_event.preventDefault();
|
||||
pending_administrator_form = current_form;
|
||||
const administrator_modal = document.getElementById("admin-confirmation-modal");
|
||||
if (administrator_modal) {
|
||||
const time_input = document.getElementById("modal_execution_time");
|
||||
if (time_input) {
|
||||
time_input.value = "";
|
||||
}
|
||||
administrator_modal.classList.add("active-modal");
|
||||
}
|
||||
});
|
||||
});
|
||||
const is_active_input = document.activeElement === current_node;
|
||||
const is_input_element = ['INPUT', 'TEXTAREA', 'SELECT'].includes(current_node.tagName);
|
||||
|
||||
const confirm_administrator_button = document.getElementById("confirm-admin-action");
|
||||
if (confirm_administrator_button) {
|
||||
confirm_administrator_button.addEventListener("click", function() {
|
||||
if (pending_administrator_form) {
|
||||
const time_input = document.getElementById("modal_execution_time");
|
||||
if (time_input && time_input.value) {
|
||||
let hidden_time_input = pending_administrator_form.querySelector("input[name='execution_time']");
|
||||
if (!hidden_time_input) {
|
||||
hidden_time_input = document.createElement("input");
|
||||
hidden_time_input.type = "hidden";
|
||||
hidden_time_input.name = "execution_time";
|
||||
pending_administrator_form.appendChild(hidden_time_input);
|
||||
}
|
||||
hidden_time_input.value = time_input.value;
|
||||
} else {
|
||||
let hidden_time_input = pending_administrator_form.querySelector("input[name='execution_time']");
|
||||
if (hidden_time_input) {
|
||||
hidden_time_input.remove();
|
||||
}
|
||||
}
|
||||
execute_form_submission(pending_administrator_form);
|
||||
pending_administrator_form = null;
|
||||
const administrator_modal = document.getElementById("admin-confirmation-modal");
|
||||
if (administrator_modal) {
|
||||
administrator_modal.classList.remove("active-modal");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (is_input_element && !is_active_input) {
|
||||
if (current_node.value !== updated_node.value) {
|
||||
current_node.value = updated_node.value;
|
||||
}
|
||||
}
|
||||
|
||||
const cancel_administrator_button = document.getElementById("cancel-admin-action");
|
||||
if (cancel_administrator_button) {
|
||||
cancel_administrator_button.addEventListener("click", function() {
|
||||
pending_administrator_form = null;
|
||||
const administrator_modal = document.getElementById("admin-confirmation-modal");
|
||||
if (administrator_modal) {
|
||||
administrator_modal.classList.remove("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
const current_attributes = current_node.attributes;
|
||||
const updated_attributes = updated_node.attributes;
|
||||
|
||||
const trigger_elimination_button = document.getElementById("trigger-elimination");
|
||||
if (trigger_elimination_button) {
|
||||
trigger_elimination_button.addEventListener("click", function() {
|
||||
const elimination_modal = document.getElementById("elimination-confirm-modal");
|
||||
if (elimination_modal) {
|
||||
elimination_modal.classList.add("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
for (let index = 0; index < updated_attributes.length; index++) {
|
||||
const attribute = updated_attributes[index];
|
||||
if (current_node.getAttribute(attribute.name) !== attribute.value) {
|
||||
if (!(is_input_element && is_active_input && attribute.name === 'value')) {
|
||||
current_node.setAttribute(attribute.name, attribute.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const proceed_elimination_button = document.getElementById("proceed-elimination");
|
||||
if (proceed_elimination_button) {
|
||||
proceed_elimination_button.addEventListener("click", function() {
|
||||
websocket_connection.emit("request_elimination");
|
||||
const elimination_modal = document.getElementById("elimination-confirm-modal");
|
||||
if (elimination_modal) {
|
||||
elimination_modal.classList.remove("active-modal");
|
||||
}
|
||||
const waiting_modal = document.getElementById("waiting-modal");
|
||||
if (waiting_modal) {
|
||||
waiting_modal.classList.add("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
for (let index = current_attributes.length - 1; index >= 0; index--) {
|
||||
const attribute = current_attributes[index];
|
||||
if (!updated_node.hasAttribute(attribute.name)) {
|
||||
current_node.removeAttribute(attribute.name);
|
||||
}
|
||||
}
|
||||
|
||||
const cancel_elimination_button = document.getElementById("cancel-elimination");
|
||||
if (cancel_elimination_button) {
|
||||
cancel_elimination_button.addEventListener("click", function() {
|
||||
const elimination_modal = document.getElementById("elimination-confirm-modal");
|
||||
if (elimination_modal) {
|
||||
elimination_modal.classList.remove("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
const current_children = Array.from(current_node.childNodes);
|
||||
const updated_children = Array.from(updated_node.childNodes);
|
||||
const max_length = Math.max(current_children.length, updated_children.length);
|
||||
|
||||
const confirm_button = document.getElementById("confirm-elimination");
|
||||
if (confirm_button) {
|
||||
confirm_button.addEventListener("click", function() {
|
||||
websocket_connection.emit("confirm_elimination");
|
||||
const modal_element = document.getElementById("verification-modal");
|
||||
if (modal_element) {
|
||||
modal_element.classList.remove("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
for (let index = 0; index < max_length; index++) {
|
||||
const current_child = current_children[index];
|
||||
const updated_child = updated_children[index];
|
||||
|
||||
const reject_button = document.getElementById("reject-elimination");
|
||||
if (reject_button) {
|
||||
reject_button.addEventListener("click", function() {
|
||||
websocket_connection.emit("reject_elimination");
|
||||
const modal_element = document.getElementById("verification-modal");
|
||||
if (modal_element) {
|
||||
modal_element.classList.remove("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const acknowledge_rejection_button = document.getElementById("acknowledge-rejection");
|
||||
if (acknowledge_rejection_button) {
|
||||
acknowledge_rejection_button.addEventListener("click", function() {
|
||||
const rejection_modal = document.getElementById("rejection-modal");
|
||||
if (rejection_modal) {
|
||||
rejection_modal.classList.remove("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const open_name_modal_button = document.getElementById("trigger-name-change-modal");
|
||||
if (open_name_modal_button) {
|
||||
open_name_modal_button.addEventListener("click", function() {
|
||||
const name_modal = document.getElementById("name-change-modal");
|
||||
if (name_modal) {
|
||||
name_modal.classList.add("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const close_name_modal_button = document.getElementById("cancel-name-change");
|
||||
if (close_name_modal_button) {
|
||||
close_name_modal_button.addEventListener("click", function() {
|
||||
const name_modal = document.getElementById("name-change-modal");
|
||||
if (name_modal) {
|
||||
name_modal.classList.remove("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!current_child && updated_child) {
|
||||
current_node.appendChild(updated_child.cloneNode(true));
|
||||
} else if (current_child && !updated_child) {
|
||||
current_node.removeChild(current_child);
|
||||
} else if (current_child && updated_child) {
|
||||
synchronize_element_nodes(current_child, updated_child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function execute_form_submission(target_form) {
|
||||
const active_tab_element = document.querySelector(".active-tab");
|
||||
let active_target = "control-panel";
|
||||
if (active_tab_element) {
|
||||
active_target = active_tab_element.getAttribute("data-target");
|
||||
}
|
||||
|
||||
fetch(target_form.action, {
|
||||
method: target_form.method,
|
||||
body: new FormData(target_form)
|
||||
})
|
||||
.then(function(network_response) { return network_response.text(); })
|
||||
.then(function(markup_text) {
|
||||
replace_main_content(markup_text);
|
||||
const new_tab_button = document.querySelector(".tab-button[data-target='" + active_target + "']");
|
||||
if (new_tab_button) {
|
||||
new_tab_button.click();
|
||||
}
|
||||
});
|
||||
/* --- Architektur & State-Management ("unehrliche" Funktionen) --- */
|
||||
|
||||
function trigger_dom_update(markup_text) {
|
||||
const document_parser = new DOMParser();
|
||||
const parsed_document = document_parser.parseFromString(markup_text, 'text/html');
|
||||
const current_container = document.querySelector('.application-content');
|
||||
const new_container = parsed_document.querySelector('.application-content');
|
||||
|
||||
if (!current_container || !new_container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const current_active_tab = document.querySelector('.active-tab');
|
||||
const active_tab_target = current_active_tab ? current_active_tab.getAttribute('data-target') : null;
|
||||
|
||||
const active_modals = [];
|
||||
document.querySelectorAll('.active-modal').forEach(function(modal) {
|
||||
active_modals.push(modal.id);
|
||||
});
|
||||
|
||||
synchronize_element_nodes(current_container, new_container);
|
||||
|
||||
if (active_tab_target) {
|
||||
document.querySelectorAll('.tab-button').forEach(function(button) { button.classList.remove('active-tab'); });
|
||||
document.querySelectorAll('.tab-content').forEach(function(content) { content.classList.remove('active-content'); });
|
||||
|
||||
const restored_tab = document.querySelector('.tab-button[data-target="' + active_tab_target + '"]');
|
||||
const restored_content = document.getElementById(active_tab_target);
|
||||
if (restored_tab && restored_content) {
|
||||
restored_tab.classList.add('active-tab');
|
||||
restored_content.classList.add('active-content');
|
||||
}
|
||||
}
|
||||
|
||||
active_modals.forEach(function(modal_identifier) {
|
||||
const restored_modal = document.getElementById(modal_identifier);
|
||||
if (restored_modal) {
|
||||
restored_modal.classList.add('active-modal');
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
}
|
||||
|
||||
function replace_main_content(markup_text) {
|
||||
const document_parser = new DOMParser();
|
||||
const parsed_document = document_parser.parseFromString(markup_text, "text/html");
|
||||
const current_container = document.querySelector(".application-content");
|
||||
const new_container = parsed_document.querySelector(".application-content");
|
||||
if (current_container && new_container) {
|
||||
current_container.innerHTML = new_container.innerHTML;
|
||||
initialize_application_behavior();
|
||||
}
|
||||
function fetch_and_synchronize_dom() {
|
||||
fetch(window.location.href)
|
||||
.then(function(network_response) {
|
||||
if (!network_response.ok) {
|
||||
throw new Error('Network response failure');
|
||||
}
|
||||
return network_response.text();
|
||||
})
|
||||
.then(function(markup_text) {
|
||||
trigger_dom_update(markup_text);
|
||||
})
|
||||
.catch(function(error_object) {
|
||||
console.error('DOM Synchronization failed:', error_object);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("/service_worker.js");
|
||||
}
|
||||
function execute_asynchronous_form_submission(target_form) {
|
||||
fetch(target_form.action, {
|
||||
method: target_form.method,
|
||||
body: new FormData(target_form)
|
||||
})
|
||||
.then(function(network_response) {
|
||||
if (!network_response.ok) {
|
||||
throw new Error('Form submission failure');
|
||||
}
|
||||
return network_response.text();
|
||||
})
|
||||
.then(function(markup_text) {
|
||||
trigger_dom_update(markup_text);
|
||||
})
|
||||
.catch(function(error_object) {
|
||||
console.error('Form execution failed:', error_object);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof io !== "undefined") {
|
||||
websocket_connection = io();
|
||||
|
||||
websocket_connection.on("game_state_changed", function() {
|
||||
if (document.querySelector(".game-section") || document.querySelector(".administrator-section")) {
|
||||
fetch(window.location.href)
|
||||
.then(function(network_response) { return network_response.text(); })
|
||||
.then(function(markup_text) {
|
||||
replace_main_content(markup_text);
|
||||
});
|
||||
}
|
||||
});
|
||||
function update_game_stopwatch_ui() {
|
||||
const stopwatch_element = document.getElementById('game-stopwatch');
|
||||
if (!stopwatch_element) {
|
||||
return;
|
||||
}
|
||||
|
||||
websocket_connection.on("admin_state_changed", function() {
|
||||
if (document.querySelector(".administrator-section")) {
|
||||
fetch(window.location.href)
|
||||
.then(function(network_response) { return network_response.text(); })
|
||||
.then(function(markup_text) {
|
||||
replace_main_content(markup_text);
|
||||
});
|
||||
}
|
||||
});
|
||||
const start_time_string = stopwatch_element.getAttribute('data-start-time');
|
||||
if (!start_time_string) {
|
||||
return;
|
||||
}
|
||||
|
||||
websocket_connection.on("elimination_verification_requested", function() {
|
||||
fetch(window.location.href)
|
||||
.then(function(network_response) { return network_response.text(); })
|
||||
.then(function(markup_text) {
|
||||
replace_main_content(markup_text);
|
||||
const new_modal_element = document.getElementById("verification-modal");
|
||||
if (new_modal_element) {
|
||||
new_modal_element.classList.add("active-modal");
|
||||
}
|
||||
});
|
||||
});
|
||||
const start_date = new Date(start_time_string);
|
||||
if (isNaN(start_date.getTime())) {
|
||||
return;
|
||||
}
|
||||
|
||||
websocket_connection.on("elimination_rejected", function() {
|
||||
const waiting_modal = document.getElementById("waiting-modal");
|
||||
if (waiting_modal) {
|
||||
waiting_modal.classList.remove("active-modal");
|
||||
}
|
||||
const rejection_modal = document.getElementById("rejection-modal");
|
||||
if (rejection_modal) {
|
||||
rejection_modal.classList.add("active-modal");
|
||||
}
|
||||
});
|
||||
}
|
||||
initialize_application_behavior();
|
||||
const current_date = new Date();
|
||||
const difference = calculate_time_difference(start_date.getTime(), current_date.getTime());
|
||||
stopwatch_element.textContent = format_stopwatch_string(difference);
|
||||
}
|
||||
|
||||
function modify_modal_state(modal_identifier, is_active) {
|
||||
const modal = document.getElementById(modal_identifier);
|
||||
if (modal) {
|
||||
if (is_active) {
|
||||
modal.classList.add('active-modal');
|
||||
} else {
|
||||
modal.classList.remove('active-modal');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function write_text_to_clipboard(text_payload) {
|
||||
if (!navigator.clipboard) {
|
||||
console.error('Clipboard API unvailable.');
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(text_payload).catch(function(error_object) {
|
||||
console.error('Clipboard action failed:', error_object);
|
||||
});
|
||||
}
|
||||
|
||||
/* --- Globale Event-Delegation --- */
|
||||
|
||||
function handle_global_clicks(mouse_event) {
|
||||
const target_element = mouse_event.target;
|
||||
|
||||
const tab_button = target_element.closest('.tab-button');
|
||||
if (tab_button) {
|
||||
const target_identifier = tab_button.getAttribute('data-target');
|
||||
const parent_container = tab_button.closest('.application-content');
|
||||
|
||||
parent_container.querySelectorAll('.tab-button').forEach(function(button) {
|
||||
button.classList.remove('active-tab');
|
||||
});
|
||||
parent_container.querySelectorAll('.tab-content').forEach(function(content) {
|
||||
content.classList.remove('active-content');
|
||||
});
|
||||
|
||||
tab_button.classList.add('active-tab');
|
||||
const target_panel = document.getElementById(target_identifier);
|
||||
if (target_panel) {
|
||||
target_panel.classList.add('active-content');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const action_mapping = {
|
||||
'trigger-elimination': function() { modify_modal_state('elimination-confirm-modal', true); },
|
||||
'cancel-elimination': function() { modify_modal_state('elimination-confirm-modal', false); },
|
||||
'proceed-elimination': function() {
|
||||
if (application_state.websocket_connection) {
|
||||
application_state.websocket_connection.emit('request_elimination');
|
||||
}
|
||||
modify_modal_state('elimination-confirm-modal', false);
|
||||
},
|
||||
'trigger-revoke-elimination': function() {
|
||||
if (application_state.websocket_connection) {
|
||||
application_state.websocket_connection.emit('revoke_elimination_request');
|
||||
}
|
||||
},
|
||||
'confirm-elimination': function() {
|
||||
if (application_state.websocket_connection) {
|
||||
application_state.websocket_connection.emit('confirm_elimination');
|
||||
}
|
||||
modify_modal_state('verification-modal', false);
|
||||
},
|
||||
'reject-elimination': function() {
|
||||
if (application_state.websocket_connection) {
|
||||
application_state.websocket_connection.emit('reject_elimination');
|
||||
}
|
||||
modify_modal_state('verification-modal', false);
|
||||
},
|
||||
'acknowledge-rejection': function() { modify_modal_state('rejection-modal', false); },
|
||||
'trigger-name-change-modal': function() { modify_modal_state('name-change-modal', true); },
|
||||
'cancel-name-change-modal': function() { modify_modal_state('name-change-modal', false); },
|
||||
'trigger-help-modal': function() { modify_modal_state('help-modal', true); },
|
||||
'close-help-modal': function() { modify_modal_state('help-modal', false); },
|
||||
'copy-identifier-button': function(button_element) {
|
||||
const identifier = button_element.getAttribute('data-identifier');
|
||||
write_text_to_clipboard(identifier);
|
||||
const icon = button_element.querySelector('i, svg');
|
||||
if (icon) {
|
||||
icon.setAttribute('data-lucide', 'check');
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
}
|
||||
},
|
||||
'confirm-admin-action': function() {
|
||||
if (application_state.pending_administrator_form) {
|
||||
const time_input = document.getElementById('modal_execution_time');
|
||||
if (time_input && time_input.value) {
|
||||
let hidden_input = application_state.pending_administrator_form.querySelector('input[name="execution_time"]');
|
||||
if (!hidden_input) {
|
||||
hidden_input = document.createElement('input');
|
||||
hidden_input.type = 'hidden';
|
||||
hidden_input.name = 'execution_time';
|
||||
application_state.pending_administrator_form.appendChild(hidden_input);
|
||||
}
|
||||
hidden_input.value = time_input.value;
|
||||
} else {
|
||||
const hidden_input = application_state.pending_administrator_form.querySelector('input[name="execution_time"]');
|
||||
if (hidden_input) {
|
||||
hidden_input.remove();
|
||||
}
|
||||
}
|
||||
execute_asynchronous_form_submission(application_state.pending_administrator_form);
|
||||
application_state.pending_administrator_form = null;
|
||||
modify_modal_state('admin-confirmation-modal', false);
|
||||
}
|
||||
},
|
||||
'cancel-admin-action': function() {
|
||||
application_state.pending_administrator_form = null;
|
||||
modify_modal_state('admin-confirmation-modal', false);
|
||||
}
|
||||
};
|
||||
|
||||
for (const [identifier, action_function] of Object.entries(action_mapping)) {
|
||||
const button = target_element.closest('#' + identifier);
|
||||
if (button) {
|
||||
action_function(button);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handle_global_submits(submit_event) {
|
||||
const target_form = submit_event.target;
|
||||
|
||||
if (target_form.classList.contains('asynchronous-form')) {
|
||||
submit_event.preventDefault();
|
||||
execute_asynchronous_form_submission(target_form);
|
||||
} else if (target_form.classList.contains('admin-asynchronous-form')) {
|
||||
submit_event.preventDefault();
|
||||
application_state.pending_administrator_form = target_form;
|
||||
const time_input = document.getElementById('modal_execution_time');
|
||||
if (time_input) {
|
||||
time_input.value = '';
|
||||
}
|
||||
modify_modal_state('admin-confirmation-modal', true);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Initialisierung --- */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/service_worker.js').catch(function(error_object) {
|
||||
console.error('Service Worker Registration failed:', error_object);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
if (application_state.stopwatch_interval_identifier) {
|
||||
clearInterval(application_state.stopwatch_interval_identifier);
|
||||
}
|
||||
application_state.stopwatch_interval_identifier = setInterval(update_game_stopwatch_ui, 1000);
|
||||
update_game_stopwatch_ui();
|
||||
|
||||
document.addEventListener('click', handle_global_clicks);
|
||||
document.addEventListener('submit', handle_global_submits);
|
||||
|
||||
if (typeof io !== 'undefined') {
|
||||
application_state.websocket_connection = io();
|
||||
|
||||
application_state.websocket_connection.on('game_state_changed', function() {
|
||||
if (document.querySelector('.game-section') || document.querySelector('.administrator-section')) {
|
||||
fetch_and_synchronize_dom();
|
||||
}
|
||||
});
|
||||
|
||||
application_state.websocket_connection.on('admin_state_changed', function() {
|
||||
if (document.querySelector('.administrator-section')) {
|
||||
fetch_and_synchronize_dom();
|
||||
}
|
||||
});
|
||||
|
||||
application_state.websocket_connection.on('elimination_verification_requested', function() {
|
||||
fetch_and_synchronize_dom();
|
||||
setTimeout(function() {
|
||||
modify_modal_state('verification-modal', true);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
application_state.websocket_connection.on('elimination_rejected', function() {
|
||||
modify_modal_state('rejection-modal', true);
|
||||
});
|
||||
|
||||
application_state.websocket_connection.on('elimination_revoked', function() {
|
||||
modify_modal_state('verification-modal', false);
|
||||
fetch_and_synchronize_dom();
|
||||
});
|
||||
}
|
||||
});
|
||||
+9
-2
@@ -1,2 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" overflow="hidden" viewBox="465.8766784667969 137.75999450683594 353.4313049316406 384.6400146484375"><path d="M811.26 275.62 819.31 380.82 714.1 388.88 712.67 370.11 685.88 388.45 696.13 522.4 612.86 522.4 603.1 394.79 564.72 376.36 561.05 382.31C552.25 394.18 538.55 402.31 522.66 403.53 493.61 405.75 468.26 384 466.03 354.95 463.81 325.9 485.56 300.55 514.61 298.32 536.4 296.65 556.11 308.47 565.54 326.74L567.55 331.82 708.91 321 706.05 283.67ZM659.98 137.76 690.31 188.35 668.03 242.97 658.84 243.67 663.52 304.85 612.47 308.76 607.79 247.58 598.6 248.28 568.27 197.69 590.54 143.07Z" fill="oklch(85% 0.15 110)"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="465 137 354 385" width="354" height="385">
|
||||
<style>
|
||||
path {
|
||||
fill: hsl(60 60% 60%);
|
||||
fill: oklch(85% 0.15 110);
|
||||
}
|
||||
</style>
|
||||
<path d="M811.26 275.62 819.31 380.82 714.1 388.88 712.67 370.11 685.88 388.45 696.13 522.4 612.86 522.4 603.1 394.79 564.72 376.36 561.05 382.31C552.25 394.18 538.55 402.31 522.66 403.53 493.61 405.75 468.26 384 466.03 354.95 463.81 325.9 485.56 300.55 514.61 298.32 536.4 296.65 556.11 308.47 565.54 326.74L567.55 331.82 708.91 321 706.05 283.67ZM659.98 137.76 690.31 188.35 668.03 242.97 658.84 243.67 663.52 304.85 612.47 308.76 607.79 247.58 598.6 248.28 568.27 197.69 590.54 143.07Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 718 B |
+368
-259
@@ -1,443 +1,552 @@
|
||||
:root {
|
||||
--lightness-primary: 0.35;
|
||||
--chroma-primary: 0.15;
|
||||
--hue-primary: 15;
|
||||
--fallback-primary: hsl(15, 80%, 45%);
|
||||
--fallback-background: hsl(15, 20%, 95%);
|
||||
--fallback-surface: hsl(0, 0%, 100%);
|
||||
--fallback-text: hsl(250, 20%, 15%);
|
||||
--fallback-static-white: hsl(0, 0%, 100%);
|
||||
--fallback-static-black: hsl(250, 20%, 15%);
|
||||
--fallback-text-inverted: hsl(0, 0%, 100%);
|
||||
--fallback-success: hsl(140, 50%, 45%);
|
||||
--fallback-danger: hsl(25, 80%, 50%);
|
||||
--fallback-warning: hsl(50, 90%, 65%);
|
||||
--fallback-border: hsla(250, 20%, 15%, 0.2);
|
||||
--fallback-border-light: hsla(250, 20%, 15%, 0.1);
|
||||
--fallback-primary-transparent: hsla(15, 80%, 45%, 0.1);
|
||||
--fallback-shadow-primary: hsla(15, 80%, 45%, 0.3);
|
||||
--fallback-overlay: rgba(0, 0, 0, 0.6);
|
||||
|
||||
--lightness-background: 0.9;
|
||||
--chroma-background: 0.01;
|
||||
--hue-background: var(--hue-primary);
|
||||
|
||||
--lightness-surface: 1;
|
||||
--chroma-surface: 0;
|
||||
--hue-surface: 0;
|
||||
|
||||
--lightness-text: 0.15;
|
||||
--chroma-text: 0.05;
|
||||
--hue-text: 250;
|
||||
|
||||
--lightness-success: 0.60;
|
||||
--chroma-success: 0.18;
|
||||
--hue-success: 140;
|
||||
|
||||
--lightness-danger: 0.50;
|
||||
--chroma-danger: 0.22;
|
||||
--hue-danger: 25;
|
||||
|
||||
--lightness-warning: 0.75;
|
||||
--chroma-warning: 0.18;
|
||||
--hue-warning: 70;
|
||||
|
||||
--dimension-multiplier: 1;
|
||||
--spacing-small: calc(0.5rem * var(--dimension-multiplier));
|
||||
--spacing-medium: calc(1rem * var(--dimension-multiplier));
|
||||
--spacing-large: calc(1.5rem * var(--dimension-multiplier));
|
||||
|
||||
--border-radius-small: calc(0.5rem * var(--dimension-multiplier));
|
||||
--border-radius-large: calc(1.25rem * var(--dimension-multiplier));
|
||||
--border-radius-pill: calc(999rem * var(--dimension-multiplier));
|
||||
--lightness-primary: 0.35;
|
||||
--chroma-primary: 0.15;
|
||||
--hue-primary: 15;
|
||||
|
||||
--lightness-background: 0.95;
|
||||
--chroma-background: 0.01;
|
||||
--hue-background: var(--hue-primary);
|
||||
|
||||
--lightness-surface: 1;
|
||||
--chroma-surface: 0;
|
||||
--hue-surface: 0;
|
||||
|
||||
--lightness-text: 0.15;
|
||||
--chroma-text: 0.05;
|
||||
--hue-text: 250;
|
||||
|
||||
--lightness-static-white: 1;
|
||||
--chroma-static-white: 0;
|
||||
--hue-static-white: 0;
|
||||
|
||||
--lightness-static-black: 0.15;
|
||||
--chroma-static-black: 0.05;
|
||||
--hue-static-black: 250;
|
||||
|
||||
--lightness-text-inverted: 1;
|
||||
--chroma-text-inverted: 0;
|
||||
--hue-text-inverted: 0;
|
||||
|
||||
--lightness-success: 0.60;
|
||||
--chroma-success: 0.18;
|
||||
--hue-success: 140;
|
||||
|
||||
--lightness-danger: 0.50;
|
||||
--chroma-danger: 0.22;
|
||||
--hue-danger: 25;
|
||||
|
||||
--lightness-warning: 0.85;
|
||||
--chroma-warning: 0.15;
|
||||
--hue-warning: 80;
|
||||
|
||||
--dimension-multiplier: 1;
|
||||
--spacing-scale-base: 0.25rem;
|
||||
--spacing-small: calc(var(--spacing-scale-base) * 2 * var(--dimension-multiplier));
|
||||
--spacing-medium: calc(var(--spacing-scale-base) * 4 * var(--dimension-multiplier));
|
||||
--spacing-large: calc(var(--spacing-scale-base) * 6 * var(--dimension-multiplier));
|
||||
|
||||
--border-radius-small: calc(0.5rem * var(--dimension-multiplier));
|
||||
--border-radius-large: calc(1.25rem * var(--dimension-multiplier));
|
||||
--border-radius-pill: calc(999rem * var(--dimension-multiplier));
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--lightness-primary: 0.65;
|
||||
--chroma-primary: 0.20;
|
||||
:root {
|
||||
--fallback-primary: hsl(15, 80%, 60%);
|
||||
--fallback-background: hsl(250, 15%, 15%);
|
||||
--fallback-surface: hsl(250, 15%, 20%);
|
||||
--fallback-text: hsl(250, 20%, 90%);
|
||||
--fallback-text-inverted: hsl(0, 0%, 0%);
|
||||
--fallback-border: hsla(250, 20%, 90%, 0.2);
|
||||
--fallback-border-light: hsla(250, 20%, 90%, 0.1);
|
||||
--fallback-primary-transparent: hsla(15, 80%, 60%, 0.1);
|
||||
--fallback-shadow-primary: hsla(15, 80%, 60%, 0.3);
|
||||
|
||||
--lightness-background: 0.2;
|
||||
--chroma-background: 0.02;
|
||||
|
||||
--lightness-surface: 0.18;
|
||||
--chroma-surface: 0.02;
|
||||
|
||||
--lightness-text: 0.90;
|
||||
--chroma-text: 0.02;
|
||||
}
|
||||
--lightness-primary: 0.65;
|
||||
--chroma-primary: 0.20;
|
||||
|
||||
--lightness-background: 0.15;
|
||||
--chroma-background: 0.02;
|
||||
|
||||
--lightness-surface: 0.20;
|
||||
--chroma-surface: 0.02;
|
||||
|
||||
--lightness-text: 0.90;
|
||||
--chroma-text: 0.02;
|
||||
|
||||
--lightness-text-inverted: 0;
|
||||
--chroma-text-inverted: 0;
|
||||
--hue-text-inverted: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
transition-duration: 0s !important;
|
||||
animation-duration: 0s !important;
|
||||
}
|
||||
* {
|
||||
transition-duration: 0s !important;
|
||||
animation-duration: 0s !important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: oklch(var(--lightness-background) var(--chroma-background) var(--hue-background));
|
||||
color: oklch(var(--lightness-text) var(--chroma-text) var(--hue-text));
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
font-size: calc(1rem * var(--dimension-multiplier));
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: var(--fallback-background);
|
||||
background-color: oklch(var(--lightness-background) var(--chroma-background) var(--hue-background));
|
||||
color: var(--fallback-text);
|
||||
color: oklch(var(--lightness-text) var(--chroma-text) var(--hue-text));
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
font-size: calc(1rem * var(--dimension-multiplier));
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.brand-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.brand-link:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.application-header {
|
||||
background-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
color: oklch(1 0 0);
|
||||
padding: var(--spacing-medium) var(--spacing-large);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background-color: var(--fallback-primary);
|
||||
background-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
color: var(--fallback-text-inverted);
|
||||
color: oklch(var(--lightness-text-inverted) var(--chroma-text-inverted) var(--hue-text-inverted));
|
||||
padding: var(--spacing-medium) var(--spacing-large);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 4px 12px var(--fallback-border-light);
|
||||
box-shadow: 0 4px 12px oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.brand-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-small);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-small);
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
width: calc(2rem * var(--dimension-multiplier));
|
||||
height: calc(2rem * var(--dimension-multiplier));
|
||||
color: currentColor;
|
||||
width: calc(2rem * var(--dimension-multiplier));
|
||||
height: calc(2rem * var(--dimension-multiplier));
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.application-title {
|
||||
margin: 0;
|
||||
font-size: calc(1.25rem * var(--dimension-multiplier));
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
margin: 0;
|
||||
font-size: calc(1.25rem * var(--dimension-multiplier));
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.application-content {
|
||||
flex: 1;
|
||||
padding: var(--spacing-large);
|
||||
width: 100%;
|
||||
max-width: calc(48rem * var(--dimension-multiplier));
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
padding: var(--spacing-large);
|
||||
width: 100%;
|
||||
max-width: calc(48rem * var(--dimension-multiplier));
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
background-color: oklch(var(--lightness-surface) var(--chroma-surface) var(--hue-surface));
|
||||
padding: var(--spacing-large);
|
||||
border-radius: var(--border-radius-large);
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.01);
|
||||
margin-bottom: var(--spacing-large);
|
||||
background-color: var(--fallback-surface);
|
||||
background-color: oklch(var(--lightness-surface) var(--chroma-surface) var(--hue-surface));
|
||||
padding: var(--spacing-large);
|
||||
border-radius: var(--border-radius-large);
|
||||
box-shadow: 0 10px 25px -5px var(--fallback-border-light), 0 8px 10px -6px var(--fallback-border-light);
|
||||
box-shadow: 0 10px 25px -5px oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.05), 0 8px 10px -6px oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.01);
|
||||
margin-bottom: var(--spacing-large);
|
||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacing-medium);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-small);
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacing-medium);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-small);
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacing-small);
|
||||
opacity: 0.7;
|
||||
text-transform: uppercase;
|
||||
font-size: calc(0.875rem * var(--dimension-multiplier));
|
||||
letter-spacing: 0.05em;
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacing-small);
|
||||
opacity: 0.7;
|
||||
text-transform: uppercase;
|
||||
font-size: calc(0.875rem * var(--dimension-multiplier));
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.input-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-medium);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-medium);
|
||||
}
|
||||
|
||||
.inline-form {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.flexible_form_container {
|
||||
display: flex;
|
||||
gap: calc(0.5rem * var(--dimension-multiplier));
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
gap: calc(0.5rem * var(--dimension-multiplier));
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(0.25rem * var(--dimension-multiplier));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(0.25rem * var(--dimension-multiplier));
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-weight: bold;
|
||||
font-size: calc(0.875rem * var(--dimension-multiplier));
|
||||
font-weight: bold;
|
||||
font-size: calc(0.875rem * var(--dimension-multiplier));
|
||||
}
|
||||
|
||||
.text-input {
|
||||
padding: var(--spacing-medium);
|
||||
border: 2px solid oklch(0.5 0 0 / 0.2);
|
||||
border-radius: var(--border-radius-small);
|
||||
background-color: oklch(var(--lightness-background) var(--chroma-background) var(--hue-background));
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
transition: border-color 0.2s;
|
||||
padding: var(--spacing-medium);
|
||||
border: 2px solid var(--fallback-border);
|
||||
border: 2px solid oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.2);
|
||||
border-radius: var(--border-radius-small);
|
||||
background-color: var(--fallback-background);
|
||||
background-color: oklch(var(--lightness-background) var(--chroma-background) var(--hue-background));
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
transition: border-color 0.2s ease, background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.text-input:focus {
|
||||
outline: none;
|
||||
border-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
outline: none;
|
||||
border-color: var(--fallback-primary);
|
||||
border-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
}
|
||||
|
||||
.small-input {
|
||||
width: calc(12rem * var(--dimension-multiplier));
|
||||
padding: var(--spacing-small);
|
||||
width: calc(12rem * var(--dimension-multiplier));
|
||||
padding: var(--spacing-small);
|
||||
}
|
||||
|
||||
.time-input {
|
||||
width: calc(13rem * var(--dimension-multiplier));
|
||||
width: calc(13rem * var(--dimension-multiplier));
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: var(--spacing-medium) var(--spacing-large);
|
||||
border: none;
|
||||
border-radius: var(--border-radius-pill);
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-small);
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: oklch(1 0 0);
|
||||
padding: var(--spacing-medium) var(--spacing-large);
|
||||
border: none;
|
||||
border-radius: var(--border-radius-pill);
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-small);
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: var(--fallback-text-inverted);
|
||||
color: oklch(var(--lightness-text-inverted) var(--chroma-text-inverted) var(--hue-text-inverted));
|
||||
}
|
||||
|
||||
.action-button:hover, .action-button:focus {
|
||||
filter: brightness(1.1);
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
padding: calc(0.5rem * var(--dimension-multiplier));
|
||||
border-radius: var(--border-radius-small);
|
||||
padding: calc(0.5rem * var(--dimension-multiplier));
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
box-shadow: 0 4px 12px oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary) / 0.3);
|
||||
background-color: var(--fallback-primary);
|
||||
background-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
box-shadow: 0 4px 12px var(--fallback-shadow-primary);
|
||||
box-shadow: 0 4px 12px oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary) / 0.3);
|
||||
}
|
||||
|
||||
.success-button {
|
||||
background-color: oklch(var(--lightness-success) var(--chroma-success) var(--hue-success));
|
||||
background-color: var(--fallback-success);
|
||||
background-color: oklch(var(--lightness-success) var(--chroma-success) var(--hue-success));
|
||||
}
|
||||
|
||||
.danger-button {
|
||||
background-color: oklch(var(--lightness-danger) var(--chroma-danger) var(--hue-danger));
|
||||
background-color: var(--fallback-danger);
|
||||
background-color: oklch(var(--lightness-danger) var(--chroma-danger) var(--hue-danger));
|
||||
}
|
||||
|
||||
.warning-button {
|
||||
background-color: oklch(var(--lightness-warning) var(--chroma-warning) var(--hue-warning));
|
||||
color: oklch(0.2 0 0);
|
||||
background-color: var(--fallback-warning);
|
||||
background-color: oklch(var(--lightness-warning) var(--chroma-warning) var(--hue-warning));
|
||||
color: var(--fallback-static-black);
|
||||
color: oklch(var(--lightness-static-black) var(--chroma-static-black) var(--hue-static-black));
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
background-color: transparent;
|
||||
color: oklch(var(--lightness-text) var(--chroma-text) var(--hue-text));
|
||||
border: 2px solid oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.2);
|
||||
background-color: transparent;
|
||||
color: var(--fallback-text);
|
||||
color: oklch(var(--lightness-text) var(--chroma-text) var(--hue-text));
|
||||
border: 2px solid var(--fallback-border);
|
||||
border: 2px solid oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.2);
|
||||
}
|
||||
|
||||
.large-button {
|
||||
padding: var(--spacing-large) calc(2.5rem * var(--dimension-multiplier));
|
||||
font-size: calc(1.125rem * var(--dimension-multiplier));
|
||||
width: 100%;
|
||||
padding: var(--spacing-large) calc(2.5rem * var(--dimension-multiplier));
|
||||
font-size: calc(1.125rem * var(--dimension-multiplier));
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.small-button {
|
||||
padding: calc(0.25rem * var(--dimension-multiplier)) calc(0.75rem * var(--dimension-multiplier));
|
||||
font-size: calc(0.875rem * var(--dimension-multiplier));
|
||||
padding: calc(0.25rem * var(--dimension-multiplier)) calc(0.75rem * var(--dimension-multiplier));
|
||||
font-size: calc(0.875rem * var(--dimension-multiplier));
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
background-color: oklch(var(--lightness-surface) var(--chroma-surface) var(--hue-surface));
|
||||
border-radius: var(--border-radius-pill);
|
||||
padding: calc(0.375rem * var(--dimension-multiplier));
|
||||
gap: calc(0.25rem * var(--dimension-multiplier));
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
margin-bottom: var(--spacing-large);
|
||||
overflow-x: auto;
|
||||
display: flex;
|
||||
background-color: var(--fallback-surface);
|
||||
background-color: oklch(var(--lightness-surface) var(--chroma-surface) var(--hue-surface));
|
||||
border-radius: var(--border-radius-pill);
|
||||
padding: calc(0.375rem * var(--dimension-multiplier));
|
||||
gap: calc(0.25rem * var(--dimension-multiplier));
|
||||
box-shadow: 0 4px 12px var(--fallback-border-light);
|
||||
box-shadow: 0 4px 12px oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.05);
|
||||
margin-bottom: var(--spacing-large);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
flex: 1;
|
||||
border-radius: var(--border-radius-pill);
|
||||
padding: calc(0.625rem * var(--dimension-multiplier)) var(--spacing-medium);
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: calc(0.5rem * var(--dimension-multiplier));
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
flex: 1;
|
||||
border-radius: var(--border-radius-pill);
|
||||
padding: calc(0.625rem * var(--dimension-multiplier)) var(--spacing-medium);
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: calc(0.5rem * var(--dimension-multiplier));
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.active-tab {
|
||||
background-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
color: oklch(1 0 0);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
background-color: var(--fallback-primary);
|
||||
background-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
color: var(--fallback-text-inverted);
|
||||
color: oklch(var(--lightness-text-inverted) var(--chroma-text-inverted) var(--hue-text-inverted));
|
||||
box-shadow: 0 2px 8px var(--fallback-border-light);
|
||||
box-shadow: 0 2px 8px oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.15);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.active-content {
|
||||
display: block;
|
||||
display: block;
|
||||
animation: fade-in 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; transform: translateY(calc(0.25rem * var(--dimension-multiplier))); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: calc(0.375rem * var(--dimension-multiplier));
|
||||
padding: calc(0.25rem * var(--dimension-multiplier)) calc(0.875rem * var(--dimension-multiplier));
|
||||
border-radius: var(--border-radius-pill);
|
||||
font-size: calc(0.875rem * var(--dimension-multiplier));
|
||||
font-weight: bold;
|
||||
color: oklch(1 0 0);
|
||||
margin-left: var(--spacing-small);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: calc(0.375rem * var(--dimension-multiplier));
|
||||
padding: calc(0.25rem * var(--dimension-multiplier)) calc(0.875rem * var(--dimension-multiplier));
|
||||
border-radius: var(--border-radius-pill);
|
||||
font-size: calc(0.875rem * var(--dimension-multiplier));
|
||||
font-weight: bold;
|
||||
color: var(--fallback-static-white);
|
||||
color: oklch(var(--lightness-static-white) var(--chroma-static-white) var(--hue-static-white));
|
||||
margin-left: var(--spacing-small);
|
||||
}
|
||||
|
||||
.warning-pill {
|
||||
background-color: oklch(var(--lightness-warning) var(--chroma-warning) var(--hue-warning));
|
||||
color: oklch(0.2 0 0);
|
||||
background-color: var(--fallback-warning);
|
||||
background-color: oklch(var(--lightness-warning) var(--chroma-warning) var(--hue-warning));
|
||||
color: var(--fallback-static-black);
|
||||
color: oklch(var(--lightness-static-black) var(--chroma-static-black) var(--hue-static-black));
|
||||
}
|
||||
|
||||
.success-pill {
|
||||
background-color: oklch(var(--lightness-success) var(--chroma-success) var(--hue-success));
|
||||
background-color: var(--fallback-success);
|
||||
background-color: oklch(var(--lightness-success) var(--chroma-success) var(--hue-success));
|
||||
}
|
||||
|
||||
.danger-pill {
|
||||
background-color: oklch(var(--lightness-danger) var(--chroma-danger) var(--hue-danger));
|
||||
background-color: var(--fallback-danger);
|
||||
background-color: oklch(var(--lightness-danger) var(--chroma-danger) var(--hue-danger));
|
||||
}
|
||||
|
||||
.notice-card {
|
||||
border-left: 6px solid oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
border-left: calc(0.375rem * var(--dimension-multiplier)) solid var(--fallback-primary);
|
||||
border-left: calc(0.375rem * var(--dimension-multiplier)) solid oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
}
|
||||
|
||||
.danger-card {
|
||||
border-left-color: oklch(var(--lightness-danger) var(--chroma-danger) var(--hue-danger));
|
||||
border-left-color: var(--fallback-danger);
|
||||
border-left-color: oklch(var(--lightness-danger) var(--chroma-danger) var(--hue-danger));
|
||||
}
|
||||
|
||||
.success-card {
|
||||
border-left-color: oklch(var(--lightness-success) var(--chroma-success) var(--hue-success));
|
||||
border-left-color: var(--fallback-success);
|
||||
border-left-color: oklch(var(--lightness-success) var(--chroma-success) var(--hue-success));
|
||||
}
|
||||
|
||||
.warning-card {
|
||||
border-left-color: oklch(var(--lightness-warning) var(--chroma-warning) var(--hue-warning));
|
||||
border-left-color: var(--fallback-warning);
|
||||
border-left-color: oklch(var(--lightness-warning) var(--chroma-warning) var(--hue-warning));
|
||||
}
|
||||
|
||||
.target-name {
|
||||
font-size: calc(2.5rem * var(--dimension-multiplier));
|
||||
text-align: center;
|
||||
margin: var(--spacing-large) 0;
|
||||
color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
font-weight: 800;
|
||||
font-size: calc(2.5rem * var(--dimension-multiplier));
|
||||
text-align: center;
|
||||
margin: var(--spacing-large) 0;
|
||||
color: var(--fallback-primary);
|
||||
color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th, .data-table td {
|
||||
padding: var(--spacing-medium);
|
||||
border-bottom: 1px solid oklch(0.5 0 0 / 0.1);
|
||||
text-align: left;
|
||||
padding: var(--spacing-medium);
|
||||
border-bottom: 1px solid var(--fallback-border-light);
|
||||
border-bottom: 1px solid oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.1);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
font-size: calc(0.75rem * var(--dimension-multiplier));
|
||||
letter-spacing: 0.05em;
|
||||
opacity: 0.7;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
font-size: calc(0.75rem * var(--dimension-multiplier));
|
||||
letter-spacing: 0.05em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.highlighted-row {
|
||||
background-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary) / 0.1);
|
||||
background-color: var(--fallback-primary-transparent);
|
||||
background-color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary) / 0.1);
|
||||
}
|
||||
|
||||
.highlighted-row td {
|
||||
font-weight: bold;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.unregistered-row {
|
||||
opacity: 0.6;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.markdown-container h1, .markdown-container h2, .markdown-container h3 {
|
||||
color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
color: var(--fallback-primary);
|
||||
color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary));
|
||||
}
|
||||
|
||||
.markdown-container ul {
|
||||
padding-left: var(--spacing-large);
|
||||
padding-left: var(--spacing-large);
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--fallback-overlay);
|
||||
background-color: oklch(0 0 0 / 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.active-modal {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
background-color: oklch(var(--lightness-surface) var(--chroma-surface) var(--hue-surface));
|
||||
padding: var(--spacing-large);
|
||||
border-radius: var(--border-radius-large);
|
||||
max-width: calc(32rem * var(--dimension-multiplier));
|
||||
width: 90%;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
|
||||
background-color: var(--fallback-surface);
|
||||
background-color: oklch(var(--lightness-surface) var(--chroma-surface) var(--hue-surface));
|
||||
padding: var(--spacing-large);
|
||||
border-radius: var(--border-radius-large);
|
||||
max-width: calc(32rem * var(--dimension-multiplier));
|
||||
width: 90%;
|
||||
box-shadow: 0 20px 40px var(--fallback-border);
|
||||
box-shadow: 0 20px 40px oklch(0 0 0 / 0.2);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-small);
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-small);
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-medium);
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--spacing-large);
|
||||
display: flex;
|
||||
gap: var(--spacing-medium);
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--spacing-large);
|
||||
}
|
||||
|
||||
.qr-code-container {
|
||||
background-color: oklch(1 0 0);
|
||||
padding: var(--spacing-medium);
|
||||
border-radius: var(--border-radius-large);
|
||||
display: inline-block;
|
||||
margin-bottom: var(--spacing-medium);
|
||||
box-shadow: inset 0 0 0 2px oklch(0.5 0 0 / 0.1);
|
||||
background-color: var(--fallback-static-white);
|
||||
background-color: oklch(var(--lightness-static-white) var(--chroma-static-white) var(--hue-static-white));
|
||||
padding: var(--spacing-medium);
|
||||
border-radius: var(--border-radius-large);
|
||||
display: inline-block;
|
||||
margin-bottom: var(--spacing-medium);
|
||||
box-shadow: inset 0 0 0 2px var(--fallback-border-light);
|
||||
box-shadow: inset 0 0 0 2px oklch(var(--lightness-text) var(--chroma-text) var(--hue-text) / 0.1);
|
||||
}
|
||||
|
||||
.qr-code-container svg {
|
||||
display: block;
|
||||
width: calc(14rem * var(--dimension-multiplier));
|
||||
height: calc(14rem * var(--dimension-multiplier));
|
||||
display: block;
|
||||
width: calc(14rem * var(--dimension-multiplier));
|
||||
height: calc(14rem * var(--dimension-multiplier));
|
||||
}
|
||||
|
||||
.qr-code-container svg path {
|
||||
fill: var(--fallback-static-black);
|
||||
fill: oklch(var(--lightness-static-black) var(--chroma-static-black) var(--hue-static-black));
|
||||
}
|
||||
|
||||
.monospace-identifier {
|
||||
font-family: monospace;
|
||||
font-family: monospace;
|
||||
}
|
||||
+343
-343
@@ -1,348 +1,348 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<section class="administrator-section">
|
||||
{% if setup_required %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="settings"></i> Administrator Einrichtung</h2>
|
||||
<form action="{{ url_for('handle_administrator') }}" method="POST" class="input-form">
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Neues Administrator-Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string_confirmation" class="input-label">Passwort wiederholen</label>
|
||||
<input type="password" id="password_string_confirmation" name="password_string_confirmation" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="check"></i> Einrichtung abschließen</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif login_required %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="shield"></i> Administrator Anmeldung</h2>
|
||||
<form action="{{ url_for('handle_administrator') }}" method="POST" class="input-form">
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
{% if two_factor_enabled %}
|
||||
<div class="form-group">
|
||||
<label for="two_factor_code" class="input-label">Zwei-Faktor Code</label>
|
||||
<input type="text" id="two_factor_code" name="two_factor_code" class="text-input" required>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="log-in"></i> Anmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif dashboard_active %}
|
||||
<div class="tab-container">
|
||||
<button class="tab-button active-tab" data-target="control-panel"><i data-lucide="sliders"></i> Steuerung</button>
|
||||
<button class="tab-button" data-target="player-panel"><i data-lucide="users"></i> Spieler</button>
|
||||
<button class="tab-button" data-target="kill-panel"><i data-lucide="message-square"></i> Meldungen</button>
|
||||
</div>
|
||||
|
||||
<div id="control-panel" class="tab-content active-content">
|
||||
<div class="content-card status-card">
|
||||
<h3 class="card-title" style="display: flex; align-items: center; gap: var(--spacing-small);">
|
||||
<i data-lucide="activity"></i> Spielstatus:
|
||||
{% if game_status == 'pending' %}
|
||||
<span class="status-pill warning-pill"><i data-lucide="clock"></i> Ausstehend</span>
|
||||
{% elif game_status == 'running' %}
|
||||
<span class="status-pill success-pill"><i data-lucide="play"></i> Läuft</span>
|
||||
{% elif game_status == 'paused' %}
|
||||
<span class="status-pill warning-pill"><i data-lucide="pause"></i> Pausiert</span>
|
||||
{% elif game_status == 'finished' %}
|
||||
<span class="status-pill danger-pill"><i data-lucide="square"></i> Beendet</span>
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
||||
{% if game_status == 'pending' %}
|
||||
{% if can_start %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="start_game">
|
||||
<button type="submit" class="action-button success-button large-button"><i data-lucide="play"></i> Spiel starten</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<p class="notice-text">Es werden mindestens zwei freigegebene Spieler benötigt, um das Spiel zu starten.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elif game_status == 'running' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form" style="margin-bottom: var(--spacing-medium);">
|
||||
<input type="hidden" name="action_type" value="pause_game">
|
||||
<button type="submit" class="action-button warning-button large-button"><i data-lucide="pause"></i> Spiel pausieren</button>
|
||||
</form>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="stop_game">
|
||||
<button type="submit" class="action-button danger-button large-button"><i data-lucide="square"></i> Spiel beenden</button>
|
||||
</form>
|
||||
{% elif game_status == 'paused' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form" style="margin-bottom: var(--spacing-medium);">
|
||||
<input type="hidden" name="action_type" value="resume_game">
|
||||
<button type="submit" class="action-button success-button large-button"><i data-lucide="play"></i> Spiel fortsetzen</button>
|
||||
</form>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="stop_game">
|
||||
<button type="submit" class="action-button danger-button large-button"><i data-lucide="square"></i> Spiel beenden</button>
|
||||
</form>
|
||||
{% elif game_status == 'finished' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="reset_game">
|
||||
<button type="submit" class="action-button danger-button large-button"><i data-lucide="rotate-ccw"></i> Spiel zurücksetzen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="content-card settings-card">
|
||||
<h3 class="card-title"><i data-lucide="shield-check"></i> Sicherheit</h3>
|
||||
|
||||
{% if two_factor_enabled %}
|
||||
<div class="content-card notice-card success-card">
|
||||
<p class="notice-text">Zwei-Faktor-Authentifizierung ist aktiv.</p>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="inline-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="disable_two_factor">
|
||||
<button type="submit" class="action-button danger-button"><i data-lucide="shield-off"></i> Deaktivieren</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<p class="notice-text">Zwei-Faktor-Authentifizierung ist inaktiv. Bitte scanne den QR-Code und bestätige zur Aktivierung.</p>
|
||||
<p>Manuelle Eingabe: <strong>{{ two_factor_secret }}</strong></p>
|
||||
<div class="qr-code-container">
|
||||
{{ qr_code_svg | safe }}
|
||||
</div>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="enable_two_factor">
|
||||
<div class="form-group">
|
||||
<label for="enable_two_factor_code" class="input-label">Bestätigungscode</label>
|
||||
<input type="text" id="enable_two_factor_code" name="two_factor_code" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button success-button"><i data-lucide="shield-alert"></i> Aktivieren</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form" style="margin-top: var(--spacing-large);">
|
||||
<input type="hidden" name="action_type" value="update_credentials">
|
||||
<div class="form-group">
|
||||
<label for="new_password_string" class="input-label">Passwort ändern</label>
|
||||
<input type="password" id="new_password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="update_password_confirmation" class="input-label">Passwort wiederholen</label>
|
||||
<input type="password" id="update_password_confirmation" name="password_string_confirmation" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="save"></i> Aktualisieren</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="player-panel" class="tab-content">
|
||||
<div class="content-card">
|
||||
<h3 class="card-title"><i data-lucide="user-plus"></i> Spieler manuell erstellen</h3>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="create_player">
|
||||
<div class="form-group">
|
||||
<label for="display_name" class="input-label">Anzeigename</label>
|
||||
<input type="text" id="display_name" name="display_name" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="plus"></i> Spieler erstellen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="content-card" style="overflow-x: auto;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kennung</th>
|
||||
<th>Name & Änderung</th>
|
||||
<th>Status</th>
|
||||
<th>Ziel</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for current_player in all_players %}
|
||||
<tr class="{% if not current_player.is_approved %}unregistered-row{% endif %}">
|
||||
<td class="monospace-identifier">
|
||||
{{ current_player.player_identifier }}
|
||||
{% if not current_player.is_approved %}
|
||||
<br><span class="status-pill warning-pill" style="margin-left: 0; margin-top: var(--spacing-small);">Wartet</span>
|
||||
{% elif current_player.late_joiner %}
|
||||
<br><span class="status-pill warning-pill" style="margin-left: 0; margin-top: var(--spacing-small);">Späteinstieg</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="edit_name">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<input type="text" name="new_display_name" value="{{ current_player.display_name }}" class="text-input small-input" required>
|
||||
<button type="submit" class="action-button primary-button small-button"><i data-lucide="save"></i></button>
|
||||
</form>
|
||||
{% if current_player.pending_display_name %}
|
||||
<div class="content-card notice-card warning-card" style="margin-top: var(--spacing-small); padding: var(--spacing-small); margin-bottom: 0;">
|
||||
<p style="margin: 0 0 var(--spacing-small) 0; font-size: calc(0.875rem * var(--dimension-multiplier));">
|
||||
Wünscht: <strong>{{ current_player.pending_display_name }}</strong>
|
||||
</p>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-bottom: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="approve_name_change">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button success-button small-button"><i data-lucide="check"></i></button>
|
||||
</form>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="reject_name_change">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button danger-button small-button"><i data-lucide="x"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not current_player.is_approved %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="approve_player">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button success-button small-button"><i data-lucide="user-check"></i> Freigeben</button>
|
||||
</form>
|
||||
{% else %}
|
||||
{% if current_player.is_alive %}
|
||||
<span class="status-pill success-pill" style="margin-left: 0;"><i data-lucide="heart"></i> Lebend</span>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="eliminate_player">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button danger-button small-button"><i data-lucide="skull"></i> Töten</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<span class="status-pill danger-pill" style="margin-left: 0;"><i data-lucide="skull"></i> Tot</span>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="revive_player">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button success-button small-button"><i data-lucide="heart-pulse"></i> Beleben</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if current_player.target_identifier %}
|
||||
{{ current_player.target_identifier }}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="change_target">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<select name="new_target_identifier" class="text-input small-input">
|
||||
{% for possible_target in all_players %}
|
||||
{% if possible_target.is_alive and possible_target.player_identifier != current_player.player_identifier %}
|
||||
<option value="{{ possible_target.player_identifier }}" {% if current_player.target_identifier == possible_target.player_identifier %}selected{% endif %}>{{ possible_target.display_name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="action-button secondary-button small-button"><i data-lucide="refresh-cw"></i></button>
|
||||
</form>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="generate_reset">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button secondary-button small-button"><i data-lucide="link"></i> Reset Link</button>
|
||||
</form>
|
||||
{% if current_player.reset_token %}
|
||||
<input type="text" readonly value="{{ url_for('handle_password_reset', token=current_player.reset_token, _external=True) }}" class="text-input small-input" style="margin: var(--spacing-small) 0;">
|
||||
{% endif %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="delete_player">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button danger-button small-button"><i data-lucide="trash-2"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kill-panel" class="tab-content">
|
||||
<div class="content-card" style="overflow-x: auto;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zeitpunkt</th>
|
||||
<th>Killer</th>
|
||||
<th>Opfer</th>
|
||||
<th>Status</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for report in kill_logs %}
|
||||
<tr>
|
||||
<td>{{ report.kill_timestamp[:16].replace('T', ' ') if report.kill_timestamp else '-' }}</td>
|
||||
<td>{{ report.killer_name or 'Unbekannt' }} ({{ report.killer_identifier }})</td>
|
||||
<td>{{ report.victim_name or 'Unbekannt' }} ({{ report.victim_identifier }})</td>
|
||||
<td>
|
||||
{% if report.report_status == 'confirmed' %}
|
||||
<span class="status-pill success-pill" style="margin-left: 0;">Bestätigt</span>
|
||||
{% elif report.report_status == 'pending' %}
|
||||
<span class="status-pill warning-pill" style="margin-left: 0;">Ausstehend</span>
|
||||
{% elif report.report_status == 'rejected' %}
|
||||
<span class="status-pill danger-pill" style="margin-left: 0;">Abgelehnt</span>
|
||||
{% elif report.report_status == 'revoked' %}
|
||||
<span class="status-pill danger-pill" style="margin-left: 0;">Widerrufen</span>
|
||||
{% else %}
|
||||
{{ report.report_status }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if report.report_status == 'confirmed' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="revoke_kill">
|
||||
<input type="hidden" name="kill_identifier" value="{{ report.kill_identifier }}">
|
||||
<button type="submit" class="action-button danger-button small-button"><i data-lucide="rotate-ccw"></i> Rückgängig</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if report.report_status == 'pending' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="force_confirm_report">
|
||||
<input type="hidden" name="kill_identifier" value="{{ report.kill_identifier }}">
|
||||
<button type="submit" class="action-button success-button small-button"><i data-lucide="check"></i> Bestätigen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if report.report_status in ['pending', 'rejected', 'revoked'] %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="delete_report">
|
||||
<input type="hidden" name="kill_identifier" value="{{ report.kill_identifier }}">
|
||||
<button type="submit" class="action-button secondary-button small-button"><i data-lucide="trash-2"></i> Löschen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center; opacity: 0.6;">Bisher keine Meldungen im System.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="admin-confirmation-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="calendar-clock"></i> Aktion planen</h2>
|
||||
<p class="modal-description">Führe die Aktion sofort aus oder plane sie für die Zukunft.</p>
|
||||
<div class="form-group" style="margin-bottom: var(--spacing-large); text-align: left;">
|
||||
<label for="modal_execution_time" class="input-label">Zeitpunkt (Optional)</label>
|
||||
<input type="datetime-local" id="modal_execution_time" class="text-input time-input" style="width: 100%; box-sizing: border-box;">
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="confirm-admin-action" class="action-button success-button"><i data-lucide="check"></i> Bestätigen</button>
|
||||
<button type="button" id="cancel-admin-action" class="action-button secondary-button"><i data-lucide="x"></i> Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if setup_required %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="settings"></i> Einrichtung</h2>
|
||||
<form action="{{ url_for('handle_administrator') }}" method="POST" class="input-form">
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Neues Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string_confirmation" class="input-label">Passwort bestätigen</label>
|
||||
<input type="password" id="password_string_confirmation" name="password_string_confirmation" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="check"></i> Abschließen</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif login_required %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="shield"></i> Admin-Login</h2>
|
||||
<form action="{{ url_for('handle_administrator') }}" method="POST" class="input-form">
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
{% if two_factor_enabled %}
|
||||
<div class="form-group">
|
||||
<label for="two_factor_code" class="input-label">2FA-Code</label>
|
||||
<input type="text" id="two_factor_code" name="two_factor_code" class="text-input" required>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="log-in"></i> Anmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif dashboard_active %}
|
||||
<div class="tab-container">
|
||||
<button class="tab-button active-tab" data-target="control-panel"><i data-lucide="sliders"></i> Steuerung</button>
|
||||
<button class="tab-button" data-target="player-panel"><i data-lucide="users"></i> Spieler</button>
|
||||
<button class="tab-button" data-target="kill-panel"><i data-lucide="message-square"></i> Meldungen</button>
|
||||
</div>
|
||||
|
||||
<div id="control-panel" class="tab-content active-content">
|
||||
<div class="content-card status-card">
|
||||
<h3 class="card-title" style="display: flex; align-items: center; gap: var(--spacing-small);">
|
||||
<i data-lucide="activity"></i> Spielstatus:
|
||||
{% if game_status == 'pending' %}
|
||||
<span class="status-pill warning-pill"><i data-lucide="clock"></i> Ausstehend</span>
|
||||
{% elif game_status == 'running' %}
|
||||
<span class="status-pill success-pill"><i data-lucide="play"></i> Läuft</span>
|
||||
{% elif game_status == 'paused' %}
|
||||
<span class="status-pill warning-pill"><i data-lucide="pause"></i> Pausiert</span>
|
||||
{% elif game_status == 'finished' %}
|
||||
<span class="status-pill danger-pill"><i data-lucide="square"></i> Beendet</span>
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
||||
{% if game_status == 'pending' %}
|
||||
{% if can_start %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="start_game">
|
||||
<button type="submit" class="action-button success-button large-button"><i data-lucide="play"></i> Spiel starten</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<p class="notice-text">Zu wenige Spieler zum Starten.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elif game_status == 'running' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form" style="margin-bottom: var(--spacing-medium);">
|
||||
<input type="hidden" name="action_type" value="pause_game">
|
||||
<button type="submit" class="action-button warning-button large-button"><i data-lucide="pause"></i> Spiel pausieren</button>
|
||||
</form>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="stop_game">
|
||||
<button type="submit" class="action-button danger-button large-button"><i data-lucide="square"></i> Spiel beenden</button>
|
||||
</form>
|
||||
{% elif game_status == 'paused' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form" style="margin-bottom: var(--spacing-medium);">
|
||||
<input type="hidden" name="action_type" value="resume_game">
|
||||
<button type="submit" class="action-button success-button large-button"><i data-lucide="play"></i> Spiel fortsetzen</button>
|
||||
</form>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="stop_game">
|
||||
<button type="submit" class="action-button danger-button large-button"><i data-lucide="square"></i> Spiel beenden</button>
|
||||
</form>
|
||||
{% elif game_status == 'finished' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="reset_game">
|
||||
<button type="submit" class="action-button danger-button large-button"><i data-lucide="rotate-ccw"></i> Spiel zurücksetzen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="content-card settings-card">
|
||||
<h3 class="card-title"><i data-lucide="shield-check"></i> Sicherheit</h3>
|
||||
|
||||
{% if two_factor_enabled %}
|
||||
<div class="content-card notice-card success-card">
|
||||
<p class="notice-text">2FA ist aktiv.</p>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="inline-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="disable_two_factor">
|
||||
<button type="submit" class="action-button danger-button"><i data-lucide="shield-off"></i> Deaktivieren</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<p class="notice-text">2FA inaktiv. QR-Code scannen.</p>
|
||||
<p>Code: <strong>{{ two_factor_secret }}</strong></p>
|
||||
<div class="qr-code-container">
|
||||
{{ qr_code_svg | safe }}
|
||||
</div>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="enable_two_factor">
|
||||
<div class="form-group">
|
||||
<label for="enable_two_factor_code" class="input-label">Bestätigungscode</label>
|
||||
<input type="text" id="enable_two_factor_code" name="two_factor_code" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button success-button"><i data-lucide="shield-alert"></i> Aktivieren</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form" style="margin-top: var(--spacing-large);">
|
||||
<input type="hidden" name="action_type" value="update_credentials">
|
||||
<div class="form-group">
|
||||
<label for="new_password_string" class="input-label">Neues Passwort</label>
|
||||
<input type="password" id="new_password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="update_password_confirmation" class="input-label">Passwort bestätigen</label>
|
||||
<input type="password" id="update_password_confirmation" name="password_string_confirmation" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="save"></i> Speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="player-panel" class="tab-content">
|
||||
<div class="content-card">
|
||||
<h3 class="card-title"><i data-lucide="user-plus"></i> Spieler erstellen</h3>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="input-form admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="create_player">
|
||||
<div class="form-group">
|
||||
<label for="display_name" class="input-label">Name</label>
|
||||
<input type="text" id="display_name" name="display_name" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="plus"></i> Erstellen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="content-card" style="overflow-x: auto;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kennung</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Ziel</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for current_player in all_players %}
|
||||
<tr class="{% if not current_player.is_approved %}unregistered-row{% endif %}">
|
||||
<td class="monospace-identifier">
|
||||
{{ current_player.player_identifier }}
|
||||
{% if not current_player.is_approved %}
|
||||
<br><span class="status-pill warning-pill" style="margin-left: 0; margin-top: var(--spacing-small);">Wartet</span>
|
||||
{% elif current_player.late_joiner %}
|
||||
<br><span class="status-pill warning-pill" style="margin-left: 0; margin-top: var(--spacing-small);">Späteinstieg</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="edit_name">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<input type="text" name="new_display_name" value="{{ current_player.display_name }}" class="text-input small-input" required>
|
||||
<button type="submit" class="action-button primary-button small-button"><i data-lucide="save"></i></button>
|
||||
</form>
|
||||
{% if current_player.pending_display_name %}
|
||||
<div class="content-card notice-card warning-card" style="margin-top: var(--spacing-small); padding: var(--spacing-small); margin-bottom: 0;">
|
||||
<p style="margin: 0 0 var(--spacing-small) 0; font-size: calc(0.875rem * var(--dimension-multiplier));">
|
||||
-> <strong>{{ current_player.pending_display_name }}</strong>
|
||||
</p>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-bottom: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="approve_name_change">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button success-button small-button"><i data-lucide="check"></i></button>
|
||||
</form>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="reject_name_change">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button danger-button small-button"><i data-lucide="x"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not current_player.is_approved %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="approve_player">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button success-button small-button"><i data-lucide="user-check"></i> Freigeben</button>
|
||||
</form>
|
||||
{% else %}
|
||||
{% if current_player.is_alive %}
|
||||
<span class="status-pill success-pill" style="margin-left: 0;"><i data-lucide="heart"></i> Lebend</span>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="eliminate_player">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button danger-button small-button"><i data-lucide="skull"></i> Killen</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<span class="status-pill danger-pill" style="margin-left: 0;"><i data-lucide="skull"></i> Gekillt</span>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="revive_player">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button success-button small-button"><i data-lucide="heart-pulse"></i> Beleben</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if current_player.target_identifier %}
|
||||
{{ current_player.target_identifier }}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="change_target">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<select name="new_target_identifier" class="text-input small-input">
|
||||
{% for possible_target in all_players %}
|
||||
{% if possible_target.is_alive and possible_target.player_identifier != current_player.player_identifier %}
|
||||
<option value="{{ possible_target.player_identifier }}" {% if current_player.target_identifier == possible_target.player_identifier %}selected{% endif %}>{{ possible_target.display_name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="action-button secondary-button small-button"><i data-lucide="refresh-cw"></i></button>
|
||||
</form>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="generate_reset">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button secondary-button small-button"><i data-lucide="link"></i> Reset Link</button>
|
||||
</form>
|
||||
{% if current_player.reset_token %}
|
||||
<input type="text" readonly value="{{ url_for('handle_password_reset', token=current_player.reset_token, _external=True) }}" class="text-input small-input" style="margin: var(--spacing-small) 0;">
|
||||
{% endif %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="delete_player">
|
||||
<input type="hidden" name="player_identifier" value="{{ current_player.player_identifier }}">
|
||||
<button type="submit" class="action-button danger-button small-button"><i data-lucide="trash-2"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kill-panel" class="tab-content">
|
||||
<div class="content-card" style="overflow-x: auto;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zeitpunkt</th>
|
||||
<th>Killer</th>
|
||||
<th>Opfer</th>
|
||||
<th>Status</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for report in kill_logs %}
|
||||
<tr>
|
||||
<td>{{ report.kill_timestamp[:16].replace('T', ' ') if report.kill_timestamp else '-' }}</td>
|
||||
<td>{{ report.killer_name or 'Unbekannt' }} ({{ report.killer_identifier }})</td>
|
||||
<td>{{ report.victim_name or 'Unbekannt' }} ({{ report.victim_identifier }})</td>
|
||||
<td>
|
||||
{% if report.report_status == 'confirmed' %}
|
||||
<span class="status-pill success-pill" style="margin-left: 0;">Bestätigt</span>
|
||||
{% elif report.report_status == 'pending' %}
|
||||
<span class="status-pill warning-pill" style="margin-left: 0;">Ausstehend</span>
|
||||
{% elif report.report_status == 'rejected' %}
|
||||
<span class="status-pill danger-pill" style="margin-left: 0;">Abgelehnt</span>
|
||||
{% elif report.report_status == 'revoked' %}
|
||||
<span class="status-pill danger-pill" style="margin-left: 0;">Widerrufen</span>
|
||||
{% else %}
|
||||
{{ report.report_status }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if report.report_status == 'confirmed' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="revoke_kill">
|
||||
<input type="hidden" name="kill_identifier" value="{{ report.kill_identifier }}">
|
||||
<button type="submit" class="action-button danger-button small-button"><i data-lucide="rotate-ccw"></i> Rückgängig</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if report.report_status == 'pending' %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="force_confirm_report">
|
||||
<input type="hidden" name="kill_identifier" value="{{ report.kill_identifier }}">
|
||||
<button type="submit" class="action-button success-button small-button"><i data-lucide="check"></i> Bestätigen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if report.report_status in ['pending', 'rejected', 'revoked'] %}
|
||||
<form action="{{ url_for('handle_administrator_action') }}" method="POST" class="flexible_form_container admin-asynchronous-form" style="margin-top: var(--spacing-small);">
|
||||
<input type="hidden" name="action_type" value="delete_report">
|
||||
<input type="hidden" name="kill_identifier" value="{{ report.kill_identifier }}">
|
||||
<button type="submit" class="action-button secondary-button small-button"><i data-lucide="trash-2"></i> Löschen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center; opacity: 0.6;">Keine Meldungen.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="admin-confirmation-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="calendar-clock"></i> Aktion planen</h2>
|
||||
<p class="modal-description">Sofort ausführen oder planen.</p>
|
||||
<div class="form-group" style="margin-bottom: var(--spacing-large); text-align: left;">
|
||||
<label for="modal_execution_time" class="input-label">Zeitpunkt (Optional)</label>
|
||||
<input type="datetime-local" id="modal_execution_time" class="text-input time-input" style="width: 100%; box-sizing: border-box;">
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="confirm-admin-action" class="action-button success-button"><i data-lucide="check"></i> Bestätigen</button>
|
||||
<button type="button" id="cancel-admin-action" class="action-button secondary-button"><i data-lucide="x"></i> Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
+189
-182
@@ -1,187 +1,194 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<section class="game-section">
|
||||
<div class="content-card">
|
||||
<h2 class="section-title" style="margin: 0;"><i data-lucide="user"></i> Willkommen, {{ player.display_name }} ({{ player.player_identifier }})!</h2>
|
||||
</div>
|
||||
|
||||
{% if not player.is_approved %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<h2 class="modal-title"><i data-lucide="hourglass"></i> Konto wird geprüft…</h2>
|
||||
</div>
|
||||
{% elif game_status == 'pending' %}
|
||||
<div class="content-card notice-card">
|
||||
<h2 class="modal-title"><i data-lucide="clock"></i> Startet bald…</h2>
|
||||
</div>
|
||||
{% elif game_status == 'finished' %}
|
||||
<div class="content-card leaderboard-card">
|
||||
<h2 class="section-title"><i data-lucide="trophy"></i> Spiel beendet – Rangliste</h2>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platz</th>
|
||||
<th>Name</th>
|
||||
<th>Punkte</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for current_player in all_players %}
|
||||
<tr class="{% if current_player.player_identifier == session['player_identifier'] %}highlighted-row{% endif %}">
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ current_player.display_name }}</td>
|
||||
<td>{{ current_player.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% elif game_status in ['running', 'paused'] %}
|
||||
<div class="tab-container">
|
||||
<button class="tab-button active-tab" data-target="mission-panel"><i data-lucide="crosshair"></i> Ziel</button>
|
||||
<button class="tab-button" data-target="leaderboard-panel"><i data-lucide="bar-chart-2"></i> Punkte</button>
|
||||
<button class="tab-button" data-target="rules-panel"><i data-lucide="book-open"></i> Regeln</button>
|
||||
</div>
|
||||
|
||||
<div id="mission-panel" class="tab-content active-content">
|
||||
{% if game_status == 'paused' %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<h2 class="modal-title"><i data-lucide="pause-circle"></i> Spiel pausiert</h2>
|
||||
<p class="notice-text">Das Spiel wurde pausiert. Eliminierungen sind aktuell nicht möglich.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if player.is_alive %}
|
||||
<div class="content-card target-card" style="text-align: center;">
|
||||
<h3 class="card-subtitle"><i data-lucide="target"></i> Dein Ziel</h3>
|
||||
<h2 class="target-name">{{ target_player.display_name if target_player else 'Niemand' }}</h2>
|
||||
{% if game_status == 'running' %}
|
||||
<button type="button" id="trigger-elimination" class="action-button danger-button large-button"><i data-lucide="crosshair"></i> Kill melden</button>
|
||||
{% else %}
|
||||
<button type="button" class="action-button secondary-button large-button" disabled><i data-lucide="lock"></i> Pausiert</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif player.late_joiner %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<h2 class="modal-title"><i data-lucide="users"></i> Warte auf Einstieg…</h2>
|
||||
<p class="notice-text">Du bist dem Spiel während einer laufenden Runde beigetreten. Du wirst aktiv, sobald du eingewechselt wirst.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="content-card notice-card danger-card">
|
||||
<h2 class="modal-title"><i data-lucide="skull"></i> Eliminiert</h2>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="content-card statistics-card">
|
||||
<h3 class="section-title"><i data-lucide="pie-chart"></i> Spielübersicht</h3>
|
||||
<p>Überlebende: <strong>{{ alive_count }}</strong></p>
|
||||
<p>Deine Kills: <strong>{{ player.score }}</strong></p>
|
||||
<p>Gesamte Kills: <strong>{{ total_eliminations }}</strong></p>
|
||||
<p>Laufzeit: <strong id="game-stopwatch" data-start-time="{{ start_time }}">Wird berechnet...</strong></p>
|
||||
</div>
|
||||
|
||||
<div class="content-card settings-card">
|
||||
<h3 class="section-title"><i data-lucide="user-cog"></i> Profil</h3>
|
||||
{% if player.pending_display_name %}
|
||||
<div class="notice-card warning-card" style="padding: var(--spacing-medium);">
|
||||
<p class="notice-text" style="margin: 0;">Der Name <strong>{{ player.pending_display_name }}</strong> wird geprüft…</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<button type="button" id="trigger-name-change-modal" class="action-button secondary-button"><i data-lucide="edit-3"></i> Name ändern</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="leaderboard-panel" class="tab-content">
|
||||
<div class="content-card">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platz</th>
|
||||
<th>Name</th>
|
||||
<th>Punkte</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for current_player in all_players %}
|
||||
<tr class="{% if current_player.player_identifier == session['player_identifier'] %}highlighted-row{% endif %}">
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ current_player.display_name }}</td>
|
||||
<td>{{ current_player.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rules-panel" class="tab-content">
|
||||
<div class="content-card markdown-container">
|
||||
{{ rules_markup | safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="elimination-confirm-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="alert-triangle"></i> Kill</h2>
|
||||
<p class="modal-description">Möchtest du melden, dass du dein Ziel gekillt hast?</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="proceed-elimination" class="action-button danger-button"><i data-lucide="crosshair"></i> Ja, melden</button>
|
||||
<button type="button" id="cancel-elimination" class="action-button secondary-button"><i data-lucide="x"></i> Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="waiting-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="loader"></i> Bitte warten</h2>
|
||||
<p class="modal-description">Warte auf Bestätigung vom Ziel...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rejection-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="x-octagon"></i> Abgelehnt</h2>
|
||||
<p class="modal-description">Das Ziel hat deinen Kill abgelehnt.</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="acknowledge-rejection" class="action-button primary-button"><i data-lucide="check"></i> Verstanden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="verification-modal" class="modal-overlay {% if player.pending_attacker_identifier %}active-modal{% endif %}">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="shield-alert"></i> Wurdest du eliminiert?</h2>
|
||||
<p class="modal-description">
|
||||
{% if pending_attacker_name %}
|
||||
<strong>{{ pending_attacker_name }}</strong> hat gemeldet, dass er/sie dich gekillt hat.
|
||||
{% else %}
|
||||
Dein Angreifer hat gemeldet, dass er dich gekillt hat.
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="confirm-elimination" class="action-button danger-button"><i data-lucide="check"></i> Bestätigen</button>
|
||||
<button type="button" id="reject-elimination" class="action-button secondary-button"><i data-lucide="x"></i> Ablehnen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="name-change-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="edit-3"></i> Name ändern</h2>
|
||||
<p class="modal-description">Dein neuer Name muss erst geprüft werden.</p>
|
||||
<form action="{{ url_for('handle_player_action') }}" method="POST" class="input-form asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="request_name_change">
|
||||
<div class="form-group" style="text-align: left;">
|
||||
<label for="pending_display_name" class="input-label">Neuer Name</label>
|
||||
<input type="text" id="pending_display_name" name="pending_display_name" class="text-input" required>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="send"></i> Abschicken</button>
|
||||
<button type="button" id="cancel-name-change" class="action-button secondary-button"><i data-lucide="x"></i> Abbrechen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title" style="margin: 0;"><i data-lucide="user"></i> Hallo, {{ player.display_name }}!</h2>
|
||||
</div>
|
||||
|
||||
{% if not player.is_approved %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<h2 class="modal-title"><i data-lucide="hourglass"></i> Warten auf Freigabe</h2>
|
||||
<p class="notice-text">Die Spielleitung muss deinen Account noch aktivieren.</p>
|
||||
</div>
|
||||
{% elif game_status == 'pending' %}
|
||||
<div class="content-card notice-card">
|
||||
<h2 class="modal-title"><i data-lucide="clock"></i> Spiel startet in Kürze</h2>
|
||||
</div>
|
||||
{% elif game_status == 'finished' %}
|
||||
<div class="content-card leaderboard-card">
|
||||
<h2 class="section-title"><i data-lucide="trophy"></i> Rangliste</h2>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platz</th>
|
||||
<th>Name</th>
|
||||
<th>Punkte</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for current_player in all_players %}
|
||||
<tr class="{% if current_player.player_identifier == session['player_identifier'] %}highlighted-row{% endif %}">
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ current_player.display_name }}</td>
|
||||
<td>{{ current_player.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% elif game_status in ['running', 'paused'] %}
|
||||
<div class="tab-container">
|
||||
<button class="tab-button active-tab" data-target="mission-panel"><i data-lucide="crosshair"></i> Ziel</button>
|
||||
<button class="tab-button" data-target="leaderboard-panel"><i data-lucide="bar-chart-2"></i> Punkte</button>
|
||||
<button class="tab-button" data-target="rules-panel"><i data-lucide="book-open"></i> Regeln</button>
|
||||
</div>
|
||||
|
||||
<div id="mission-panel" class="tab-content active-content">
|
||||
{% if game_status == 'paused' %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<h2 class="modal-title"><i data-lucide="pause-circle"></i> Pausiert</h2>
|
||||
<p class="notice-text">Kills sind vorübergehend deaktiviert.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if player.is_alive %}
|
||||
<div class="content-card target-card" style="text-align: center;">
|
||||
<h3 class="card-subtitle"><i data-lucide="target"></i> Ziel</h3>
|
||||
<h2 class="target-name">{{ target_player.display_name if target_player else 'Niemand' }}</h2>
|
||||
|
||||
{% if game_status == 'running' %}
|
||||
{% if has_pending_kill_report %}
|
||||
<div class="notice-card warning-card" style="padding: var(--spacing-medium); border-radius: var(--border-radius-small); margin-top: var(--spacing-large);">
|
||||
<h3 style="margin-top: 0; display: flex; align-items: center; justify-content: center; gap: var(--spacing-small);"><i data-lucide="loader"></i> Kill gemeldet</h3>
|
||||
<p>Warte auf Bestätigung…</p>
|
||||
<button type="button" id="trigger-revoke-elimination" class="action-button secondary-button"><i data-lucide="rotate-ccw"></i> Zurückziehen</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<button type="button" id="trigger-elimination" class="action-button danger-button large-button"><i data-lucide="crosshair"></i> Kill melden</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button type="button" class="action-button secondary-button large-button" disabled><i data-lucide="lock"></i> Gesperrt</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif player.late_joiner %}
|
||||
<div class="content-card notice-card warning-card">
|
||||
<h2 class="modal-title"><i data-lucide="users"></i> Warten auf Einwechslung</h2>
|
||||
<p class="notice-text">Die Spielleitung wechselt dich bald ein.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="content-card notice-card danger-card">
|
||||
<h2 class="modal-title"><i data-lucide="skull"></i> Gekillt</h2>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="content-card statistics-card">
|
||||
<h3 class="section-title"><i data-lucide="pie-chart"></i> Übersicht</h3>
|
||||
<p>Lebend: <strong>{{ alive_count }}</strong></p>
|
||||
<p>Kills: <strong>{{ player.score }}</strong></p>
|
||||
<p>Gesamt: <strong>{{ total_eliminations }}</strong></p>
|
||||
<p>Zeit: <strong id="game-stopwatch" data-start-time="{{ start_time }}">Lade...</strong></p>
|
||||
</div>
|
||||
|
||||
<div class="content-card settings-card">
|
||||
<h3 class="section-title"><i data-lucide="user-cog"></i> Profil</h3>
|
||||
{% if player.pending_display_name %}
|
||||
<div class="notice-card warning-card" style="padding: var(--spacing-medium); border-radius: var(--border-radius-small);">
|
||||
<p class="notice-text" style="margin-top: 0;"><strong>{{ player.pending_display_name }}</strong> wird geprüft…</p>
|
||||
<form action="{{ url_for('handle_player_action') }}" method="POST" class="asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="cancel_name_change">
|
||||
<button type="submit" class="action-button secondary-button small-button"><i data-lucide="x"></i> Abbrechen</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<button type="button" id="trigger-name-change-modal" class="action-button secondary-button"><i data-lucide="edit-3"></i> Name ändern</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="leaderboard-panel" class="tab-content">
|
||||
<div class="content-card">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platz</th>
|
||||
<th>Name</th>
|
||||
<th>Punkte</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for current_player in all_players %}
|
||||
<tr class="{% if current_player.player_identifier == session['player_identifier'] %}highlighted-row{% endif %}">
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ current_player.display_name }}</td>
|
||||
<td>{{ current_player.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rules-panel" class="tab-content">
|
||||
<div class="content-card markdown-container">
|
||||
{{ rules_markup | safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="elimination-confirm-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="alert-triangle"></i> Kill melden</h2>
|
||||
<p class="modal-description">Sicher, dass du den Kill melden möchtest?</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="proceed-elimination" class="action-button danger-button"><i data-lucide="crosshair"></i> Melden</button>
|
||||
<button type="button" id="cancel-elimination" class="action-button secondary-button"><i data-lucide="x"></i> Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rejection-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="x-octagon"></i> Abgelehnt</h2>
|
||||
<p class="modal-description">Das Ziel hat die Meldung abgelehnt. Bitte klären.</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="acknowledge-rejection" class="action-button primary-button"><i data-lucide="check"></i> Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="verification-modal" class="modal-overlay {% if player.pending_attacker_identifier %}active-modal{% endif %}">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="shield-alert"></i> Gekillt?</h2>
|
||||
<p class="modal-description">
|
||||
{% if pending_attacker_name %}
|
||||
Wurdest du von <strong>{{ pending_attacker_name }}</strong> gekillt?
|
||||
{% else %}
|
||||
Wurdest du gekillt?
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="confirm-elimination" class="action-button danger-button"><i data-lucide="check"></i> Ja</button>
|
||||
<button type="button" id="reject-elimination" class="action-button secondary-button"><i data-lucide="x"></i> Nein</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="name-change-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="edit-3"></i> Name ändern</h2>
|
||||
<p class="modal-description">Der Name wird vorher von der Spielleitung geprüft.</p>
|
||||
<form action="{{ url_for('handle_player_action') }}" method="POST" class="input-form asynchronous-form">
|
||||
<input type="hidden" name="action_type" value="request_name_change">
|
||||
<div class="form-group" style="text-align: left;">
|
||||
<label for="pending_display_name" class="input-label">Neuer Name</label>
|
||||
<input type="text" id="pending_display_name" name="pending_display_name" class="text-input" required>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="send"></i> Speichern</button>
|
||||
<button type="button" id="cancel-name-change-modal" class="action-button secondary-button"><i data-lucide="x"></i> Abbrechen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
+28
-26
@@ -1,33 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>SCOicide Squad</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='logo.svg') }}" type="image/svg">
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
<meta name="theme-color" content="#1a1e29">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='stylesheet.css') }}">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js" defer></script>
|
||||
<script src="https://unpkg.com/lucide@latest" defer></script>
|
||||
<script src="{{ url_for('static', filename='behavior.js') }}" defer></script>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>SCOicide Squad</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='logo.svg') }}" type="image/svg">
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
<meta name="theme-color" content="#1a1e29">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='stylesheet.css') }}">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js" defer></script>
|
||||
<script src="https://unpkg.com/lucide@latest" defer></script>
|
||||
<script src="{{ url_for('static', filename='behavior.js') }}" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="application-header">
|
||||
<div class="brand-container">
|
||||
<img src="{{ url_for('static', filename='logo.svg') }}" alt="Logo" class="brand-logo">
|
||||
<h1 class="application-title"><i>SCO</i>icide Squad</h1>
|
||||
</div>
|
||||
<nav class="application-navigation">
|
||||
{% if session.get('player_identifier') or session.get('administrator_authenticated') %}
|
||||
<a href="{{ url_for('handle_logout') }}" class="action-button secondary-button icon-button">
|
||||
<i data-lucide="log-out"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</header>
|
||||
<main class="application-content">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
<header class="application-header">
|
||||
<a href="/" class="brand-link">
|
||||
<div class="brand-container">
|
||||
<img src="{{ url_for('static', filename='logo.svg') }}" alt="Logo" class="brand-logo">
|
||||
<h1 class="application-title"><i>SCO</i>icide Squad</h1>
|
||||
</div>
|
||||
</a>
|
||||
<nav class="application-navigation">
|
||||
{% if session.get('player_identifier') or session.get('administrator_authenticated') %}
|
||||
<a href="{{ url_for('handle_logout') }}" class="action-button secondary-button icon-button">
|
||||
<i data-lucide="log-out"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</header>
|
||||
<main class="application-content">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
+80
-67
@@ -1,72 +1,85 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<section class="authentication-section">
|
||||
{% if reset_mode %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="key-round"></i> Passwort zurücksetzen</h2>
|
||||
<form action="{{ url_for('handle_password_reset') }}" method="POST" class="input-form">
|
||||
<input type="hidden" name="token" value="{{ token }}">
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Neues Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string_confirmation" class="input-label">Passwort wiederholen</label>
|
||||
<input type="password" id="password_string_confirmation" name="password_string_confirmation" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="save"></i> Passwort speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif register_mode %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="user-plus"></i> Neues Konto erstellen</h2>
|
||||
<form action="{{ url_for('handle_registration') }}" method="POST" class="input-form">
|
||||
<div class="form-group">
|
||||
<label for="display_name" class="input-label">Dein Name</label>
|
||||
<input type="text" id="display_name" name="display_name" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string_confirmation" class="input-label">Passwort wiederholen</label>
|
||||
<input type="password" id="password_string_confirmation" name="password_string_confirmation" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="check-circle"></i> Registrieren</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="content-card" style="text-align: center;">
|
||||
<a href="{{ url_for('handle_login') }}" class="action-button secondary-button"><i data-lucide="arrow-left"></i> Zurück zur Anmeldung</a>
|
||||
</div>
|
||||
{% elif registration_success %}
|
||||
<div class="content-card notice-card success-card" style="text-align: center;">
|
||||
<h2 class="section-title" style="justify-content: center;"><i data-lucide="party-popper"></i> Registrierung erfolgreich</h2>
|
||||
<p>Deine ID für die Anmeldung lautet:</p>
|
||||
<h1 class="monospace-identifier" style="font-size: calc(3rem * var(--dimension-multiplier)); letter-spacing: calc(0.25rem * var(--dimension-multiplier)); color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary)); margin: var(--spacing-medium) 0;">{{ new_identifier }}</h1>
|
||||
<p>Bitte speichere oder merke sie dir!</p>
|
||||
<div style="margin-top: var(--spacing-large);">
|
||||
<a href="{{ url_for('handle_login') }}" class="action-button primary-button"><i data-lucide="log-in"></i> Jetzt anmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="lock"></i> Anmeldung</h2>
|
||||
<form action="{{ url_for('handle_login') }}" method="POST" class="input-form">
|
||||
<div class="form-group">
|
||||
<label for="player_identifier" class="input-label">ID</label>
|
||||
<input type="text" id="player_identifier" name="player_identifier" class="text-input" style="text-transform: uppercase;" maxlength="{{ identifier_length }}" oninput="this.value = this.value.replace(/[^a-zA-Z]/g, '').toUpperCase().slice(0, {{ identifier_length }});" pattern="[A-Za-z]{{ '{' }}{{ identifier_length }}{{ '}' }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="log-in"></i> Anmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="content-card" style="text-align: center;">
|
||||
<a href="{{ url_for('handle_registration') }}" class="action-button secondary-button"><i data-lucide="user-plus"></i> Noch kein Konto? Hier registrieren</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if reset_mode %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="key-round"></i> Passwort zurücksetzen</h2>
|
||||
<form action="{{ url_for('handle_password_reset') }}" method="POST" class="input-form">
|
||||
<input type="hidden" name="token" value="{{ token }}">
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Neues Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string_confirmation" class="input-label">Passwort bestätigen</label>
|
||||
<input type="password" id="password_string_confirmation" name="password_string_confirmation" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="save"></i> Speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif register_mode %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="user-plus"></i> Registrieren</h2>
|
||||
<form action="{{ url_for('handle_registration') }}" method="POST" class="input-form">
|
||||
<div class="form-group">
|
||||
<label for="display_name" class="input-label">Name</label>
|
||||
<input type="text" id="display_name" name="display_name" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string_confirmation" class="input-label">Passwort bestätigen</label>
|
||||
<input type="password" id="password_string_confirmation" name="password_string_confirmation" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="check-circle"></i> Konto erstellen</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="content-card" style="text-align: center;">
|
||||
<a href="{{ url_for('handle_login') }}" class="action-button secondary-button"><i data-lucide="arrow-left"></i> Zur Anmeldung</a>
|
||||
</div>
|
||||
{% elif registration_success %}
|
||||
<div class="content-card notice-card success-card" style="text-align: center;">
|
||||
<h2 class="section-title" style="justify-content: center;"><i data-lucide="party-popper"></i> Erfolgreich</h2>
|
||||
<h2 style="color: var(--fallback-danger); color: oklch(var(--lightness-danger) var(--chroma-danger) var(--hue-danger));">Wichtig!</h2>
|
||||
<p>Deine ID:</p>
|
||||
<h1 class="monospace-identifier" style="font-size: calc(3rem * var(--dimension-multiplier)); letter-spacing: calc(0.25rem * var(--dimension-multiplier)); color: var(--fallback-primary); color: oklch(var(--lightness-primary) var(--chroma-primary) var(--hue-primary)); margin: var(--spacing-medium) 0;">{{ new_identifier }}</h1>
|
||||
<button type="button" id="copy-identifier-button" class="action-button secondary-button" data-identifier="{{ new_identifier }}"><i data-lucide="copy"></i> ID kopieren</button>
|
||||
<p style="margin-top: var(--spacing-medium);">Gut aufbewahren!</p>
|
||||
<div style="margin-top: var(--spacing-large);">
|
||||
<a href="{{ url_for('handle_login', prefill_identifier=new_identifier) }}" class="action-button primary-button"><i data-lucide="log-in"></i> Anmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="content-card">
|
||||
<h2 class="section-title"><i data-lucide="lock"></i> Login</h2>
|
||||
<form action="{{ url_for('handle_login') }}" method="POST" class="input-form">
|
||||
<div class="form-group">
|
||||
<label for="player_identifier" class="input-label">ID</label>
|
||||
<input type="text" id="player_identifier" name="player_identifier" class="text-input" value="{{ request.args.get('prefill_identifier', '') }}" style="text-transform: uppercase;" maxlength="{{ identifier_length }}" oninput="this.value = this.value.replace(/[^a-zA-Z]/g, '').toUpperCase().slice(0, {{ identifier_length }});" pattern="[A-Za-z]{{ '{' }}{{ identifier_length }}{{ '}' }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_string" class="input-label">Passwort</label>
|
||||
<input type="password" id="password_string" name="password_string" class="text-input" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button primary-button"><i data-lucide="log-in"></i> Anmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="content-card" style="text-align: center; display: flex; flex-direction: column; gap: var(--spacing-medium);">
|
||||
<a href="{{ url_for('handle_registration') }}" class="action-button secondary-button"><i data-lucide="user-plus"></i> Registrieren</a>
|
||||
<button type="button" id="trigger-help-modal" class="action-button secondary-button"><i data-lucide="life-buoy"></i> Probleme beim Login?</button>
|
||||
</div>
|
||||
|
||||
<div id="help-modal" class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<h2 class="modal-title"><i data-lucide="help-circle"></i> Hilfe</h2>
|
||||
<p class="modal-description">Wende dich an die Spielleitung, falls du deine ID oder dein Passwort vergessen hast.</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" id="close-help-modal" class="action-button primary-button"><i data-lucide="check"></i> Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user