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

747 lines
24 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>Synchrotron Mini Crossword</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;
--cell: #f8f3ea;
--ink: #071014;
}
* { 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 14% 14%, 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(42px, 6vw, 60px);
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: 94px;
padding: 7px 9px;
}
.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: 1rem;
font-variant-numeric: tabular-nums;
}
main {
position: relative;
min-height: 0;
display: grid;
grid-template-columns: minmax(500px, 1.08fr) minmax(300px, 0.92fr);
gap: 14px;
}
main::before {
content: "";
position: absolute;
inset: 10% 16%;
background: url("assets/txproducts-logo-watermark.png") center / contain no-repeat;
opacity: 0.07;
pointer-events: none;
}
.panel {
position: relative;
z-index: 1;
padding: 16px;
overflow: hidden;
}
.board-wrap {
display: block;
margin: 0;
}
.board {
width: min(100%, 540px);
max-width: 100%;
display: grid;
grid-template-columns: repeat(17, 1fr);
gap: 2px;
padding: 6px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.08);
}
.cell {
position: relative;
aspect-ratio: 1;
min-width: 0;
min-height: 0;
padding: 0;
display: grid;
place-items: center;
border: 0;
border-radius: 4px;
color: var(--ink);
background: var(--cell);
font: inherit;
font-size: clamp(0.76rem, 1.45vw, 1.08rem);
font-weight: 950;
text-transform: uppercase;
cursor: pointer;
touch-action: manipulation;
box-shadow: none;
backdrop-filter: none;
}
.cell.block {
background: rgba(255, 255, 255, 0.06);
cursor: default;
}
.cell.active {
background: var(--gold);
box-shadow: 0 0 0 3px rgba(242, 201, 107, 0.2);
}
.cell.related {
background: #d8f6fb;
}
.cell.correct {
background: var(--green);
}
.num {
position: absolute;
top: 2px;
left: 3px;
color: rgba(7, 16, 20, 0.68);
font-size: 0.42rem;
font-weight: 950;
}
.side {
display: grid;
grid-template-rows: minmax(190px, 1fr) auto auto auto;
gap: 10px;
}
.message {
margin: 0 0 12px;
color: var(--gold);
font-size: clamp(1rem, 2vw, 1.25rem);
font-weight: 850;
line-height: 1.32;
}
.clues {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
max-height: none;
overflow: auto;
}
.clues h2 {
margin: 0 0 8px;
color: var(--cyan);
font-size: 0.82rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.clues ol {
margin: 0;
padding-left: 20px;
color: var(--muted);
font-size: clamp(0.76rem, 1.15vw, 0.9rem);
line-height: 1.25;
}
.clues li {
margin-bottom: 4px;
cursor: pointer;
}
.clues li.active {
color: var(--text);
font-weight: 850;
}
.keyboard {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 7px;
}
.controls {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 6px;
}
.difficulty {
display: grid;
grid-template-columns: repeat(3, minmax(72px, 100px));
gap: 5px;
justify-content: start;
}
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;
font-size: 0.76rem;
padding: 0 6px;
}
.ghost {
color: var(--muted);
background: rgba(255, 255, 255, 0.07);
}
@media (max-width: 900px) {
header,
footer {
align-items: flex-start;
flex-direction: column;
}
.stats {
justify-content: flex-start;
}
main {
grid-template-columns: 1fr;
}
.board {
width: min(100%, 540px);
}
.clues {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 680px) {
.board {
width: 100%;
}
.clues {
grid-template-columns: 1fr;
}
.keyboard {
grid-template-columns: repeat(9, minmax(0, 1fr));
}
.controls {
grid-template-columns: repeat(2, 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 technical crossword</small>
<h1>Synchrotron Mini Crossword</h1>
</div>
</div>
<div class="stats">
<div class="stat"><span>Score</span><strong id="score">0</strong></div>
<div class="stat"><span>Filled</span><strong id="filled">0%</strong></div>
<div class="stat"><span>Mode</span><strong id="mode">Across</strong></div>
</div>
</header>
<main>
<section class="panel board-wrap">
<p class="message" id="message">Tap a square or clue, then fill the answer. Short, technical, and booth-friendly.</p>
<div class="board" id="board"></div>
</section>
<aside class="side">
<section class="panel clues">
<div>
<h2>Across</h2>
<ol id="acrossClues"></ol>
</div>
<div>
<h2>Down</h2>
<ol id="downClues"></ol>
</div>
</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="keyboard" id="keyboard"></section>
<section class="controls">
<button class="primary" id="checkButton">Check</button>
<button id="revealButton">Reveal</button>
<button id="clearButton">Clear</button>
<button class="ghost" id="resetButton">Reset</button>
</section>
</aside>
</main>
<footer>
<button class="ghost" onclick="window.location.href='index.html'">Back to menu</button>
</footer>
</div>
<script>
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
const rows = 17;
const cols = 17;
const puzzleSets = {
Easy: [
{ num: 1, dir: "across", row: 2, col: 7, answer: "PICOSWITCH", clue: "Product that shortens x-ray pulses." },
{ num: 2, dir: "down", row: 2, col: 14, answer: "TXSERVICES", clue: "Support area for customers and experiments." },
{ num: 3, dir: "down", row: 4, col: 7, answer: "VALUE", clue: "What the right solution should create for a customer." },
{ num: 4, dir: "across", row: 5, col: 0, answer: "DATA", clue: "Measurement output before analysis." },
{ num: 5, dir: "across", row: 5, col: 10, answer: "PULSE", clue: "Short burst of x-rays or signal." },
{ num: 6, dir: "down", row: 5, col: 2, answer: "TXDATA", clue: "Turns measurements into insight." },
{ num: 7, dir: "down", row: 5, col: 10, answer: "PHOTON", clue: "Particle of light." },
{ num: 8, dir: "down", row: 6, col: 5, answer: "XRAY", clue: "Radiation used to probe structures." },
{ num: 9, dir: "across", row: 8, col: 4, answer: "WAVEGATE", clue: "Product for tailoring pulse timing." },
{ num: 10, dir: "across", row: 10, col: 0, answer: "BRAGG", clue: "Diffraction name linked to crystal planes." },
{ num: 11, dir: "across", row: 10, col: 7, answer: "SIGNAL", clue: "Useful information separated from noise." },
{ num: 12, dir: "down", row: 10, col: 0, answer: "BEAM", clue: "Focused stream used at a beamline." },
{ num: 13, dir: "down", row: 10, col: 4, answer: "GATE", clue: "Timing window that lets a pulse through." },
{ num: 14, dir: "down", row: 10, col: 8, answer: "ION", clue: "Charged particle." },
{ num: 15, dir: "across", row: 12, col: 4, answer: "TIMING", clue: "When pulses arrive." }
],
Medium: [
{ num: 1, dir: "across", row: 1, col: 1, answer: "WAVEGATE", clue: "TXProducts device for tailoring synchrotron pulse timing." },
{ num: 2, dir: "down", row: 3, col: 4, answer: "BEAMTIME", clue: "Scarce experiment time that teams want to use well." },
{ num: 3, dir: "across", row: 3, col: 6, answer: "TXSERVICES", clue: "Support area that helps teams get more from beamtime and experiments." },
{ num: 4, dir: "down", row: 3, col: 12, answer: "INSIGHT", clue: "What useful analysis gives a business after the measurement." },
{ num: 5, dir: "across", row: 5, col: 3, answer: "LATTICE", clue: "Ordered atomic structure studied in many materials experiments." },
{ num: 6, dir: "down", row: 6, col: 10, answer: "GATE", clue: "A timing window that lets the wanted pulse through." },
{ num: 7, dir: "across", row: 6, col: 12, answer: "ION", clue: "Charged particle found throughout accelerator physics vocabulary." },
{ num: 8, dir: "down", row: 8, col: 1, answer: "BRAGG", clue: "Diffraction condition central to selecting and switching x-rays." },
{ num: 9, dir: "across", row: 8, col: 3, answer: "PICOSWITCH", clue: "Photo-acoustic Bragg switch for shortening x-ray pulses." },
{ num: 10, dir: "down", row: 8, col: 7, answer: "SIGNAL", clue: "Useful measurement information that must be separated from noise." },
{ num: 11, dir: "across", row: 10, col: 0, answer: "VALUE", clue: "What the right product, data, and service should create for a customer." },
{ num: 12, dir: "down", row: 11, col: 11, answer: "DATA", clue: "Raw experiment output before analysis turns it into decisions." },
{ num: 13, dir: "across", row: 12, col: 6, answer: "WAVEGATE", clue: "Product that tailors synchrotron pulse structure from nanoseconds to seconds." },
{ num: 14, dir: "across", row: 14, col: 9, answer: "XRAY", clue: "Radiation used to probe structure and dynamics at beamlines." },
{ num: 15, dir: "down", row: 14, col: 10, answer: "RAY", clue: "A beam, line, or part of the x-ray term." }
],
Hard: [
{ num: 1, dir: "down", row: 0, col: 0, answer: "DETECTOR", clue: "Instrument that records the experimental signal." },
{ num: 2, dir: "down", row: 1, col: 6, answer: "SIGNAL", clue: "Measurement information separated from noise." },
{ num: 3, dir: "across", row: 2, col: 5, answer: "PICOSWITCH", clue: "Photo-acoustic Bragg switch for shortening x-ray pulses." },
{ num: 4, dir: "down", row: 2, col: 10, answer: "WAVEGATE", clue: "Product that tailors temporal pulse structure." },
{ num: 5, dir: "down", row: 3, col: 15, answer: "DYNAMICS", clue: "Time-dependent motion studied in materials." },
{ num: 6, dir: "across", row: 4, col: 0, answer: "COHERENCE", clue: "Wave property important in many advanced experiments." },
{ num: 7, dir: "across", row: 5, col: 9, answer: "BEAMLINE", clue: "Experimental path that delivers synchrotron radiation." },
{ num: 8, dir: "down", row: 7, col: 8, answer: "SAMPLE", clue: "Material placed in the beam for measurement." },
{ num: 9, dir: "down", row: 7, col: 13, answer: "ANALYSIS", clue: "Process that turns data into conclusions." },
{ num: 10, dir: "across", row: 8, col: 3, answer: "DIFFRACTION", clue: "Scattering pattern used to infer structure." },
{ num: 11, dir: "down", row: 10, col: 5, answer: "BRAGG", clue: "Diffraction condition named for a father-son team." },
{ num: 12, dir: "down", row: 10, col: 11, answer: "PHOTON", clue: "Quantum of light, including x-rays." },
{ num: 13, dir: "across", row: 12, col: 4, answer: "VALUE", clue: "Business result created by better data and support." },
{ num: 14, dir: "across", row: 13, col: 9, answer: "LATTICE", clue: "Ordered atomic arrangement in a crystal." },
{ num: 15, dir: "across", row: 15, col: 7, answer: "TIMING", clue: "Critical control variable for pulses." }
]
};
let entries = puzzleSets.Easy;
let grid = [];
let guesses = [];
let cellEntries = {};
let numbers = {};
const state = {
level: "Easy",
active: null,
row: 0,
col: 0,
score: 0
};
function buildGrid() {
entries = puzzleSets[state.level];
grid = Array.from({ length: rows }, () => Array.from({ length: cols }, () => null));
guesses = Array.from({ length: rows }, () => Array.from({ length: cols }, () => ""));
cellEntries = {};
numbers = {};
entries.forEach((entry) => {
numbers[`${entry.row},${entry.col}`] = entry.num;
entry.answer.split("").forEach((letter, index) => {
const row = entry.row + (entry.dir === "down" ? index : 0);
const col = entry.col + (entry.dir === "across" ? index : 0);
grid[row][col] = letter;
const entryKey = `${row},${col}`;
if (!cellEntries[entryKey]) cellEntries[entryKey] = [];
cellEntries[entryKey].push(entry);
});
});
state.active = entries[0];
state.row = entries[0].row;
state.col = entries[0].col;
}
const boardEl = document.getElementById("board");
const keyboardEl = document.getElementById("keyboard");
const acrossEl = document.getElementById("acrossClues");
const downEl = document.getElementById("downClues");
const messageEl = document.getElementById("message");
const scoreEl = document.getElementById("score");
const filledEl = document.getElementById("filled");
const modeEl = document.getElementById("mode");
const levelButtons = document.querySelectorAll("[data-level]");
function key(row, col) {
return `${row},${col}`;
}
function activeCells() {
const cells = [];
state.active.answer.split("").forEach((_, index) => {
cells.push({
row: state.active.row + (state.active.dir === "down" ? index : 0),
col: state.active.col + (state.active.dir === "across" ? index : 0)
});
});
return cells;
}
function chooseCell(row, col) {
if (!grid[row][col]) return;
const options = cellEntries[key(row, col)];
if (options.includes(state.active) && options.length > 1) {
state.active = options.find((entry) => entry !== state.active);
} else {
state.active = options[0];
}
state.row = row;
state.col = col;
render();
}
function chooseEntry(entry) {
state.active = entry;
state.row = entry.row;
state.col = entry.col;
render();
}
function renderBoard() {
const related = new Set(activeCells().map((cell) => key(cell.row, cell.col)));
boardEl.innerHTML = "";
for (let row = 0; row < rows; row += 1) {
for (let col = 0; col < cols; col += 1) {
const button = document.createElement("button");
button.type = "button";
button.className = "cell";
if (!grid[row][col]) {
button.classList.add("block");
button.disabled = true;
} else {
if (related.has(key(row, col))) button.classList.add("related");
if (row === state.row && col === state.col) button.classList.add("active");
if (guesses[row][col] === grid[row][col]) button.classList.add("correct");
button.textContent = guesses[row][col];
if (numbers[key(row, col)]) {
const num = document.createElement("span");
num.className = "num";
num.textContent = numbers[key(row, col)];
button.appendChild(num);
}
button.addEventListener("click", () => chooseCell(row, col));
}
boardEl.appendChild(button);
}
}
}
function renderClues() {
acrossEl.innerHTML = "";
downEl.innerHTML = "";
entries.forEach((entry) => {
const li = document.createElement("li");
li.value = entry.num;
li.textContent = entry.clue;
if (entry === state.active) li.className = "active";
li.addEventListener("click", () => chooseEntry(entry));
(entry.dir === "across" ? acrossEl : downEl).appendChild(li);
});
}
function renderKeyboard() {
keyboardEl.innerHTML = "";
alphabet.forEach((letter) => {
const button = document.createElement("button");
button.className = "letter";
button.textContent = letter;
button.addEventListener("click", () => typeLetter(letter));
keyboardEl.appendChild(button);
});
}
function renderStats() {
let total = 0;
let filled = 0;
for (let row = 0; row < rows; row += 1) {
for (let col = 0; col < cols; col += 1) {
if (grid[row][col]) {
total += 1;
if (guesses[row][col]) filled += 1;
}
}
}
scoreEl.textContent = state.score;
filledEl.textContent = `${Math.round((filled / total) * 100)}%`;
modeEl.textContent = `${state.level} ${state.active.dir === "across" ? "Across" : "Down"}`;
}
function render() {
renderBoard();
renderClues();
renderStats();
}
function nextCell() {
const cells = activeCells();
const current = cells.findIndex((cell) => cell.row === state.row && cell.col === state.col);
const next = cells[Math.min(current + 1, cells.length - 1)] || cells[0];
state.row = next.row;
state.col = next.col;
}
function previousCell() {
const cells = activeCells();
const current = cells.findIndex((cell) => cell.row === state.row && cell.col === state.col);
const previous = cells[Math.max(current - 1, 0)] || cells[0];
state.row = previous.row;
state.col = previous.col;
}
function typeLetter(letter) {
guesses[state.row][state.col] = letter;
nextCell();
render();
}
function backspace() {
if (guesses[state.row][state.col]) {
guesses[state.row][state.col] = "";
} else {
previousCell();
guesses[state.row][state.col] = "";
}
render();
}
function checkPuzzle() {
let wrong = 0;
let correct = 0;
for (let row = 0; row < rows; row += 1) {
for (let col = 0; col < cols; col += 1) {
if (grid[row][col] && guesses[row][col]) {
if (guesses[row][col] === grid[row][col]) correct += 1;
else wrong += 1;
}
}
}
state.score = correct * 20;
messageEl.textContent = wrong ? `${wrong} squares need another look.` : "Everything filled so far is correct.";
if (isComplete() && !wrong) {
state.score += 400;
messageEl.textContent = "Crossword complete: product, physics, data, and service all connected.";
}
render();
}
function isComplete() {
for (let row = 0; row < rows; row += 1) {
for (let col = 0; col < cols; col += 1) {
if (grid[row][col] && guesses[row][col] !== grid[row][col]) return false;
}
}
return true;
}
function revealActive() {
activeCells().forEach((cell, index) => {
guesses[cell.row][cell.col] = state.active.answer[index];
});
messageEl.textContent = `${state.active.num} ${state.active.dir} revealed.`;
render();
}
function clearActive() {
activeCells().forEach((cell) => {
guesses[cell.row][cell.col] = "";
});
messageEl.textContent = "Current answer cleared.";
render();
}
function resetPuzzle() {
buildGrid();
state.score = 0;
messageEl.textContent = `${state.level} crossword ready. Tap a square or clue, then fill the answer.`;
render();
}
function setLevel(level) {
state.level = level;
levelButtons.forEach((button) => button.classList.toggle("active-level", button.dataset.level === level));
resetPuzzle();
}
levelButtons.forEach((button) => button.addEventListener("click", () => setLevel(button.dataset.level)));
document.getElementById("checkButton").addEventListener("click", checkPuzzle);
document.getElementById("revealButton").addEventListener("click", revealActive);
document.getElementById("clearButton").addEventListener("click", clearActive);
document.getElementById("resetButton").addEventListener("click", resetPuzzle);
window.addEventListener("keydown", (event) => {
const letter = event.key.toUpperCase();
if (alphabet.includes(letter)) typeLetter(letter);
if (event.key === "Backspace") backspace();
if (event.key === "Enter") checkPuzzle();
if (event.key === "Tab") {
event.preventDefault();
const index = entries.indexOf(state.active);
chooseEntry(entries[(index + 1) % entries.length]);
}
if (event.key.toLowerCase() === "r") resetPuzzle();
});
buildGrid();
renderKeyboard();
render();
</script>
</body>
</html>