Files
games/all-games/cryptogram.html
T
2026-06-17 09:13:06 +02:00

607 lines
18 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Decode the Synchrotron Signal</title>
<style>
:root {
color-scheme: dark;
--panel: rgba(10, 16, 25, 0.92);
--line: rgba(255, 255, 255, 0.16);
--text: #f8f3ea;
--muted: #aeb8c8;
--cyan: #65d8e8;
--gold: #f2c96b;
--green: #88d76f;
--red: #ef735f;
}
* { box-sizing: border-box; }
html,
body {
min-height: 100%;
margin: 0;
}
body {
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
background:
radial-gradient(circle at 16% 12%, rgba(101, 216, 232, 0.22), transparent 28%),
radial-gradient(circle at 86% 78%, rgba(242, 201, 107, 0.13), transparent 32%),
linear-gradient(140deg, #101926 0%, #1b242d 48%, #080b10 100%);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.shell {
min-height: 100vh;
display: grid;
grid-template-rows: auto 1fr auto;
gap: 14px;
padding: 16px;
}
header,
footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.brand {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 12px;
}
.brand img {
width: clamp(48px, 8vw, 72px);
opacity: 0.58;
}
.brand small {
color: var(--cyan);
font-size: 0.78rem;
font-weight: 900;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: clamp(1.45rem, 3.5vw, 2.55rem);
line-height: 1;
}
.stats {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
}
.stat,
.panel,
button {
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel);
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.28);
backdrop-filter: blur(12px);
}
.stat {
min-width: 104px;
padding: 8px 10px;
}
.stat span {
display: block;
color: var(--muted);
font-size: 0.7rem;
font-weight: 900;
text-transform: uppercase;
}
.stat strong {
display: block;
margin-top: 2px;
font-size: 1.12rem;
font-variant-numeric: tabular-nums;
}
main {
position: relative;
min-height: 0;
display: grid;
grid-template-columns: minmax(320px, 1.15fr) minmax(280px, 0.85fr);
gap: 14px;
}
main::before {
content: "";
position: absolute;
inset: 10% 14%;
background: url("assets/txproducts-logo-watermark.png") center / contain no-repeat;
opacity: 0.08;
pointer-events: none;
}
.panel {
position: relative;
z-index: 1;
padding: 16px;
overflow: hidden;
}
.category {
display: inline-flex;
width: fit-content;
margin-bottom: 14px;
padding: 7px 10px;
border: 1px solid rgba(101, 216, 232, 0.32);
border-radius: 8px;
color: var(--cyan);
font-size: 0.8rem;
font-weight: 900;
letter-spacing: 0.08em;
text-transform: uppercase;
background: rgba(101, 216, 232, 0.09);
}
.message {
min-height: 42px;
margin: 0 0 12px;
color: var(--gold);
font-size: clamp(1rem, 2vw, 1.25rem);
font-weight: 850;
line-height: 1.32;
}
.phrase {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
gap: 12px 18px;
min-height: 330px;
padding: 10px 0;
}
.word {
display: flex;
gap: 5px;
}
.tile {
width: clamp(32px, 4.7vw, 52px);
display: grid;
gap: 4px;
place-items: center;
cursor: pointer;
}
.cipher {
width: 100%;
color: var(--muted);
font-size: 0.72rem;
font-weight: 900;
text-align: center;
}
.plain {
width: 100%;
aspect-ratio: 1;
display: grid;
place-items: center;
border: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 3px solid rgba(101, 216, 232, 0.65);
border-radius: 7px;
background: rgba(255, 255, 255, 0.07);
font-size: clamp(1.05rem, 2.8vw, 1.65rem);
font-weight: 950;
text-transform: uppercase;
}
.tile.active .plain {
border-color: rgba(242, 201, 107, 0.95);
box-shadow: 0 0 0 4px rgba(242, 201, 107, 0.16);
}
.tile.correct .plain {
color: #061106;
background: var(--green);
border-color: var(--green);
}
.side {
display: grid;
grid-template-rows: auto auto auto 1fr;
gap: 10px;
}
.hint h2 {
margin: 0 0 10px;
font-size: clamp(1.4rem, 3vw, 2.1rem);
}
.hint p {
margin: 0;
color: var(--muted);
font-size: clamp(1rem, 2vw, 1.15rem);
line-height: 1.45;
}
.controls {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 6px;
}
.difficulty {
display: grid;
grid-template-columns: repeat(3, minmax(72px, 100px));
gap: 5px;
justify-content: start;
}
.keyboard {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 7px;
align-content: end;
}
button {
min-height: 40px;
padding: 0 8px;
border-color: rgba(255, 255, 255, 0.18);
color: var(--text);
font: inherit;
font-weight: 900;
cursor: pointer;
touch-action: manipulation;
}
button.primary {
color: #071014;
background: linear-gradient(135deg, var(--gold), #ffdc83);
border-color: rgba(255, 220, 131, 0.9);
}
button.active-level {
color: #071014;
background: var(--green);
border-color: rgba(136, 215, 111, 0.85);
}
button.letter {
min-height: 48px;
color: #071014;
background: var(--cyan);
border-color: rgba(101, 216, 232, 0.8);
font-size: clamp(1rem, 1.8vw, 1.24rem);
}
.controls button {
min-height: 38px;
font-size: 0.88rem;
}
.difficulty button {
min-height: 28px;
padding: 0 6px;
font-size: 0.76rem;
}
button:disabled {
opacity: 0.38;
cursor: default;
}
.ghost {
color: var(--muted);
background: rgba(255, 255, 255, 0.07);
}
@media (max-width: 840px) {
header,
footer {
align-items: flex-start;
flex-direction: column;
}
.stats {
justify-content: flex-start;
}
main {
grid-template-columns: 1fr;
}
.phrase {
min-height: 250px;
}
.keyboard {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
}
</style>
</head>
<body>
<div class="shell">
<header>
<div class="brand">
<img src="assets/txproducts-logo-watermark.png" alt="">
<div>
<small>txproducts.de signal puzzle</small>
<h1>Decode the Synchrotron Signal</h1>
</div>
</div>
<div class="stats">
<div class="stat"><span>Score</span><strong id="score">0</strong></div>
<div class="stat"><span>Signal</span><strong id="signal">1 / 8</strong></div>
<div class="stat"><span>Errors</span><strong id="errors">0</strong></div>
</div>
</header>
<main>
<section class="panel">
<div class="category" id="category">Product</div>
<p class="message" id="message">Tap an empty tile, then choose the decoded letter.</p>
<div class="phrase" id="phrase"></div>
</section>
<aside class="side">
<section class="panel hint">
<h2>Are you ready?</h2>
<p>Decode the hidden phrase. The small top letters are the encrypted signal; your job is to recover the real synchrotron message below.</p>
</section>
<section class="difficulty" aria-label="Difficulty">
<button class="active-level" data-level="Easy" type="button">Easy</button>
<button data-level="Medium" type="button">Medium</button>
<button data-level="Hard" type="button">Hard</button>
</section>
<section class="controls">
<button class="primary" id="hintButton">Hint</button>
<button id="checkButton">Check</button>
<button id="nextButton">Next</button>
</section>
<section class="keyboard" id="keyboard"></section>
</aside>
</main>
<footer>
<button class="ghost" id="resetButton">Reset</button>
<button class="ghost" onclick="window.location.href='index.html'">Back to menu</button>
</footer>
</div>
<script>
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
const puzzleSets = {
Easy: [
{ category: "Easy Product", phrase: "WAVEGATE", note: "WaveGate is the pulse timing product visitors should remember." },
{ category: "Easy Product", phrase: "PICOSWITCH", note: "PicoSwitch is the fast x-ray pulse shortening product." },
{ category: "Easy Data", phrase: "TXDATA", note: "TXData connects measurements with usable insight." },
{ category: "Easy Service", phrase: "TXSERVICES", note: "TXServices supports teams around experiments and beamtime." },
{ category: "Easy Physics", phrase: "XRAY PULSE", note: "X-ray pulses are central to these timing games." },
{ category: "Easy Business", phrase: "BETTER DATA", note: "Better data helps create better decisions." }
],
Medium: [
{ category: "Product", phrase: "WAVEGATE TAILORS PULSE STRUCTURE", note: "WaveGate shapes synchrotron pulse timing across nanoseconds to seconds." },
{ category: "Product", phrase: "PICOSWITCH CUTS X RAY PULSES", note: "PicoSwitch uses photo-acoustic Bragg switching for short x-ray pulses." },
{ category: "Physics", phrase: "BRAGG DIFFRACTION SELECTS THE BEAM", note: "Bragg diffraction is the physics clue behind precise switching." },
{ category: "Beamline", phrase: "SYNCHROTRON TIMING MATTERS", note: "Timing can decide whether a measurement is clean or noisy." },
{ category: "Data", phrase: "TXDATA TURNS MEASUREMENTS INTO INSIGHT", note: "TXData connects experimental output with decisions people can use." },
{ category: "Service", phrase: "TXSERVICES SUPPORTS BETTER BEAMTIME", note: "Good support helps teams get more value from scarce experiment time." },
{ category: "Business", phrase: "BETTER DATA BETTER DECISIONS", note: "The booth story links products, data, and service to business value." },
{ category: "Experiment", phrase: "COHERENT LATTICE DYNAMICS", note: "Short pulses can reveal ultrafast motion inside materials." }
],
Hard: [
{ category: "Hard Physics", phrase: "PHOTO ACOUSTIC BRAGG SWITCHING", note: "PicoSwitch uses photo-acoustic Bragg switching to shorten x-ray pulses." },
{ category: "Hard Timing", phrase: "TEMPORAL SYNCHROTRON PULSE STRUCTURE", note: "WaveGate tailors synchrotron pulse structure across broad timescales." },
{ category: "Hard Experiment", phrase: "COHERENT LATTICE DYNAMICS IN THIN FILMS", note: "Short x-ray pulses can reveal coherent lattice motion in materials." },
{ category: "Hard Business", phrase: "MEASUREMENTS BECOME ACTIONABLE INSIGHT", note: "TXData and TXServices help turn complex experimental output into decisions." },
{ category: "Hard Beamline", phrase: "DIFFRACTION EFFICIENCY LIMITS REPETITION RATE", note: "Technical constraints shape how a device performs in real experiments." },
{ category: "Hard Product", phrase: "NANOSECONDS TO SECONDS PULSE TAILORING", note: "That broad timing span is the memorable WaveGate story." }
]
};
let puzzles = puzzleSets.Easy;
const shifts = [7, 11, 5, 19, 9, 15, 21, 3];
const state = {
level: "Easy",
index: 0,
score: 0,
errors: 0,
selected: 0,
guesses: []
};
const phraseEl = document.getElementById("phrase");
const keyboardEl = document.getElementById("keyboard");
const messageEl = document.getElementById("message");
const scoreEl = document.getElementById("score");
const signalEl = document.getElementById("signal");
const errorsEl = document.getElementById("errors");
const categoryEl = document.getElementById("category");
const levelButtons = document.querySelectorAll("[data-level]");
function encodeLetter(letter) {
if (!/[A-Z]/.test(letter)) return letter;
const shift = shifts[state.index % shifts.length];
const next = (alphabet.indexOf(letter) + shift) % 26;
return alphabet[next];
}
function getLetters() {
return puzzles[state.index].phrase.split("").map((char, index) => ({
char,
cipher: encodeLetter(char),
index,
isLetter: /[A-Z]/.test(char)
}));
}
function startPuzzle() {
const puzzle = puzzles[state.index];
state.guesses = Array(puzzle.phrase.length).fill("");
state.selected = getLetters().find((item) => item.isLetter)?.index || 0;
categoryEl.textContent = `${state.level} / ${puzzle.category}`;
messageEl.textContent = "Tap an empty tile, then choose the decoded letter.";
render();
}
function render() {
const puzzle = puzzles[state.index];
scoreEl.textContent = state.score;
signalEl.textContent = `${state.index + 1} / ${puzzles.length}`;
errorsEl.textContent = state.errors;
phraseEl.innerHTML = "";
const words = puzzle.phrase.split(" ");
let offset = 0;
words.forEach((word) => {
const wordEl = document.createElement("div");
wordEl.className = "word";
for (const char of word) {
const index = offset;
const tile = document.createElement("button");
tile.className = "tile";
tile.type = "button";
if (index === state.selected) tile.classList.add("active");
if (state.guesses[index] === puzzle.phrase[index]) tile.classList.add("correct");
tile.innerHTML = `<span class="cipher">${encodeLetter(char)}</span><span class="plain">${state.guesses[index] || ""}</span>`;
tile.addEventListener("click", () => {
state.selected = index;
render();
});
wordEl.appendChild(tile);
offset += 1;
}
phraseEl.appendChild(wordEl);
offset += 1;
});
}
function renderKeyboard() {
keyboardEl.innerHTML = "";
alphabet.forEach((letter) => {
const button = document.createElement("button");
button.className = "letter";
button.textContent = letter;
button.addEventListener("click", () => guess(letter));
keyboardEl.appendChild(button);
});
}
function nextEmpty(fromIndex = 0) {
const puzzle = puzzles[state.index];
for (let i = fromIndex; i < puzzle.phrase.length; i += 1) {
if (/[A-Z]/.test(puzzle.phrase[i]) && !state.guesses[i]) return i;
}
for (let i = 0; i < puzzle.phrase.length; i += 1) {
if (/[A-Z]/.test(puzzle.phrase[i]) && !state.guesses[i]) return i;
}
return state.selected;
}
function guess(letter) {
const puzzle = puzzles[state.index];
if (!/[A-Z]/.test(puzzle.phrase[state.selected])) {
state.selected = nextEmpty();
}
state.guesses[state.selected] = letter;
if (letter === puzzle.phrase[state.selected]) {
state.score += 25;
messageEl.textContent = "Good decode. Keep going.";
} else {
state.errors += 1;
messageEl.textContent = "That signal is off. Try another tile.";
}
state.selected = nextEmpty(state.selected + 1);
render();
if (isSolved()) completePuzzle();
}
function isSolved() {
const puzzle = puzzles[state.index];
return puzzle.phrase.split("").every((char, index) => !/[A-Z]/.test(char) || state.guesses[index] === char);
}
function completePuzzle() {
const puzzle = puzzles[state.index];
state.score += 150;
messageEl.textContent = `Signal decoded: ${puzzle.note}`;
render();
}
function revealHint() {
const puzzle = puzzles[state.index];
const missing = puzzle.phrase.split("").findIndex((char, index) => /[A-Z]/.test(char) && state.guesses[index] !== char);
if (missing >= 0) {
state.guesses[missing] = puzzle.phrase[missing];
state.selected = nextEmpty(missing + 1);
messageEl.textContent = "Hint revealed one correct signal letter.";
render();
if (isSolved()) completePuzzle();
}
}
function checkPuzzle() {
if (isSolved()) {
completePuzzle();
return;
}
const puzzle = puzzles[state.index];
const wrong = puzzle.phrase.split("").filter((char, index) => /[A-Z]/.test(char) && state.guesses[index] && state.guesses[index] !== char).length;
messageEl.textContent = wrong ? `${wrong} decoded letters need another look.` : "No wrong letters yet. Keep decoding.";
}
function nextPuzzle() {
state.index = (state.index + 1) % puzzles.length;
startPuzzle();
}
function setLevel(level) {
state.level = level;
puzzles = puzzleSets[level];
state.index = 0;
state.score = 0;
state.errors = 0;
levelButtons.forEach((button) => button.classList.toggle("active-level", button.dataset.level === level));
startPuzzle();
}
levelButtons.forEach((button) => button.addEventListener("click", () => setLevel(button.dataset.level)));
document.getElementById("hintButton").addEventListener("click", revealHint);
document.getElementById("checkButton").addEventListener("click", checkPuzzle);
document.getElementById("nextButton").addEventListener("click", nextPuzzle);
document.getElementById("resetButton").addEventListener("click", () => {
state.index = 0;
state.score = 0;
state.errors = 0;
startPuzzle();
});
window.addEventListener("keydown", (event) => {
const letter = event.key.toUpperCase();
if (alphabet.includes(letter)) guess(letter);
if (event.key === "Enter") checkPuzzle();
if (event.key.toLowerCase() === "r") {
state.score = 0;
state.errors = 0;
startPuzzle();
}
});
renderKeyboard();
startPuzzle();
</script>
</body>
</html>