ui redesign
This commit is contained in:
769
ayto/index.html
769
ayto/index.html
@@ -1,121 +1,188 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" class="h-full" data-theme="mocha">
|
||||
<html lang="de" data-theme="ayto">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AYTO? Match-Rechner | Wer passt zu wem?</title>
|
||||
<title>AYTO? Match-Rechner | Themed Edition</title>
|
||||
<!-- Anti-Caching Meta Tags to ensure latest version loads -->
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
|
||||
<link rel="icon" href="/ayto/favicon.ico" type="image/x-icon">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
|
||||
<style>
|
||||
:root, [data-theme="mocha"] {
|
||||
--crust: #11111b; --mantle: #181825; --base: #1e1e2e;
|
||||
--surface0: #313244; --surface1: #45475a; --surface2: #585b70;
|
||||
--overlay0: #6c7086; --overlay1: #7f849c; --overlay2: #9399b2;
|
||||
--subtext0: #a6adc8; --subtext1: #bac2de; --text: #cdd6f4;
|
||||
--lavender: #b4befe; --blue: #89b4fa; --sapphire: #74c7ec;
|
||||
--sky: #89dceb; --teal: #94e2d5; --green: #a6e3a1;
|
||||
--yellow: #f9e2af; --peach: #fab387; --maroon: #eba0ac;
|
||||
--red: #f38ba8; --mauve: #cba6f7; --pink: #f5c2e7;
|
||||
--flamingo: #f2cdcd; --rosewater: #f5e0dc;
|
||||
}
|
||||
[data-theme="latte"] {
|
||||
--crust: #dce0e8; --mantle: #e6e9ef; --base: #eff1f5;
|
||||
--surface0: #ccd0da; --surface1: #bcc0cc; --surface2: #acb0be;
|
||||
--overlay0: #9ca0b0; --overlay1: #8c8fa1; --overlay2: #7c7f93;
|
||||
--subtext0: #6c6f85; --subtext1: #5c5f77; --text: #4c4f69;
|
||||
--lavender: #7287fd; --blue: #1e66f5; --sapphire: #209fb5;
|
||||
--sky: #04a5e5; --teal: #179299; --green: #40a02b;
|
||||
--yellow: #df8e1d; --peach: #fe640b; --maroon: #e64553;
|
||||
--red: #d20f39; --mauve: #8839ef; --pink: #ea76cb;
|
||||
--flamingo: #dd7878; --rosewater: #dc8a78;
|
||||
}
|
||||
[data-theme="frappe"] {
|
||||
--crust: #232634; --mantle: #292c3c; --base: #303446;
|
||||
--surface0: #414559; --surface1: #51576d; --surface2: #626880;
|
||||
--overlay0: #737994; --overlay1: #838ba7; --overlay2: #949cbb;
|
||||
--subtext0: #a5adce; --subtext1: #b5bfe2; --text: #c6d0f5;
|
||||
--lavender: #babbf1; --blue: #8caaee; --sapphire: #85c1dc;
|
||||
--sky: #99d1db; --teal: #81c8be; --green: #a6d189;
|
||||
--yellow: #e5c890; --peach: #ef9f76; --maroon: #ea999c;
|
||||
--red: #e78284; --mauve: #ca9ee6; --pink: #f4b8e4;
|
||||
--flamingo: #eebebe; --rosewater: #f2d5cf;
|
||||
}
|
||||
[data-theme="macchiato"] {
|
||||
--crust: #181926; --mantle: #1e2030; --base: #24273a;
|
||||
--surface0: #363a4f; --surface1: #494d64; --surface2: #5b6078;
|
||||
--overlay0: #6e738d; --overlay1: #8087a2; --overlay2: #939ab7;
|
||||
--subtext0: #a5adcb; --subtext1: #b8c0e0; --text: #cad3f5;
|
||||
--lavender: #b7bdf8; --blue: #8aadf4; --sapphire: #7dc4e4;
|
||||
--sky: #91d7e3; --teal: #8bd5ca; --green: #a6da95;
|
||||
--yellow: #eed49f; --peach: #f5a97f; --maroon: #ee99a0;
|
||||
--red: #ed8796; --mauve: #c6a0f6; --pink: #f5bde6;
|
||||
--flamingo: #f0c6c6; --rosewater: #f4dbd6;
|
||||
/* === THEME DEFINITIONS === */
|
||||
:root, [data-theme="ayto"] {
|
||||
--bg-color: #0a0a0f;
|
||||
--panel-bg: rgba(20, 20, 30, 0.7);
|
||||
--panel-border: rgba(0, 229, 255, 0.2);
|
||||
--primary: #00e5ff; /* Cyan */
|
||||
--primary-hover: #00b3cc;
|
||||
--secondary: #ff007a; /* Magenta */
|
||||
--text-main: #ffffff;
|
||||
--text-muted: #8b92a5;
|
||||
--input-bg: rgba(0, 0, 0, 0.4);
|
||||
--success: #00ff88;
|
||||
--error: #ff3333;
|
||||
}
|
||||
|
||||
[data-theme="sai"] {
|
||||
--bg-color: #eafafc;
|
||||
--panel-bg: rgba(255, 255, 255, 0.8);
|
||||
--panel-border: rgba(107, 192, 213, 0.4);
|
||||
--primary: #f38da1; /* SAI Pink */
|
||||
--primary-hover: #e07489;
|
||||
--secondary: #6bc0d5; /* SAI Blue */
|
||||
--text-main: #2c3e50;
|
||||
--text-muted: #7f8c8d;
|
||||
--input-bg: #f8fcfd;
|
||||
--success: #2ecc71;
|
||||
--error: #e74c3c;
|
||||
}
|
||||
|
||||
[data-theme="trench"] {
|
||||
--bg-color: #151515;
|
||||
--panel-bg: rgba(34, 34, 34, 0.8);
|
||||
--panel-border: rgba(252, 227, 0, 0.2);
|
||||
--primary: #fce300; /* Trench Yellow */
|
||||
--primary-hover: #d4bf00;
|
||||
--secondary: #575c3a; /* Olive */
|
||||
--text-main: #e0e0e0;
|
||||
--text-muted: #888888;
|
||||
--input-bg: #111111;
|
||||
--success: #8bb35c;
|
||||
--error: #cc4444;
|
||||
}
|
||||
|
||||
[data-theme="blurryface"] {
|
||||
--bg-color: #ececec;
|
||||
--panel-bg: rgba(255, 255, 255, 0.9);
|
||||
--panel-border: rgba(206, 45, 45, 0.3);
|
||||
--primary: #ce2d2d; /* Blurryface Red */
|
||||
--primary-hover: #a82020;
|
||||
--secondary: #000000; /* Black */
|
||||
--text-main: #111111;
|
||||
--text-muted: #666666;
|
||||
--input-bg: #f5f5f5;
|
||||
--success: #27ae60;
|
||||
--error: #ce2d2d;
|
||||
}
|
||||
|
||||
[data-theme="clancy"] {
|
||||
--bg-color: #1c1c1c;
|
||||
--panel-bg: rgba(44, 44, 44, 0.8);
|
||||
--panel-border: rgba(217, 56, 46, 0.3);
|
||||
--primary: #f2c12e; /* Clancy Yellow */
|
||||
--primary-hover: #d9ab24;
|
||||
--secondary: #d9382e; /* Clancy Red */
|
||||
--text-main: #f5f5f5;
|
||||
--text-muted: #a0a0a0;
|
||||
--input-bg: #111111;
|
||||
--success: #f2c12e;
|
||||
--error: #d9382e;
|
||||
}
|
||||
|
||||
/* === GLOBAL STYLES === */
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
background: linear-gradient(135deg, var(--crust), var(--base));
|
||||
background-attachment: fixed;
|
||||
transition: background 0.5s ease;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-main);
|
||||
transition: background-color 0.4s ease, color 0.4s ease;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(var(--surface0-rgb, 49, 50, 68), 0.4);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
.theme-panel {
|
||||
background: var(--panel-bg);
|
||||
border: 1px solid var(--panel-border);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
[data-theme="latte"] .glass-panel {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.theme-text { color: var(--text-main); }
|
||||
.theme-text-muted { color: var(--text-muted); }
|
||||
.theme-primary-text { color: var(--primary); }
|
||||
.theme-secondary-text { color: var(--secondary); }
|
||||
|
||||
.gradient-heading {
|
||||
background: linear-gradient(to right, var(--text), var(--lavender));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-fill-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.input-elegant {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid var(--surface2);
|
||||
border-radius: 12px;
|
||||
.theme-input {
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--panel-border);
|
||||
color: var(--text-main);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.input-elegant:focus {
|
||||
border-color: var(--blue);
|
||||
box-shadow: 0 0 0 3px rgba(137, 180, 250, 0.15);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
.theme-input:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 2px var(--panel-border);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-elegant {
|
||||
border-radius: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.025em;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.btn-elegant:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar { width: 4px; height: 4px; }
|
||||
.table-container::-webkit-scrollbar-thumb { background: var(--surface2); border-radius: 10px; }
|
||||
|
||||
.prob-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.75rem;
|
||||
.theme-btn {
|
||||
background: var(--primary);
|
||||
color: var(--bg-color);
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.theme-btn:hover:not(:disabled) {
|
||||
background: var(--primary-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px var(--panel-border);
|
||||
}
|
||||
.theme-btn:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.theme-btn-outline {
|
||||
background: transparent;
|
||||
border: 2px solid var(--primary);
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.theme-btn-outline:hover:not(:disabled) {
|
||||
background: var(--primary);
|
||||
color: var(--bg-color);
|
||||
}
|
||||
|
||||
.neon-glow {
|
||||
text-shadow: 0 0 10px var(--primary), 0 0 20px var(--secondary);
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: var(--bg-color); }
|
||||
::-webkit-scrollbar-thumb { background: var(--primary); border-radius: 4px; }
|
||||
|
||||
/* Probabilities */
|
||||
.prob-badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-weight: 800;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Matrix Table Styling */
|
||||
table th, table td { border-color: var(--panel-border); }
|
||||
|
||||
/* Select Reset */
|
||||
select.theme-input {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0.75rem center;
|
||||
background-size: 1rem;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
@@ -123,254 +190,210 @@
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.animate-slide-in { animation: slideIn 0.5s ease-out forwards; }
|
||||
|
||||
/* Custom Dropdown Styling */
|
||||
select.input-elegant {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%237f849c'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0.75rem center;
|
||||
background-size: 1rem;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
rosewater: 'var(--rosewater)', flamingo: 'var(--flamingo)', pink: 'var(--pink)',
|
||||
mauve: 'var(--mauve)', red: 'var(--red)', maroon: 'var(--maroon)',
|
||||
peach: 'var(--peach)', yellow: 'var(--yellow)', green: 'var(--green)',
|
||||
teal: 'var(--teal)', sky: 'var(--sky)', sapphire: 'var(--sapphire)',
|
||||
blue: 'var(--blue)', lavender: 'var(--lavender)', text: 'var(--text)',
|
||||
subtext1: 'var(--subtext1)', subtext0: 'var(--subtext0)',
|
||||
overlay2: 'var(--overlay2)', overlay1: 'var(--overlay1)', overlay0: 'var(--overlay0)',
|
||||
surface2: 'var(--surface2)', surface1: 'var(--surface1)', surface0: 'var(--surface0)',
|
||||
base: 'var(--base)', mantle: 'var(--mantle)', crust: 'var(--crust)',
|
||||
}
|
||||
}
|
||||
extend: {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-base text-text min-h-full">
|
||||
<body class="pb-12">
|
||||
|
||||
<!-- Navigation Header -->
|
||||
<header class="sticky top-0 z-50 glass-panel border-b border-white/5 py-4 px-6 mb-8 flex items-center justify-between">
|
||||
<!-- Navbar -->
|
||||
<nav class="sticky top-0 z-50 theme-panel border-b border-t-0 border-l-0 border-r-0 py-4 px-6 mb-8 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 bg-gradient-to-br from-mauve to-blue rounded-xl shadow-lg">
|
||||
<i data-lucide="heart" class="w-6 h-6 text-base"></i>
|
||||
<div class="p-2 rounded-xl" style="background: var(--primary); color: var(--bg-color);">
|
||||
<i data-lucide="heart-pulse" class="w-6 h-6"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-lg font-extrabold tracking-tight">AYTO? Match-Rechner</h1>
|
||||
<p class="text-[10px] font-bold text-overlay2 uppercase tracking-widest">Findet eure Perfect Matches</p>
|
||||
<h1 class="text-xl font-black tracking-tight" style="color: var(--primary);">AYTO MATCH-RECHNER</h1>
|
||||
<p class="text-xs font-bold uppercase tracking-widest theme-text-muted">Probability Engine</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="hidden sm:flex items-center gap-2 px-3 py-1.5 glass-panel rounded-xl">
|
||||
<i data-lucide="cpu" class="w-4 h-4 text-blue"></i>
|
||||
<span class="text-xs font-bold text-subtext1">Rust Engine 1.85</span>
|
||||
</div>
|
||||
<select id="theme-select" class="input-elegant py-1.5 px-3 text-xs font-bold border-none outline-none">
|
||||
<option value="mocha">Mocha</option>
|
||||
<option value="macchiato">Macchiato</option>
|
||||
<option value="frappe">Frappé</option>
|
||||
<option value="latte">Latte</option>
|
||||
<div class="flex items-center gap-3 w-full sm:w-auto">
|
||||
<label for="theme-select" class="text-xs font-bold uppercase tracking-widest theme-text-muted hidden sm:block">Theme:</label>
|
||||
<select id="theme-select" class="theme-input py-2 px-4 rounded-xl text-xs font-bold w-full sm:w-auto cursor-pointer border-2" style="border-color: var(--primary);">
|
||||
<option value="ayto">📺 Are You The One (Neon)</option>
|
||||
<option value="sai">🐉 Scaled And Icy (TØP)</option>
|
||||
<option value="trench">🦅 Trench (TØP)</option>
|
||||
<option value="blurryface">🔴 Blurryface (TØP)</option>
|
||||
<option value="clancy">🔥 Clancy (TØP)</option>
|
||||
</select>
|
||||
</div>
|
||||
</header>
|
||||
</nav>
|
||||
|
||||
<main class="max-w-screen-2xl mx-auto px-4 sm:px-8 flex flex-col xl:flex-row gap-8 pb-12">
|
||||
<!-- Main Content -->
|
||||
<main class="w-full max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8 items-start">
|
||||
|
||||
<!-- Sidebar / Controls -->
|
||||
<aside class="w-full xl:w-[420px] shrink-0 space-y-6">
|
||||
<!-- LEFT COLUMN: INPUTS -->
|
||||
<div class="lg:col-span-4 flex flex-col gap-6">
|
||||
|
||||
<!-- Stage 1: The Cast -->
|
||||
<section class="glass-panel rounded-3xl p-6 border border-white/5 animate-slide-in shadow-xl">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-8 h-8 rounded-full bg-blue/20 text-blue flex items-center justify-center">
|
||||
<i data-lucide="users" class="w-4 h-4"></i>
|
||||
<!-- 1. The Cast -->
|
||||
<section class="theme-panel rounded-3xl p-6 shadow-xl animate-slide-in">
|
||||
<div class="flex items-center gap-3 mb-6 border-b pb-4" style="border-color: var(--panel-border);">
|
||||
<i data-lucide="users" class="w-6 h-6 theme-primary-text"></i>
|
||||
<h2 class="text-lg font-black uppercase tracking-widest">1. Die Villa</h2>
|
||||
</div>
|
||||
<h2 class="text-sm font-black uppercase tracking-[0.1em]">1. Wer ist in der Villa?</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 mb-6">
|
||||
<div>
|
||||
<label class="text-[10px] font-black text-overlay2 uppercase tracking-widest mb-2 block">Gruppe A (Wählen zuerst)</label>
|
||||
<textarea id="group1-names" rows="4" class="w-full input-elegant p-4 text-sm placeholder:text-surface2 scrollbar-hide" placeholder="Namen untereinander, z.B. Sophie, Lara..."></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-[10px] font-black text-overlay2 uppercase tracking-widest mb-2 block">Gruppe B (Die Auswahl)</label>
|
||||
<textarea id="group2-names" rows="4" class="w-full input-elegant p-4 text-sm placeholder:text-surface2" placeholder="Namen untereinander, z.B. Max, Leon..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="setup-button" class="w-full btn-elegant py-4 bg-blue text-base hover:shadow-[0_0_20px_rgba(137,180,250,0.3)]">
|
||||
VILLA INITIALISIEREN
|
||||
</button>
|
||||
|
||||
<div class="flex gap-2 mt-4">
|
||||
<button id="save-button" class="flex-1 btn-elegant py-2.5 bg-surface1 text-xs hover:bg-surface2 disabled:opacity-30 disabled:pointer-events-none" disabled>
|
||||
<span class="flex items-center justify-center gap-2"><i data-lucide="save" class="w-3 h-3"></i>SICHERN</span>
|
||||
</button>
|
||||
<button id="load-button" class="flex-1 btn-elegant py-2.5 bg-surface1 text-xs hover:bg-surface2">
|
||||
<span class="flex items-center justify-center gap-2"><i data-lucide="upload-cloud" class="w-3 h-3"></i>LADEN</span>
|
||||
</button>
|
||||
<input type="file" id="file-loader" class="hidden" accept=".json">
|
||||
</div>
|
||||
<p id="initial-possibilities-text" class="text-center mt-4 text-[10px] font-bold text-overlay1 italic tracking-wide"></p>
|
||||
</section>
|
||||
|
||||
<!-- Stage 2: Inputs -->
|
||||
<div id="input-sections" class="hidden space-y-6">
|
||||
<!-- Truth Booth -->
|
||||
<section class="glass-panel rounded-3xl p-6 border border-white/5 animate-slide-in shadow-xl">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-8 h-8 rounded-full bg-green/20 text-green flex items-center justify-center">
|
||||
<i data-lucide="search" class="w-4 h-4"></i>
|
||||
<div class="space-y-4 mb-6">
|
||||
<div>
|
||||
<label class="text-[10px] font-black uppercase tracking-widest mb-2 block theme-text-muted">Gruppe A (Wählt zuerst)</label>
|
||||
<textarea id="group1-names" rows="4" class="w-full theme-input rounded-xl p-4 text-sm" placeholder="Namen untereinander, z.B. Sophie, Lara..."></textarea>
|
||||
</div>
|
||||
<h2 class="text-sm font-black uppercase tracking-[0.1em]">2. Truth Booth Ergebnis</h2>
|
||||
</div>
|
||||
<form id="truth-booth-form" class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<select id="tb-group1" class="input-elegant p-3 text-sm"></select>
|
||||
<select id="tb-group2" class="input-elegant p-3 text-sm"></select>
|
||||
<div>
|
||||
<label class="text-[10px] font-black uppercase tracking-widest mb-2 block theme-text-muted">Gruppe B (Die Auswahl)</label>
|
||||
<textarea id="group2-names" rows="4" class="w-full theme-input rounded-xl p-4 text-sm" placeholder="Namen untereinander, z.B. Max, Leon..."></textarea>
|
||||
</div>
|
||||
<select id="tb-result" class="w-full input-elegant p-3 text-sm">
|
||||
<option value="no-match">❌ KEIN MATCH</option>
|
||||
<option value="match">💖 PERFECT MATCH</option>
|
||||
</select>
|
||||
<button type="submit" class="w-full btn-elegant py-3 bg-green/10 text-green border border-green/20 hover:bg-green/20">
|
||||
TRUTH BOOTH HINZUFÜGEN
|
||||
</div>
|
||||
|
||||
<button id="setup-button" class="w-full theme-btn py-4 rounded-xl shadow-lg">
|
||||
VILLA STARTEN
|
||||
</button>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3 mt-4">
|
||||
<button id="save-button" class="w-full theme-btn-outline py-2.5 rounded-xl text-xs disabled:opacity-30 flex items-center justify-center gap-2" disabled>
|
||||
<i data-lucide="save" class="w-4 h-4"></i> SICHERN
|
||||
</button>
|
||||
</form>
|
||||
<div id="truth-booth-list" class="mt-6 space-y-2"></div>
|
||||
<button id="load-button" class="w-full theme-btn-outline py-2.5 rounded-xl text-xs flex items-center justify-center gap-2">
|
||||
<i data-lucide="folder-up" class="w-4 h-4"></i> LADEN
|
||||
</button>
|
||||
<input type="file" id="file-loader" class="hidden" accept=".json">
|
||||
</div>
|
||||
<p id="initial-possibilities-text" class="text-center mt-4 text-[10px] font-bold theme-text-muted italic tracking-wide"></p>
|
||||
</section>
|
||||
|
||||
<!-- Matchup Ceremony -->
|
||||
<section class="glass-panel rounded-3xl p-6 border border-white/5 animate-slide-in shadow-xl">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-8 h-8 rounded-full bg-mauve/20 text-mauve flex items-center justify-center">
|
||||
<i data-lucide="flame" class="w-4 h-4"></i>
|
||||
<!-- Hidden until setup -->
|
||||
<div id="input-sections" class="hidden flex-col gap-6">
|
||||
|
||||
<!-- 2. Truth Booth -->
|
||||
<section class="theme-panel rounded-3xl p-6 shadow-xl animate-slide-in">
|
||||
<div class="flex items-center gap-3 mb-6 border-b pb-4" style="border-color: var(--panel-border);">
|
||||
<i data-lucide="monitor-play" class="w-6 h-6 theme-primary-text"></i>
|
||||
<h2 class="text-lg font-black uppercase tracking-widest">2. Truth Booth</h2>
|
||||
</div>
|
||||
<h2 class="text-sm font-black uppercase tracking-[0.1em]">3. Matching Night</h2>
|
||||
</div>
|
||||
<form id="ceremony-form">
|
||||
<div id="ceremony-pairs-container" class="space-y-2 mb-6 max-h-[300px] overflow-y-auto pr-2 table-container"></div>
|
||||
<div class="bg-base/40 rounded-2xl p-4 mb-4 flex items-center justify-between border border-white/5">
|
||||
<span class="text-[10px] font-black text-overlay2 uppercase tracking-[0.15em]">Anzahl Lichter (Beams)</span>
|
||||
<div class="flex items-center gap-4">
|
||||
<button type="button" onclick="ceremony_beams.stepDown()" class="w-8 h-8 flex items-center justify-center hover:bg-white/5 rounded-lg">
|
||||
<i data-lucide="minus" class="w-3 h-3 text-overlay1"></i>
|
||||
</button>
|
||||
<input type="number" id="ceremony-beams" min="0" value="0" class="w-12 bg-transparent text-center font-black text-lg focus:outline-none">
|
||||
<button type="button" onclick="ceremony_beams.stepUp()" class="w-8 h-8 flex items-center justify-center hover:bg-white/5 rounded-lg">
|
||||
<i data-lucide="plus" class="w-3 h-3 text-overlay1"></i>
|
||||
</button>
|
||||
<form id="truth-booth-form" class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<select id="tb-group1" class="theme-input rounded-xl p-3 text-sm"></select>
|
||||
<select id="tb-group2" class="theme-input rounded-xl p-3 text-sm"></select>
|
||||
</div>
|
||||
<select id="tb-result" class="w-full theme-input rounded-xl p-3 text-sm font-bold">
|
||||
<option value="no-match">❌ KEIN MATCH</option>
|
||||
<option value="match">💖 PERFECT MATCH</option>
|
||||
</select>
|
||||
<button type="submit" class="w-full theme-btn py-3 rounded-xl text-sm border border-transparent hover:border-white/20">
|
||||
ERGEBNIS HINZUFÜGEN
|
||||
</button>
|
||||
</form>
|
||||
<div id="truth-booth-list" class="mt-6 flex flex-col gap-2"></div>
|
||||
</section>
|
||||
|
||||
<!-- 3. Matching Night -->
|
||||
<section class="theme-panel rounded-3xl p-6 shadow-xl animate-slide-in">
|
||||
<div class="flex items-center gap-3 mb-6 border-b pb-4" style="border-color: var(--panel-border);">
|
||||
<i data-lucide="flame" class="w-6 h-6 theme-secondary-text"></i>
|
||||
<h2 class="text-lg font-black uppercase tracking-widest">3. Matching Night</h2>
|
||||
</div>
|
||||
<button type="submit" class="w-full btn-elegant py-4 bg-mauve/10 text-mauve border border-mauve/20 hover:bg-mauve/20">
|
||||
NACHT SPEICHERN
|
||||
</button>
|
||||
</form>
|
||||
<div id="ceremony-list" class="mt-6 space-y-4"></div>
|
||||
</section>
|
||||
<form id="ceremony-form">
|
||||
<div id="ceremony-pairs-container" class="space-y-3 mb-6 max-h-[350px] overflow-y-auto pr-2"></div>
|
||||
|
||||
<div class="theme-input rounded-xl p-4 mb-4 flex items-center justify-between border-2" style="border-color: var(--panel-border);">
|
||||
<span class="text-xs font-black uppercase tracking-widest theme-text-muted">Lichter (Beams)</span>
|
||||
<div class="flex items-center gap-4">
|
||||
<button type="button" onclick="ceremony_beams.stepDown()" class="w-8 h-8 flex items-center justify-center hover:bg-black/20 rounded-lg theme-text">
|
||||
<i data-lucide="minus" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<input type="number" id="ceremony-beams" min="0" value="0" class="w-12 bg-transparent text-center font-black text-2xl focus:outline-none theme-text">
|
||||
<button type="button" onclick="ceremony_beams.stepUp()" class="w-8 h-8 flex items-center justify-center hover:bg-black/20 rounded-lg theme-text">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full theme-btn py-4 rounded-xl shadow-lg" style="background: var(--secondary); color: var(--bg-color);">
|
||||
NACHT SPEICHERN
|
||||
</button>
|
||||
</form>
|
||||
<div id="ceremony-list" class="mt-6 flex flex-col gap-4"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content: Results -->
|
||||
<div class="flex-1 space-y-8">
|
||||
<!-- RIGHT COLUMN: RESULTS -->
|
||||
<div class="lg:col-span-8 flex flex-col gap-6">
|
||||
|
||||
<!-- Dashboard / Status -->
|
||||
<section id="status-dashboard" class="hidden animate-slide-in">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="glass-panel rounded-[32px] p-8 relative overflow-hidden shadow-xl">
|
||||
<div class="absolute -right-8 -top-8 w-32 h-32 bg-blue/10 rounded-full blur-3xl"></div>
|
||||
<h3 class="text-[11px] font-black text-overlay2 uppercase tracking-[0.25em] mb-4">Verbleibende Möglichkeiten</h3>
|
||||
<p id="total-possibilities" class="text-7xl font-black gradient-heading tracking-tighter">--</p>
|
||||
<div class="mt-6 flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-blue animate-pulse"></span>
|
||||
<span class="text-[10px] font-bold text-blue uppercase tracking-widest">Rust-Engine berechnet live</span>
|
||||
<section id="status-dashboard" class="hidden animate-slide-in">
|
||||
<div class="theme-panel rounded-3xl p-8 relative overflow-hidden flex flex-col md:flex-row items-center justify-between gap-6 shadow-2xl">
|
||||
<!-- Decorative Glow -->
|
||||
<div class="absolute -right-16 -top-16 w-64 h-64 rounded-full blur-3xl opacity-20" style="background: var(--primary); z-index: 0;"></div>
|
||||
|
||||
<div style="z-index: 1;">
|
||||
<h3 class="text-xs font-black uppercase tracking-[0.2em] mb-2 theme-text-muted">Verbleibende Möglichkeiten</h3>
|
||||
<p id="total-possibilities" class="text-5xl sm:text-7xl font-black tracking-tighter neon-glow" style="color: var(--primary);">--</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-auto flex flex-col gap-3" style="z-index: 1;">
|
||||
<button id="calculate-button" class="w-full theme-btn py-4 px-8 rounded-xl flex items-center justify-center gap-3">
|
||||
<i data-lucide="refresh-cw" class="w-5 h-5"></i> NEU BERECHNEN
|
||||
</button>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full animate-pulse" style="background: var(--success);"></span>
|
||||
<span class="text-[10px] font-bold uppercase tracking-widest theme-text-muted">Live Engine Active</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="grid-section" class="hidden theme-panel rounded-3xl p-6 overflow-hidden shadow-2xl animate-slide-in">
|
||||
<div class="flex flex-col xl:flex-row xl:items-end justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-black tracking-tight mb-1 theme-text">Match-Matrix</h2>
|
||||
<p class="text-xs font-bold uppercase tracking-widest theme-text-muted">Wahrscheinlichkeit pro Paar</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 text-[10px] font-bold tracking-widest">
|
||||
<div class="px-3 py-1.5 rounded-lg flex items-center gap-2 border" style="background: rgba(0,255,136,0.1); color: var(--success); border-color: rgba(0,255,136,0.3);">
|
||||
<i data-lucide="check-circle" class="w-3 h-3"></i> 100% MATCH
|
||||
</div>
|
||||
<div class="px-3 py-1.5 rounded-lg flex items-center gap-2 border" style="background: rgba(255,51,51,0.1); color: var(--error); border-color: rgba(255,51,51,0.3);">
|
||||
<i data-lucide="x-circle" class="w-3 h-3"></i> UNMÖGLICH
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<button id="calculate-button" class="flex-1 btn-elegant bg-gradient-to-br from-blue to-sapphire text-base p-8 shadow-2xl hover:scale-[1.02] active:scale-[0.98] group">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<i data-lucide="refresh-cw" class="w-6 h-6 text-base group-hover:rotate-180 transition-transform duration-500"></i>
|
||||
<span class="text-sm tracking-[0.2em]">WAHRSCHEINLICHKEITEN AKTUALISIEREN</span>
|
||||
</div>
|
||||
</button>
|
||||
<div class="overflow-x-auto border rounded-xl" style="border-color: var(--panel-border);">
|
||||
<table id="probability-table" class="w-full text-left border-collapse min-w-max">
|
||||
<thead id="probability-table-head" style="background: var(--input-bg);" class="text-[10px] font-black uppercase tracking-widest theme-text-muted"></thead>
|
||||
<tbody id="probability-table-body" class="divide-y" style="border-color: var(--panel-border);"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- Probability Matrix -->
|
||||
<section id="grid-section" class="hidden glass-panel rounded-[40px] p-8 border border-white/5 animate-slide-in shadow-2xl relative">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h2 class="text-2xl font-black tracking-tight mb-1">Wahrscheinlichkeits-Matrix</h2>
|
||||
<p class="text-[10px] font-bold text-overlay2 uppercase tracking-[0.2em]">Chance auf Perfect Match pro Paarung</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="px-3 py-1 bg-green/10 text-green rounded-full text-[10px] font-bold border border-green/20">BESTÄTIGT</div>
|
||||
<div class="px-3 py-1 bg-red/10 text-red rounded-full text-[10px] font-bold border border-red/20">UNMÖGLICH</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container overflow-x-auto rounded-[24px] border border-white/5 bg-black/10">
|
||||
<table id="probability-table" class="w-full text-left">
|
||||
<thead id="probability-table-head" class="bg-surface0/30 text-[9px] font-black uppercase tracking-[0.2em] text-overlay1"></thead>
|
||||
<tbody id="probability-table-body" class="divide-y divide-white/5"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div class="mt-8 flex flex-wrap gap-6 border-t border-white/5 pt-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded bg-green shadow-[0_0_10px_rgba(166,227,161,0.3)]"></div>
|
||||
<span class="text-[10px] font-bold text-overlay1 uppercase tracking-widest">100% Match</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded bg-red opacity-50"></div>
|
||||
<span class="text-[10px] font-bold text-overlay1 uppercase tracking-widest">Ausgeschlossen</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded bg-peach"></div>
|
||||
<span class="text-[10px] font-bold text-overlay1 uppercase tracking-widest">Wahrscheinlichkeit</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- UI Components: Loading & Error -->
|
||||
<div id="error-message" class="hidden fixed bottom-8 right-8 max-w-sm glass-panel border-red/20 bg-red/5 text-red p-4 rounded-2xl shadow-2xl z-[100] border backdrop-blur-2xl flex items-start gap-3 transition-all duration-500">
|
||||
<div class="p-2 bg-red/10 rounded-lg">
|
||||
<i data-lucide="alert-circle" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<!-- Global Modals/Overlays -->
|
||||
<div id="error-message" class="hidden fixed bottom-6 left-1/2 -translate-x-1/2 theme-panel border-l-4 px-6 py-4 rounded-xl shadow-2xl z-[100] flex items-center gap-4 transition-all duration-300" style="border-left-color: var(--error);">
|
||||
<i data-lucide="alert-triangle" class="w-6 h-6" style="color: var(--error);"></i>
|
||||
<div>
|
||||
<p class="text-xs font-black uppercase tracking-widest mb-1">Algorithmus Warnung</p>
|
||||
<p id="error-text" class="text-[11px] font-medium opacity-90 leading-relaxed"></p>
|
||||
<p class="text-xs font-black uppercase tracking-widest mb-1 theme-text">Achtung</p>
|
||||
<p id="error-text" class="text-sm font-medium theme-text-muted"></p>
|
||||
</div>
|
||||
<button onclick="errorMessage.classList.add('hidden')" class="opacity-40 hover:opacity-100 transition-opacity">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onclick="document.getElementById('error-message').classList.add('hidden')" class="ml-4 opacity-50 hover:opacity-100 theme-text"><i data-lucide="x" class="w-4 h-4"></i></button>
|
||||
</div>
|
||||
|
||||
<div id="loading-spinner" class="hidden fixed inset-0 bg-base/80 backdrop-blur-xl flex items-center justify-center z-[200]">
|
||||
<div class="flex flex-col items-center gap-6 p-12 rounded-[48px] bg-white/[0.02] border border-white/5">
|
||||
<div class="relative w-20 h-20">
|
||||
<div class="absolute inset-0 border-4 border-blue/10 rounded-full"></div>
|
||||
<div class="absolute inset-0 border-4 border-blue border-t-transparent rounded-full animate-spin"></div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-xs font-black text-blue tracking-[0.4em] uppercase mb-2">Analysiere Muster</p>
|
||||
<p class="text-[10px] text-overlay1 font-medium italic">Berechne surjective Mappings in Rust...</p>
|
||||
<div id="loading-spinner" class="hidden fixed inset-0 backdrop-blur-md flex items-center justify-center z-[200]" style="background: rgba(0,0,0,0.7);">
|
||||
<div class="theme-panel p-10 rounded-3xl flex flex-col items-center shadow-2xl">
|
||||
<div class="relative w-16 h-16 mb-6">
|
||||
<div class="absolute inset-0 rounded-full border-4 opacity-20" style="border-color: var(--primary);"></div>
|
||||
<div class="absolute inset-0 rounded-full border-4 border-t-transparent animate-spin" style="border-color: var(--primary);"></div>
|
||||
</div>
|
||||
<p class="text-sm font-black uppercase tracking-[0.3em] theme-text mb-2">Algorithmus läuft</p>
|
||||
<p class="text-xs theme-text-muted">Berechne Kombinationen...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -420,35 +443,56 @@
|
||||
// Initialize Icons
|
||||
lucide.createIcons();
|
||||
|
||||
themeSelect.addEventListener('change', (e) => {
|
||||
document.documentElement.setAttribute('data-theme', e.target.value);
|
||||
localStorage.setItem('ayto-theme', e.target.value);
|
||||
});
|
||||
|
||||
const savedTheme = localStorage.getItem('ayto-theme');
|
||||
if (savedTheme) {
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
themeSelect.value = savedTheme;
|
||||
// --- Cookie Theme Manager ---
|
||||
function setCookie(name, value, days) {
|
||||
let expires = "";
|
||||
if (days) {
|
||||
let date = new Date();
|
||||
date.setTime(date.getTime() + (days*24*60*60*1000));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
let nameEQ = name + "=";
|
||||
let ca = document.cookie.split(';');
|
||||
for(let i=0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
themeSelect.value = theme;
|
||||
setCookie('ayto-theme', theme, 365); // Save for 1 year
|
||||
}
|
||||
|
||||
themeSelect.addEventListener('change', (e) => applyTheme(e.target.value));
|
||||
|
||||
// Load saved theme on boot
|
||||
const savedTheme = getCookie('ayto-theme') || 'ayto';
|
||||
applyTheme(savedTheme);
|
||||
|
||||
// --- UI Feedback ---
|
||||
function showLoading() { loadingSpinner.classList.remove("hidden"); }
|
||||
function hideLoading() { loadingSpinner.classList.add("hidden"); }
|
||||
|
||||
function showError(message) {
|
||||
errorText.textContent = message;
|
||||
errorMessage.classList.remove("hidden");
|
||||
errorMessage.style.opacity = "1";
|
||||
setTimeout(() => {
|
||||
errorMessage.style.opacity = "0";
|
||||
setTimeout(() => errorMessage.classList.add("hidden"), 500);
|
||||
}, 5000);
|
||||
setTimeout(() => errorMessage.classList.add("hidden"), 5000);
|
||||
}
|
||||
|
||||
// --- Core Logic ---
|
||||
function handleSetupContestants() {
|
||||
const g1 = g1NamesText.value.split('\n').map(s => s.trim()).filter(s => s.length > 0);
|
||||
const g2 = g2NamesText.value.split('\n').map(s => s.trim()).filter(s => s.length > 0);
|
||||
|
||||
if (g1.length === 0 || g2.length === 0) return showError("Bitte gib die Namen der Teilnehmer ein.");
|
||||
if (g1.length === 0 || g2.length === 0) return showError("Bitte Namen für beide Gruppen eintragen.");
|
||||
if (g1.length > g2.length) return showError("Gruppe A darf nicht größer sein als Gruppe B.");
|
||||
|
||||
group1Names = g1;
|
||||
@@ -464,11 +508,14 @@
|
||||
initialPossibilitiesText.textContent = `${group1Names.length} in Gruppe A • ${group2Names.length} in Gruppe B`;
|
||||
|
||||
inputSections.classList.remove("hidden");
|
||||
inputSections.classList.add("flex");
|
||||
statusDashboard.classList.remove("hidden");
|
||||
gridSection.classList.remove("hidden");
|
||||
|
||||
saveButton.disabled = false;
|
||||
lucide.createIcons();
|
||||
|
||||
handleCalculate(); // Auto-calculate on start
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -480,10 +527,10 @@
|
||||
|
||||
function populateCeremonyPairBuilder() {
|
||||
ceremonyPairsContainer.innerHTML = group1Names.map(name => `
|
||||
<div class="flex items-center gap-3 p-2 group transition-all">
|
||||
<span class="text-[10px] font-black text-overlay2 w-16 truncate uppercase">${name}</span>
|
||||
<select data-g1-name="${name}" class="ceremony-pair-select flex-1 input-elegant p-2 text-xs">
|
||||
<option value="">Paarung wählen...</option>
|
||||
<div class="flex items-center gap-3 p-2 border-b last:border-0" style="border-color: var(--panel-border);">
|
||||
<span class="text-xs font-bold w-20 truncate theme-text">${name}</span>
|
||||
<select data-g1-name="${name}" class="ceremony-pair-select flex-1 theme-input rounded-xl p-2 text-xs font-bold">
|
||||
<option value="">Wähle Match...</option>
|
||||
${group2Names.map(g2name => `<option value="${g2name}">${g2name}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
@@ -492,14 +539,16 @@
|
||||
|
||||
function renderTruthBoothUI(booth) {
|
||||
const el = document.createElement('div');
|
||||
const color = booth.isMatch ? 'text-green border-green/20' : 'text-red border-red/20';
|
||||
el.className = `p-3 rounded-2xl flex justify-between items-center bg-white/[0.02] border animate-slide-in ${color}`;
|
||||
el.className = `p-3 rounded-xl flex justify-between items-center theme-input border-l-4 animate-slide-in shadow-md`;
|
||||
el.style.borderLeftColor = booth.isMatch ? 'var(--success)' : 'var(--error)';
|
||||
el.innerHTML = `
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="${booth.isMatch ? 'heart' : 'x-circle'}" class="w-3 h-3"></i>
|
||||
<span class="text-[10px] font-black tracking-widest uppercase truncate">${booth.p1} + ${booth.p2}</span>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-xs font-black uppercase tracking-wider theme-text">${booth.p1} + ${booth.p2}</span>
|
||||
<span class="text-[10px] font-bold uppercase tracking-widest px-2 py-1 rounded" style="background: ${booth.isMatch ? 'rgba(0,255,136,0.1)' : 'rgba(255,51,51,0.1)'}; color: ${booth.isMatch ? 'var(--success)' : 'var(--error)'}">
|
||||
${booth.isMatch ? 'MATCH' : 'NO MATCH'}
|
||||
</span>
|
||||
</div>
|
||||
<button data-id="${booth.id}" class="remove-tb-btn hover:scale-125 transition-transform"><i data-lucide="trash-2" class="w-3 h-3"></i></button>
|
||||
<button data-id="${booth.id}" class="remove-tb-btn p-1 transition-all opacity-50 hover:opacity-100" style="color: var(--error);"><i data-lucide="trash-2" class="w-4 h-4"></i></button>
|
||||
`;
|
||||
tbList.appendChild(el);
|
||||
el.querySelector('.remove-tb-btn').addEventListener('click', handleRemoveTruthBooth);
|
||||
@@ -523,19 +572,21 @@
|
||||
|
||||
function renderCeremonyUI(ceremony, index) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'p-5 rounded-3xl glass-panel border-white/5 shadow-lg animate-slide-in group';
|
||||
el.className = 'p-5 rounded-2xl theme-input border animate-slide-in shadow-md relative overflow-hidden';
|
||||
el.style.borderColor = 'var(--panel-border)';
|
||||
el.innerHTML = `
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-6 h-6 rounded-lg bg-mauve/10 text-mauve flex items-center justify-center text-[10px] font-black">${index}</span>
|
||||
<span class="text-[10px] font-black uppercase tracking-widest text-mauve">${ceremony.beams} Lichter</span>
|
||||
<div class="absolute top-0 left-0 w-1 h-full" style="background: var(--secondary);"></div>
|
||||
<div class="flex justify-between items-center mb-4 pb-3 border-b" style="border-color: var(--panel-border);">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-sm font-black uppercase tracking-widest theme-text">Nacht ${index}</span>
|
||||
<span class="px-2.5 py-1 rounded-md text-[10px] font-bold uppercase" style="background: var(--primary); color: var(--bg-color);">${ceremony.beams} Lichter</span>
|
||||
</div>
|
||||
<button data-id="${ceremony.id}" class="remove-ceremony-btn opacity-0 group-hover:opacity-100 transition-all text-red"><i data-lucide="trash-2" class="w-3 h-3"></i></button>
|
||||
<button data-id="${ceremony.id}" class="remove-ceremony-btn p-1 transition-all opacity-50 hover:opacity-100" style="color: var(--error);"><i data-lucide="trash-2" class="w-4 h-4"></i></button>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
${Object.entries(ceremony.pairs).map(([p1, p2]) => `
|
||||
<div class="text-[9px] bg-base/50 px-2.5 py-1 rounded-full border border-white/5 font-bold text-overlay1">
|
||||
${p1} ↔ ${p2}
|
||||
<div class="text-[10px] px-2 py-1 rounded border font-bold theme-text flex gap-1" style="background: var(--input-bg); border-color: var(--panel-border);">
|
||||
<span>${p1}</span> <span class="theme-text-muted">&</span> <span>${p2}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
@@ -553,12 +604,12 @@
|
||||
|
||||
for (const select of pairSelectors) {
|
||||
const g2Name = select.value;
|
||||
if (!g2Name) return showError("Bitte wähle alle Paarungen für diese Nacht aus.");
|
||||
if (!g2Name) return showError("Bitte wähle alle Paarungen aus.");
|
||||
selectedG2Names.push(g2Name);
|
||||
pairs[select.dataset.g1Name] = g2Name;
|
||||
}
|
||||
|
||||
if (new Set(selectedG2Names).size !== selectedG2Names.length) return showError("Jede Person aus Gruppe B darf nur einmal gewählt werden.");
|
||||
if (new Set(selectedG2Names).size !== selectedG2Names.length) return showError("Jede Person aus Gruppe B darf pro Nacht nur einmal gewählt werden.");
|
||||
|
||||
const beams = parseInt(ceremonyBeams.value, 10);
|
||||
const ceremony = { id: Date.now(), pairs, beams };
|
||||
@@ -585,26 +636,28 @@
|
||||
ceremonies: ceremonies.map(c => ({ pairs: c.pairs, beams: c.beams }))
|
||||
};
|
||||
|
||||
// The browser proxy resolves /api/solve to the backend automatically
|
||||
const res = await fetch('/api/solve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Rechenfehler im Backend.");
|
||||
if (!res.ok) throw new Error("Backend Fehler");
|
||||
const results = await res.json();
|
||||
|
||||
totalPossibilitiesText.textContent = results.possibilities.toLocaleString();
|
||||
totalPossibilitiesText.textContent = results.possibilities.toLocaleString('de-DE');
|
||||
|
||||
if (results.possibilities === 0) {
|
||||
showError("WIDERSPRUCH: Die eingegebenen Daten sind mathematisch unmöglich.");
|
||||
showError("WIDERSPRUCH: Die eingegebenen Daten sind mathematisch unmöglich!");
|
||||
probTableBody.innerHTML = '';
|
||||
probTableHead.innerHTML = '';
|
||||
} else {
|
||||
displayProbabilityGrid(results.grid_data);
|
||||
}
|
||||
} catch (e) {
|
||||
showError("Verbindung zum Server fehlgeschlagen.");
|
||||
console.error(e);
|
||||
showError("Verbindung zum Server fehlgeschlagen. Stelle sicher, dass das Backend läuft.");
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
@@ -613,26 +666,31 @@
|
||||
function displayProbabilityGrid(gridData) {
|
||||
probTableHead.innerHTML = `
|
||||
<tr>
|
||||
<th class="p-4 border-b border-white/5"></th>
|
||||
${gridData.columns.map(name => `<th class="p-4 border-b border-white/5 whitespace-nowrap">${name}</th>`).join('')}
|
||||
<th class="p-3 border-b border-r" style="border-color: var(--panel-border);"></th>
|
||||
${gridData.columns.map(name => `<th class="p-3 border-b whitespace-nowrap text-center" style="border-color: var(--panel-border);">${name}</th>`).join('')}
|
||||
</tr>
|
||||
`;
|
||||
|
||||
probTableBody.innerHTML = gridData.index.map((g1Name, rowIndex) => `
|
||||
<tr class="hover:bg-white/[0.02] transition-colors">
|
||||
<th class="p-4 text-[10px] font-black border-r border-white/5 bg-white/[0.01] sticky left-0 z-10 uppercase tracking-widest">${g1Name}</th>
|
||||
<th class="p-3 text-xs font-black border-r sticky left-0 z-10 theme-input" style="border-color: var(--panel-border);">${g1Name}</th>
|
||||
${gridData.columns.map((g2Name, colIndex) => {
|
||||
const prob = gridData.data[rowIndex][colIndex] * 100;
|
||||
let cellClass = 'text-overlay1';
|
||||
let badgeClass = 'bg-white/5 text-overlay1';
|
||||
let badgeStyle = '';
|
||||
let textContent = `${prob.toFixed(1)}%`;
|
||||
|
||||
if (prob >= 99.9) badgeClass = 'bg-green/20 text-green shadow-[0_0_15px_rgba(166,227,161,0.2)]';
|
||||
else if (prob <= 0.1) badgeClass = 'bg-red/10 text-red opacity-30';
|
||||
else if (prob > 50) badgeClass = 'bg-blue/20 text-blue';
|
||||
else if (prob > 0) badgeClass = 'bg-peach/20 text-peach';
|
||||
if (prob >= 99.9) {
|
||||
badgeStyle = 'background: rgba(0,255,136,0.15); color: var(--success); border: 1px solid var(--success); box-shadow: 0 0 10px rgba(0,255,136,0.2);';
|
||||
textContent = '100%';
|
||||
} else if (prob <= 0.1) {
|
||||
badgeStyle = 'background: rgba(255,51,51,0.05); color: var(--error); opacity: 0.4;';
|
||||
textContent = '0%';
|
||||
} else {
|
||||
badgeStyle = 'color: var(--text-main); font-weight: 700;';
|
||||
}
|
||||
|
||||
return `<td class="p-4 border-white/5">
|
||||
<span class="prob-badge ${badgeClass}">${prob.toFixed(1)}%</span>
|
||||
return `<td class="p-3 text-center border-b" style="border-color: var(--panel-border);">
|
||||
<span class="prob-badge" style="${badgeStyle}">${textContent}</span>
|
||||
</td>`;
|
||||
}).join('')}
|
||||
</tr>
|
||||
@@ -649,22 +707,31 @@
|
||||
}
|
||||
|
||||
function handleLoadConfig() { fileLoader.click(); }
|
||||
|
||||
fileLoader.addEventListener("change", (e) => {
|
||||
const file = e.target.files[0];
|
||||
if(!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (ev) => {
|
||||
const s = JSON.parse(ev.target.result);
|
||||
g1NamesText.value = s.group1Names.join('\n');
|
||||
g2NamesText.value = s.group2Names.join('\n');
|
||||
handleSetupContestants();
|
||||
truthBooths = s.truthBooths; ceremonies = s.ceremonies;
|
||||
truthBooths.forEach(renderTruthBoothUI);
|
||||
ceremonies.forEach((c, i) => renderCeremonyUI(c, i + 1));
|
||||
handleCalculate();
|
||||
try {
|
||||
const s = JSON.parse(ev.target.result);
|
||||
g1NamesText.value = s.group1Names.join('\n');
|
||||
g2NamesText.value = s.group2Names.join('\n');
|
||||
handleSetupContestants();
|
||||
truthBooths = s.truthBooths || [];
|
||||
ceremonies = s.ceremonies || [];
|
||||
truthBooths.forEach(renderTruthBoothUI);
|
||||
ceremonies.forEach((c, i) => renderCeremonyUI(c, i + 1));
|
||||
handleCalculate();
|
||||
} catch(err) {
|
||||
showError("Fehler beim Laden der Datei. Ist sie beschädigt?");
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
e.target.value = ''; // Reset so same file can be loaded again
|
||||
});
|
||||
|
||||
// Event Listeners
|
||||
setupButton.addEventListener("click", handleSetupContestants);
|
||||
tbForm.addEventListener("submit", handleAddTruthBooth);
|
||||
ceremonyForm.addEventListener("submit", handleAddCeremony);
|
||||
|
||||
Reference in New Issue
Block a user