redesign ux

This commit is contained in:
2026-03-14 21:34:10 +01:00
parent c3589639ce
commit 774136e352

View File

@@ -3,12 +3,14 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AYTO? Match Calculator</title>
<title>AYTO Matcher | Probability Engine</title>
<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">
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<style>
:root, [data-theme="mocha"] {
@@ -60,68 +62,94 @@
font-family: 'Plus Jakarta Sans', sans-serif;
background: linear-gradient(135deg, var(--crust), var(--base));
background-attachment: fixed;
transition: background 0.5s ease;
}
.glass {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
.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);
}
[data-theme="latte"] .glass {
[data-theme="latte"] .glass-panel {
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.05);
}
.glass-card {
border-radius: 24px;
.step-pill {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glass-card:hover {
border-color: rgba(255, 255, 255, 0.15);
transform: translateY(-2px);
}
.gradient-text {
background: linear-gradient(to right, var(--lavender), var(--blue));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.btn-primary {
background: linear-gradient(135deg, var(--blue), var(--sapphire));
.step-pill.active {
background: var(--blue);
color: var(--base);
transition: all 0.2s;
}
.btn-primary:hover:not(:disabled) {
filter: brightness(1.1);
transform: scale(1.02);
box-shadow: 0 0 20px rgba(137, 180, 250, 0.3);
box-shadow: 0 0 15px rgba(137, 180, 250, 0.4);
}
.input-glass {
background: rgba(0, 0, 0, 0.2);
border: 1px solid var(--surface1);
transition: all 0.2s;
.gradient-heading {
background: linear-gradient(to right, var(--text), var(--lavender));
-webkit-background-clip: text;
-webkit-fill-color: transparent;
color: transparent;
}
.input-glass:focus {
.input-elegant {
background: rgba(0, 0, 0, 0.15);
border: 1px solid var(--surface2);
border-radius: 12px;
transition: all 0.2s ease;
}
.input-elegant:focus {
border-color: var(--blue);
background: rgba(0, 0, 0, 0.3);
box-shadow: 0 0 0 3px rgba(137, 180, 250, 0.15);
background: rgba(0, 0, 0, 0.2);
outline: none;
box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.1);
}
.table-container::-webkit-scrollbar { width: 6px; height: 6px; }
.table-container::-webkit-scrollbar-thumb { background-color: var(--overlay1); border-radius: 10px; }
.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);
}
.btn-elegant:active:not(:disabled) {
transform: translateY(0);
}
.prob-100 { background: linear-gradient(135deg, rgba(166, 227, 161, 0.2), rgba(166, 227, 161, 0.4)); color: var(--green); font-weight: 700; }
.prob-0 { background: linear-gradient(135deg, rgba(243, 139, 168, 0.1), rgba(243, 139, 168, 0.2)); color: var(--red); opacity: 0.8; }
.prob-possible { background: transparent; color: var(--text); }
.table-container::-webkit-scrollbar { width: 4px; height: 4px; }
.table-container::-webkit-scrollbar-thumb { background: var(--surface2); border-radius: 10px; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.animate-fade-in { animation: fadeIn 0.4s ease-out forwards; }
.prob-badge {
padding: 4px 8px;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 800;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-slide-in { animation: slideIn 0.5s ease-out forwards; }
.card-glow:hover {
box-shadow: 0 0 25px rgba(203, 166, 247, 0.1);
}
/* 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>
@@ -144,155 +172,221 @@
}
</script>
</head>
<body class="bg-base text-text min-h-full pb-12">
<body class="bg-base text-text min-h-full">
<!-- Top Bar -->
<nav class="w-full glass sticky top-0 z-50 border-b border-white/5 px-6 py-3 flex justify-between items-center">
<div class="flex items-center space-x-2">
<div class="w-8 h-8 rounded-lg bg-gradient-to-tr from-mauve to-pink flex items-center justify-center font-bold text-base">?</div>
<span class="text-lg font-bold gradient-text tracking-tight">AYTO Matcher</span>
<!-- 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">
<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="calculator" class="w-6 h-6 text-base"></i>
</div>
<div>
<h1 class="text-lg font-extrabold tracking-tight">AYTO Matcher</h1>
<p class="text-[10px] font-bold text-overlay2 uppercase tracking-widest">Surjective probability engine</p>
</div>
</div>
<div class="flex items-center space-x-4">
<select id="theme-select" class="px-3 py-1.5 glass rounded-xl text-xs font-semibold focus:ring-1 ring-blue/50 outline-none border-none">
<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 API 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>
</select>
</div>
</nav>
</header>
<div class="max-w-7xl mx-auto px-4 sm:px-8 mt-8">
<!-- Hero Header -->
<header class="text-center mb-12 animate-fade-in">
<h1 class="text-5xl font-extrabold tracking-tight mb-4 leading-tight">
Unlock the <span class="gradient-text">Perfect Matches</span>
</h1>
<p class="text-subtext1 max-w-2xl mx-auto text-lg">
The ultimate probability engine for "Are You The One?".
Our Rust-powered core calculates millions of possibilities in real-time.
</p>
</header>
<main class="max-w-screen-2xl mx-auto px-4 sm:px-8 flex flex-col xl:flex-row gap-8 pb-12">
<div id="error-message" class="hidden fixed top-20 left-1/2 -translate-x-1/2 glass border-red/20 bg-red/10 text-red px-6 py-3 rounded-2xl shadow-2xl z-[60] backdrop-blur-xl border flex items-center space-x-3 transition-all duration-300">
<span class="font-bold">Error</span>
<span id="error-text"></span>
</div>
<!-- Sidebar / Controls -->
<aside class="w-full xl:w-[420px] shrink-0 space-y-6">
<div id="loading-spinner" class="hidden fixed inset-0 bg-base/60 backdrop-blur-md flex items-center justify-center z-[100]">
<div class="glass p-8 rounded-[32px] flex flex-col items-center">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue mb-4"></div>
<p class="text-blue font-bold tracking-widest uppercase text-xs">Processing Algorithm</p>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-12 gap-8 items-start">
<!-- Sidebar: Setup & Logic -->
<div class="xl:col-span-5 space-y-6">
<!-- 1. Setup -->
<section class="glass glass-card p-8 animate-fade-in" style="animation-delay: 0.1s">
<div class="flex items-center space-x-3 mb-6">
<span class="w-8 h-8 rounded-full bg-blue/10 text-blue flex items-center justify-center font-bold text-sm">1</span>
<h2 class="text-xl font-bold">The Cast</h2>
<!-- 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>
</div>
<h2 class="text-sm font-black uppercase tracking-[0.1em]">1. Season Setup</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label class="block text-xs font-bold text-overlay2 uppercase tracking-widest mb-2">Matchers (G1)</label>
<textarea id="group1-names" rows="8" class="w-full input-glass rounded-2xl p-4 text-sm placeholder:text-surface2" placeholder="One name per line..."></textarea>
</div>
<div>
<label class="block text-xs font-bold text-overlay2 uppercase tracking-widest mb-2">The Pool (G2)</label>
<textarea id="group2-names" rows="8" class="w-full input-glass rounded-2xl p-4 text-sm placeholder:text-surface2" placeholder="One name per line..."></textarea>
</div>
<div class="space-y-4 mb-6">
<div>
<label class="text-[10px] font-black text-overlay2 uppercase tracking-widest mb-2 block">Matcher Group (G1)</label>
<textarea id="group1-names" rows="4" class="w-full input-elegant p-4 text-sm placeholder:text-surface2 scrollbar-hide" placeholder="E.g. Chris, Justin, Remy..."></textarea>
</div>
<div>
<label class="text-[10px] font-black text-overlay2 uppercase tracking-widest mb-2 block">The Pool (G2)</label>
<textarea id="group2-names" rows="4" class="w-full input-elegant p-4 text-sm placeholder:text-surface2" placeholder="E.g. Kai, Jenna, Max..."></textarea>
</div>
</div>
<button id="setup-button" class="w-full py-4 rounded-2xl btn-primary font-bold text-sm shadow-lg tracking-wide uppercase">
Initialize Season
<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)]">
INITIALIZE CALCULATOR
</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>SAVE</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>LOAD</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>
<div class="flex space-x-3 mt-4">
<button id="save-button" class="flex-1 py-3 glass rounded-xl text-xs font-bold text-subtext0 hover:text-text transition-colors uppercase disabled:opacity-30" disabled>Save</button>
<button id="load-button" class="flex-1 py-3 glass rounded-xl text-xs font-bold text-subtext0 hover:text-text transition-colors uppercase">Load</button>
<input type="file" id="file-loader" class="hidden" accept=".json">
</div>
<p id="initial-possibilities-text" class="text-center mt-4 text-xs font-medium text-overlay1 italic"></p>
</section>
<!-- 2. Truth Booth -->
<section id="truth-booth-section" class="glass glass-card p-8 hidden animate-fade-in">
<div class="flex items-center space-x-3 mb-6">
<span class="w-8 h-8 rounded-full bg-green/10 text-green flex items-center justify-center font-bold text-sm">2</span>
<h2 class="text-xl font-bold">Truth Booth</h2>
<!-- 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>
<h2 class="text-sm font-black uppercase tracking-[0.1em]">2. Truth Booth</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-glass rounded-xl p-3 text-sm focus:ring-0 appearance-none"></select>
<select id="tb-group2" class="input-glass rounded-xl p-3 text-sm focus:ring-0 appearance-none"></select>
<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>
<select id="tb-result" class="w-full input-glass rounded-xl p-3 text-sm focus:ring-0 appearance-none">
<option value="no-match">No Match</option>
<option value="match">Perfect Match</option>
<select id="tb-result" class="w-full input-elegant p-3 text-sm">
<option value="no-match">❌ NO MATCH</option>
<option value="match">💖 PERFECT MATCH</option>
</select>
<button type="submit" class="w-full py-3 bg-green/20 hover:bg-green/30 text-green rounded-xl font-bold text-sm transition-all uppercase tracking-wider">Add Result</button>
<button type="submit" class="w-full btn-elegant py-3 bg-green/10 text-green border border-green/20 hover:bg-green/20">
ADD TB RESULT
</button>
</form>
<div id="truth-booth-list" class="mt-6 space-y-2"></div>
</section>
<!-- 3. Ceremony -->
<section id="ceremony-section" class="glass glass-card p-8 hidden animate-fade-in">
<div class="flex items-center space-x-3 mb-6">
<span class="w-8 h-8 rounded-full bg-mauve/10 text-mauve flex items-center justify-center font-bold text-sm">3</span>
<h2 class="text-xl font-bold">Matchup Ceremony</h2>
<!-- 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>
</div>
<h2 class="text-sm font-black uppercase tracking-[0.1em]">3. Matchup Ceremony</h2>
</div>
<form id="ceremony-form">
<div id="ceremony-pairs-container" class="space-y-2 mb-6 max-h-60 overflow-y-auto pr-2 custom-scrollbar"></div>
<div class="glass rounded-2xl p-4 mb-6 flex items-center justify-between">
<label class="text-xs font-bold text-overlay2 uppercase tracking-widest">Correct Beams</label>
<input type="number" id="ceremony-beams" min="0" value="0" class="w-16 input-glass rounded-xl p-2 text-center font-bold">
<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]">Correct 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>
</div>
</div>
<button type="submit" class="w-full py-4 bg-mauve/20 hover:bg-mauve/30 text-mauve rounded-2xl font-bold text-sm transition-all uppercase tracking-wider">Record Ceremony</button>
<button type="submit" class="w-full btn-elegant py-4 bg-mauve/10 text-mauve border border-mauve/20 hover:bg-mauve/20">
RECORD CEREMONY
</button>
</form>
<div id="ceremony-list" class="mt-6 space-y-3"></div>
<div id="ceremony-list" class="mt-6 space-y-4"></div>
</section>
</div>
</aside>
<!-- Main Panel: Probabilities -->
<div class="xl:col-span-7 space-y-6">
<!-- Main Content: Results -->
<div class="flex-1 space-y-8">
<section id="calculate-section" class="hidden animate-fade-in">
<div class="glass glass-card p-1 bg-gradient-to-r from-blue/20 via-transparent to-sapphire/20">
<button id="calculate-button" class="w-full py-6 rounded-[22px] text-lg font-extrabold tracking-widest uppercase hover:bg-white/5 transition-all">
Run Probability Engine
<!-- 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 card-glow transition-all duration-300">
<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">Valid Scenarios</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">Live Engine Active</span>
</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="play" class="w-6 h-6 fill-base"></i>
<span class="text-sm tracking-[0.2em]">RECALCULATE PROBABILITIES</span>
</div>
</button>
</div>
</div>
</section>
<div id="results-display" class="mt-8 grid grid-cols-1 md:grid-cols-3 gap-4 hidden">
<div class="glass glass-card p-6 text-center md:col-span-3">
<h3 class="text-xs font-bold text-overlay2 uppercase tracking-[0.2em] mb-2">Remaining Possibilities</h3>
<p id="total-possibilities" class="text-6xl font-black gradient-text">--</p>
</div>
<!-- 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">Probability Matrix</h2>
<p class="text-[10px] font-bold text-overlay2 uppercase tracking-[0.2em]">Percentage chance per perfect match</p>
</div>
</section>
<section id="grid-section" class="glass glass-card p-8 hidden animate-fade-in">
<div class="flex items-center justify-between mb-8">
<div>
<h2 class="text-2xl font-bold mb-1">Probability Matrix</h2>
<p class="text-xs text-overlay2 font-medium tracking-wide uppercase">Real-time Surjective Mapping</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">CONFIRMED</div>
<div class="px-3 py-1 bg-red/10 text-red rounded-full text-[10px] font-bold border border-red/20">IMPOSSIBLE</div>
</div>
</div>
<div id="probability-grid-container" 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-white/5 text-[10px] font-black uppercase tracking-[0.2em] text-overlay1"></thead>
<tbody id="probability-table-body" class="divide-y divide-white/5"></tbody>
</table>
<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>
</section>
<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">0% Impossible</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">Calculated Prob</span>
</div>
</div>
</section>
</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>
<div>
<p class="text-xs font-black uppercase tracking-widest mb-1">Algorithm Warning</p>
<p id="error-text" class="text-[11px] font-medium opacity-90 leading-relaxed"></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>
</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">Analyzing Patterns</p>
<p class="text-[10px] text-overlay1 font-medium italic">Simulating surjective mappings in Rust...</p>
</div>
</div>
</div>
@@ -311,24 +405,23 @@
const loadButton = $("load-button");
const fileLoader = $("file-loader");
const themeSelect = $("theme-select");
const inputSections = $("input-sections");
const statusDashboard = $("status-dashboard");
const g1NamesText = $("group1-names");
const g2NamesText = $("group2-names");
const tbSection = $("truth-booth-section");
const tbForm = $("truth-booth-form");
const tbGroup1 = $("tb-group1");
const tbGroup2 = $("tb-group2");
const tbResult = $("tb-result");
const tbList = $("truth-booth-list");
const ceremonySection = $("ceremony-section");
const ceremonyForm = $("ceremony-form");
const ceremonyPairsContainer = $("ceremony-pairs-container");
const ceremonyBeams = $("ceremony-beams");
const ceremonyList = $("ceremony-list");
const calculateSection = $("calculate-section");
const gridSection = $("grid-section");
const resultsDisplay = $("results-display");
const totalPossibilitiesText = $("total-possibilities");
@@ -341,6 +434,9 @@
const loadingSpinner = $("loading-spinner");
const initialPossibilitiesText = $("initial-possibilities-text");
// Initialize Icons
lucide.createIcons();
themeSelect.addEventListener('change', (e) => {
document.documentElement.setAttribute('data-theme', e.target.value);
localStorage.setItem('ayto-theme', e.target.value);
@@ -358,19 +454,19 @@
function showError(message) {
errorText.textContent = message;
errorMessage.classList.remove("hidden");
errorMessage.classList.remove("opacity-0");
errorMessage.style.opacity = "1";
setTimeout(() => {
errorMessage.classList.add("opacity-0");
setTimeout(() => errorMessage.classList.add("hidden"), 300);
}, 4000);
errorMessage.style.opacity = "0";
setTimeout(() => errorMessage.classList.add("hidden"), 500);
}, 5000);
}
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("The villa is empty! Add names.");
if (g1.length > g2.length) return showError("Too many matchers, not enough pool!");
if (g1.length === 0 || g2.length === 0) return showError("Enter contestant names to begin.");
if (g1.length > g2.length) return showError("The matcher group must be smaller than or equal to the pool.");
group1Names = g1;
group2Names = g2;
@@ -382,15 +478,14 @@
populateSelectors();
populateCeremonyPairBuilder();
initialPossibilitiesText.textContent = `Villa ready: ${group1Names.length} vs ${group2Names.length}`;
initialPossibilitiesText.textContent = `${group1Names.length} Matches • ${group2Names.length} Potential Pools`;
tbSection.classList.remove("hidden");
ceremonySection.classList.remove("hidden");
calculateSection.classList.remove("hidden");
inputSections.classList.remove("hidden");
statusDashboard.classList.remove("hidden");
gridSection.classList.remove("hidden");
resultsDisplay.classList.add("hidden");
saveButton.disabled = false;
lucide.createIcons();
return true;
}
@@ -402,10 +497,10 @@
function populateCeremonyPairBuilder() {
ceremonyPairsContainer.innerHTML = group1Names.map(name => `
<div class="flex items-center space-x-3 bg-white/5 p-3 rounded-xl border border-white/5">
<span class="text-xs font-bold text-overlay2 w-20 truncate">${name}</span>
<select data-g1-name="${name}" class="ceremony-pair-select flex-1 bg-surface0 rounded-lg p-2 text-xs outline-none focus:ring-1 ring-blue/30">
<option value="">Select match...</option>
<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="">Choose match...</option>
${group2Names.map(g2name => `<option value="${g2name}">${g2name}</option>`).join('')}
</select>
</div>
@@ -414,13 +509,18 @@
function renderTruthBoothUI(booth) {
const el = document.createElement('div');
el.className = `p-4 rounded-2xl flex justify-between items-center glass animate-fade-in ${booth.isMatch ? 'border-green/20 text-green' : 'border-red/20 text-red'}`;
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.innerHTML = `
<span class="text-xs font-bold uppercase tracking-widest">${booth.p1} + ${booth.p2} = ${booth.isMatch ? 'Match' : 'No'}</span>
<button data-id="${booth.id}" class="remove-tb-btn opacity-50 hover:opacity-100">&times;</button>
<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>
<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>
`;
tbList.appendChild(el);
el.querySelector('.remove-tb-btn').addEventListener('click', handleRemoveTruthBooth);
lucide.createIcons();
}
function handleAddTruthBooth(e) {
@@ -428,33 +528,38 @@
const booth = { id: Date.now(), p1: tbGroup1.value, p2: tbGroup2.value, isMatch: tbResult.value === 'match' };
truthBooths.push(booth);
renderTruthBoothUI(booth);
handleCalculate();
}
function handleRemoveTruthBooth(e) {
const idToRemove = Number(e.target.dataset.id);
const idToRemove = Number(e.currentTarget.dataset.id);
truthBooths = truthBooths.filter(b => b.id !== idToRemove);
e.target.parentElement.remove();
e.currentTarget.closest('div').remove();
handleCalculate();
}
function renderCeremonyUI(ceremony, index) {
const el = document.createElement('div');
el.className = 'p-5 rounded-2xl glass border-white/5 ceremony-item animate-fade-in';
el.className = 'p-5 rounded-3xl glass-panel border-white/5 shadow-lg animate-slide-in group';
el.innerHTML = `
<div class="flex justify-between items-center mb-3">
<span class="text-xs font-black uppercase tracking-[0.2em] text-mauve">Ceremony ${index}${ceremony.beams} Beams</span>
<button data-id="${ceremony.id}" class="remove-ceremony-btn text-red/50 hover:text-red transition-colors">&times;</button>
<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} Beams</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>
</div>
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-wrap gap-1.5">
${Object.entries(ceremony.pairs).map(([p1, p2]) => `
<div class="text-[10px] bg-white/5 p-1.5 rounded-lg border border-white/5 text-overlay1 flex justify-between">
<span class="font-bold">${p1}</span>
<span>${p2}</span>
<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>
`).join('')}
</div>
`;
ceremonyList.appendChild(el);
el.querySelector('.remove-ceremony-btn').addEventListener('click', handleRemoveCeremony);
lucide.createIcons();
}
function handleAddCeremony(e) {
@@ -465,24 +570,26 @@
for (const select of pairSelectors) {
const g2Name = select.value;
if (!g2Name) return showError("Incomplete ceremony! Pair everyone.");
if (!g2Name) return showError("Match everyone before recording the ceremony.");
selectedG2Names.push(g2Name);
pairs[select.dataset.g1Name] = g2Name;
}
if (new Set(selectedG2Names).size !== selectedG2Names.length) return showError("Duplicate selection in ceremony!");
if (new Set(selectedG2Names).size !== selectedG2Names.length) return showError("Each pool member can only be selected once.");
const beams = parseInt(ceremonyBeams.value, 10);
const ceremony = { id: Date.now(), pairs, beams };
ceremonies.push(ceremony);
renderCeremonyUI(ceremony, ceremonies.length);
handleCalculate();
}
function handleRemoveCeremony(e) {
const idToRemove = Number(e.target.dataset.id);
const idToRemove = Number(e.currentTarget.dataset.id);
ceremonies = ceremonies.filter(c => c.id !== idToRemove);
ceremonyList.innerHTML = '';
ceremonies.forEach((c, i) => renderCeremonyUI(c, i + 1));
handleCalculate();
}
async function handleCalculate() {
@@ -501,22 +608,20 @@
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error("Backend malfunction");
if (!res.ok) throw new Error("Calculation engine error.");
const results = await res.json();
totalPossibilitiesText.textContent = results.possibilities.toLocaleString();
resultsDisplay.classList.remove("hidden");
if (results.possibilities === 0) {
showError("Impossible scenario detected!");
showError("IMPOSSIBLE SCENARIO: The data entered is contradictory.");
probTableBody.innerHTML = '';
probTableHead.innerHTML = '';
} else {
displayProbabilityGrid(results.grid_data);
gridSection.scrollIntoView({ behavior: 'smooth' });
}
} catch (e) {
showError("Connection failed");
showError("Could not reach the analysis server.");
} finally {
hideLoading();
}
@@ -525,20 +630,27 @@
function displayProbabilityGrid(gridData) {
probTableHead.innerHTML = `
<tr>
<th class="p-5 text-[10px] border-b border-white/5"></th>
${gridData.columns.map(name => `<th class="p-5 text-[10px] border-b border-white/5 whitespace-nowrap">${name}</th>`).join('')}
<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('')}
</tr>
`;
probTableBody.innerHTML = gridData.index.map((g1Name, rowIndex) => `
<tr class="hover:bg-white/[0.02] transition-colors">
<th class="p-5 text-[10px] font-black border-r border-white/5 bg-white/[0.01] sticky left-0 z-10">${g1Name}</th>
<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>
${gridData.columns.map((g2Name, colIndex) => {
const prob = gridData.data[rowIndex][colIndex] * 100;
let cellClass = 'prob-possible';
if (prob >= 99.9) cellClass = 'prob-100';
else if (prob <= 0.1) cellClass = 'prob-0';
return `<td class="${cellClass} p-5 text-sm font-bold border-white/5">${prob.toFixed(1)}%</td>`;
let cellClass = 'text-overlay1';
let badgeClass = 'bg-white/5 text-overlay1';
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';
return `<td class="p-4 border-white/5">
<span class="prob-badge ${badgeClass}">${prob.toFixed(1)}%</span>
</td>`;
}).join('')}
</tr>
`).join('');
@@ -565,6 +677,7 @@
truthBooths = s.truthBooths; ceremonies = s.ceremonies;
truthBooths.forEach(renderTruthBoothUI);
ceremonies.forEach((c, i) => renderCeremonyUI(c, i + 1));
handleCalculate();
};
reader.readAsText(file);
});