573 lines
17 KiB
HTML
573 lines
17 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 Challenge</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 {
|
|
height: 100%;
|
|
margin: 0;
|
|
}
|
|
|
|
body {
|
|
overflow: hidden;
|
|
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% 80%, rgba(242, 201, 107, 0.12), 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 {
|
|
display: grid;
|
|
grid-template-rows: auto 1fr auto;
|
|
height: 100%;
|
|
gap: 12px;
|
|
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-logo {
|
|
width: clamp(48px, 8vw, 76px);
|
|
height: auto;
|
|
opacity: 0.58;
|
|
}
|
|
|
|
.brand-text {
|
|
display: grid;
|
|
gap: 2px;
|
|
}
|
|
|
|
.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.5rem);
|
|
line-height: 1;
|
|
}
|
|
|
|
.scorebar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
}
|
|
|
|
.score,
|
|
.tip,
|
|
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);
|
|
}
|
|
|
|
.score {
|
|
min-width: 112px;
|
|
padding: 8px 10px;
|
|
}
|
|
|
|
.score span {
|
|
display: block;
|
|
color: var(--muted);
|
|
font-size: 0.7rem;
|
|
font-weight: 900;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.score strong {
|
|
display: block;
|
|
margin-top: 2px;
|
|
font-size: 1.1rem;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
main {
|
|
min-height: 0;
|
|
position: relative;
|
|
display: grid;
|
|
}
|
|
|
|
.watermark {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 0;
|
|
display: grid;
|
|
place-items: center;
|
|
pointer-events: none;
|
|
opacity: 0.05;
|
|
transform: rotate(-18deg);
|
|
}
|
|
|
|
.watermark img {
|
|
width: min(62vmin, 520px);
|
|
height: auto;
|
|
}
|
|
|
|
.board {
|
|
min-height: 0;
|
|
position: relative;
|
|
z-index: 1;
|
|
display: grid;
|
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
grid-template-rows: auto repeat(5, minmax(0, 1fr));
|
|
gap: 8px;
|
|
}
|
|
|
|
.category,
|
|
.tile {
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
background: rgba(9, 14, 22, 0.88);
|
|
box-shadow: 0 18px 42px rgba(0, 0, 0, 0.28);
|
|
}
|
|
|
|
.category {
|
|
min-height: 74px;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 8px;
|
|
color: var(--cyan);
|
|
font-size: clamp(0.72rem, 1.45vw, 1rem);
|
|
font-weight: 900;
|
|
text-align: center;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.tile {
|
|
min-height: 72px;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 8px;
|
|
color: var(--gold);
|
|
font-size: clamp(1.3rem, 3.4vw, 2.6rem);
|
|
font-weight: 1000;
|
|
cursor: pointer;
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
.tile.used {
|
|
color: rgba(255, 255, 255, 0.2);
|
|
background: rgba(255, 255, 255, 0.04);
|
|
cursor: default;
|
|
}
|
|
|
|
.overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 10;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 16px;
|
|
background: rgba(2, 5, 10, 0.62);
|
|
backdrop-filter: blur(8px);
|
|
}
|
|
|
|
.overlay[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.modal {
|
|
width: min(900px, 100%);
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
padding: 24px;
|
|
background: rgba(7, 11, 18, 0.95);
|
|
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.55);
|
|
}
|
|
|
|
.modal-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: start;
|
|
gap: 12px;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.modal h2 {
|
|
margin: 0;
|
|
color: var(--cyan);
|
|
font-size: clamp(1rem, 2.2vw, 1.25rem);
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.value {
|
|
color: var(--gold);
|
|
font-size: clamp(1.4rem, 4vw, 2.4rem);
|
|
font-weight: 1000;
|
|
line-height: 1;
|
|
}
|
|
|
|
.clue {
|
|
min-height: 145px;
|
|
display: grid;
|
|
align-items: center;
|
|
margin: 0;
|
|
font-size: clamp(1.55rem, 4vw, 3.2rem);
|
|
font-weight: 900;
|
|
line-height: 1.12;
|
|
}
|
|
|
|
.answer {
|
|
margin: 16px 0 0;
|
|
border-left: 4px solid var(--gold);
|
|
padding: 12px 0 12px 14px;
|
|
color: var(--text);
|
|
font-size: clamp(1.2rem, 2.6vw, 1.7rem);
|
|
font-weight: 800;
|
|
}
|
|
|
|
.answer[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.modal-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-top: 18px;
|
|
}
|
|
|
|
button {
|
|
min-height: 40px;
|
|
padding: 0 8px;
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-weight: 900;
|
|
cursor: pointer;
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
.modal-actions button,
|
|
footer button {
|
|
min-height: 38px;
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
button.primary {
|
|
border-color: rgba(242, 201, 107, 0.62);
|
|
background: linear-gradient(180deg, #f3d16e, #d89a37);
|
|
color: #211609;
|
|
}
|
|
|
|
button.good {
|
|
border-color: rgba(136, 215, 111, 0.58);
|
|
background: rgba(136, 215, 111, 0.14);
|
|
}
|
|
|
|
button.bad {
|
|
border-color: rgba(239, 115, 95, 0.58);
|
|
background: rgba(239, 115, 95, 0.14);
|
|
}
|
|
|
|
.tip {
|
|
padding: 8px 10px;
|
|
color: var(--muted);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.footer-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
}
|
|
|
|
@media (max-width: 820px) {
|
|
body {
|
|
overflow: auto;
|
|
}
|
|
|
|
.shell {
|
|
min-height: 100%;
|
|
height: auto;
|
|
padding: 10px;
|
|
}
|
|
|
|
header,
|
|
footer {
|
|
align-items: stretch;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.scorebar,
|
|
.footer-actions {
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.board {
|
|
grid-template-columns: repeat(5, minmax(92px, 1fr));
|
|
overflow-x: auto;
|
|
padding-bottom: 6px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="shell">
|
|
<header>
|
|
<div class="brand">
|
|
<img class="brand-logo" src="assets/txproducts-logo-watermark.png" alt="">
|
|
<div class="brand-text">
|
|
<small>txproducts.de exhibition quiz</small>
|
|
<h1>Synchrotron Challenge</h1>
|
|
</div>
|
|
</div>
|
|
<div class="scorebar">
|
|
<div class="score"><span>Score</span><strong id="score">0</strong></div>
|
|
<div class="score"><span>Answered</span><strong id="answered">0 / 25</strong></div>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="watermark" aria-hidden="true"><img src="assets/txproducts-logo-watermark.png" alt=""></div>
|
|
<section class="board" id="board" aria-label="Synchrotron quiz board"></section>
|
|
</main>
|
|
|
|
<footer>
|
|
<div class="tip">Tap a value, discuss the clue, reveal the answer, then mark correct or missed.</div>
|
|
<div class="footer-actions">
|
|
<button id="resetButton" type="button">Reset board</button>
|
|
<button type="button" onclick="window.location.href='index.html'">Back to menu</button>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
|
|
<div class="overlay" id="overlay" hidden>
|
|
<article class="modal">
|
|
<div class="modal-top">
|
|
<div>
|
|
<h2 id="modalCategory">Category</h2>
|
|
<div id="modalDifficulty">Difficulty</div>
|
|
</div>
|
|
<div class="value" id="modalValue">100</div>
|
|
</div>
|
|
<p class="clue" id="modalClue"></p>
|
|
<div class="answer" id="modalAnswer" hidden></div>
|
|
<div class="modal-actions">
|
|
<button class="primary" id="showAnswerButton" type="button">Show answer</button>
|
|
<button class="good" id="correctButton" type="button" hidden>Correct</button>
|
|
<button class="bad" id="missedButton" type="button" hidden>Missed</button>
|
|
<button id="closeButton" type="button">Close</button>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
|
|
<script>
|
|
const categories = [
|
|
{
|
|
title: "Synchrotron Basics",
|
|
clues: [
|
|
["A large accelerator facility that produces extremely bright x-rays for experiments.", "What is a synchrotron?"],
|
|
["These charged particles are accelerated around the storage ring to generate synchrotron radiation.", "What are electrons?"],
|
|
["Scientists use synchrotron x-rays because they are bright, tunable, and can probe this tiny scale.", "What is the atomic or nanoscale?"],
|
|
["This property of synchrotron light lets researchers choose x-ray energies for specific materials or edges.", "What is tunability?"],
|
|
["This repeating time pattern of x-ray bunches is what timing devices can reshape for experiments.", "What is pulse structure?"]
|
|
]
|
|
},
|
|
{
|
|
title: "TXProducts",
|
|
clues: [
|
|
["This product tailors synchrotron pulse structure from nanoseconds to seconds.", "What is WaveGate?"],
|
|
["This product cuts x-ray pulses to picosecond duration using photo-acoustic Bragg switching.", "What is PicoSwitch?"],
|
|
["WaveGate controls when pulses are allowed through, shaping this aspect of the beam.", "What is temporal structure?"],
|
|
["PicoSwitch uses a photo-acoustic effect together with this diffraction condition.", "What is Bragg diffraction?"],
|
|
["Together, WaveGate and PicoSwitch help researchers control this dimension of x-ray experiments.", "What is time?"]
|
|
]
|
|
},
|
|
{
|
|
title: "TXData",
|
|
clues: [
|
|
["A data service helps turn raw detector output into this useful form.", "What is actionable insight?"],
|
|
["Good experimental data needs metadata, calibration, and this quality check.", "What is validation?"],
|
|
["TXData can help researchers compare runs, spot trends, and reduce this costly problem.", "What is wasted beamtime or failed experiments?"],
|
|
["When pulse timing, detector output, and sample conditions are linked, the result is better this.", "What is data context or traceability?"],
|
|
["A business benefits when scientific data becomes searchable, reusable, and easier to share across this group.", "What is a team or organization?"]
|
|
]
|
|
},
|
|
{
|
|
title: "TXServices",
|
|
clues: [
|
|
["TXServices can support planning, setup, and optimization so teams make better use of this scarce resource.", "What is beamtime?"],
|
|
["A service partner can help translate an experimental question into this practical beamline plan.", "What is a measurement strategy?"],
|
|
["Training and support reduce the gap between advanced instruments and this day-to-day user need.", "What is usability or confidence?"],
|
|
["When equipment, data, and workflow are aligned, customers can move faster from experiment to this.", "What is decision or result?"],
|
|
["For a customer, expert service lowers technical risk and protects this project investment.", "What is time, budget, or ROI?"]
|
|
]
|
|
},
|
|
{
|
|
title: "Business Value",
|
|
clues: [
|
|
["Better pulse control can improve experiment quality by reducing unwanted signal known as this.", "What is background or leakage?"],
|
|
["Reliable timing tools help customers spend less time troubleshooting and more time getting these.", "What are results?"],
|
|
["TXProducts, TXData, and TXServices together can connect hardware, analysis, and this human support layer.", "What is expertise or service?"],
|
|
["A business improves when experiments become more repeatable, data becomes clearer, and decisions become this.", "What is faster or more confident?"],
|
|
["For industrial or research customers, the combined offering can turn advanced synchrotron access into this advantage.", "What is competitive advantage?"]
|
|
]
|
|
}
|
|
];
|
|
|
|
const values = [100, 200, 300, 400, 500];
|
|
const difficulties = ["Warm-up", "Basic", "Applied", "Technical", "Expert"];
|
|
const board = document.querySelector("#board");
|
|
const scoreEl = document.querySelector("#score");
|
|
const answeredEl = document.querySelector("#answered");
|
|
const overlay = document.querySelector("#overlay");
|
|
const modalCategory = document.querySelector("#modalCategory");
|
|
const modalDifficulty = document.querySelector("#modalDifficulty");
|
|
const modalValue = document.querySelector("#modalValue");
|
|
const modalClue = document.querySelector("#modalClue");
|
|
const modalAnswer = document.querySelector("#modalAnswer");
|
|
const showAnswerButton = document.querySelector("#showAnswerButton");
|
|
const correctButton = document.querySelector("#correctButton");
|
|
const missedButton = document.querySelector("#missedButton");
|
|
const closeButton = document.querySelector("#closeButton");
|
|
const resetButton = document.querySelector("#resetButton");
|
|
|
|
const state = {
|
|
score: 0,
|
|
answered: 0,
|
|
current: null,
|
|
used: new Set()
|
|
};
|
|
|
|
function renderBoard() {
|
|
board.innerHTML = "";
|
|
for (const category of categories) {
|
|
const cell = document.createElement("div");
|
|
cell.className = "category";
|
|
cell.textContent = category.title;
|
|
board.appendChild(cell);
|
|
}
|
|
|
|
for (let row = 0; row < values.length; row += 1) {
|
|
for (let col = 0; col < categories.length; col += 1) {
|
|
const key = `${col}-${row}`;
|
|
const tile = document.createElement("button");
|
|
tile.className = `tile${state.used.has(key) ? " used" : ""}`;
|
|
tile.type = "button";
|
|
tile.textContent = state.used.has(key) ? "..." : values[row];
|
|
tile.disabled = state.used.has(key);
|
|
tile.addEventListener("click", () => openClue(col, row));
|
|
tile.addEventListener("touchend", (event) => {
|
|
event.preventDefault();
|
|
openClue(col, row);
|
|
}, { passive: false });
|
|
board.appendChild(tile);
|
|
}
|
|
}
|
|
}
|
|
|
|
function openClue(col, row) {
|
|
const key = `${col}-${row}`;
|
|
if (state.used.has(key)) return;
|
|
const category = categories[col];
|
|
const [clue, answer] = category.clues[row];
|
|
state.current = { key, value: values[row], clue, answer, category: category.title, difficulty: difficulties[row] };
|
|
modalCategory.textContent = category.title;
|
|
modalDifficulty.textContent = difficulties[row];
|
|
modalValue.textContent = values[row];
|
|
modalClue.textContent = clue;
|
|
modalAnswer.hidden = true;
|
|
modalAnswer.textContent = answer;
|
|
showAnswerButton.hidden = false;
|
|
correctButton.hidden = true;
|
|
missedButton.hidden = true;
|
|
overlay.hidden = false;
|
|
}
|
|
|
|
function showAnswer() {
|
|
modalAnswer.hidden = false;
|
|
showAnswerButton.hidden = true;
|
|
correctButton.hidden = false;
|
|
missedButton.hidden = false;
|
|
}
|
|
|
|
function scoreCurrent(correct) {
|
|
if (!state.current) return;
|
|
state.used.add(state.current.key);
|
|
state.answered += 1;
|
|
if (correct) state.score += state.current.value;
|
|
else state.score = Math.max(0, state.score - Math.floor(state.current.value / 2));
|
|
closeModal();
|
|
updateHud();
|
|
renderBoard();
|
|
}
|
|
|
|
function closeModal() {
|
|
overlay.hidden = true;
|
|
state.current = null;
|
|
}
|
|
|
|
function updateHud() {
|
|
scoreEl.textContent = state.score;
|
|
answeredEl.textContent = `${state.answered} / 25`;
|
|
}
|
|
|
|
function resetBoard() {
|
|
state.score = 0;
|
|
state.answered = 0;
|
|
state.current = null;
|
|
state.used.clear();
|
|
updateHud();
|
|
renderBoard();
|
|
overlay.hidden = true;
|
|
}
|
|
|
|
showAnswerButton.addEventListener("click", showAnswer);
|
|
correctButton.addEventListener("click", () => scoreCurrent(true));
|
|
missedButton.addEventListener("click", () => scoreCurrent(false));
|
|
closeButton.addEventListener("click", closeModal);
|
|
resetButton.addEventListener("click", resetBoard);
|
|
window.addEventListener("keydown", (event) => {
|
|
if (event.key === "Escape") closeModal();
|
|
if (event.key.toLowerCase() === "r") resetBoard();
|
|
});
|
|
|
|
renderBoard();
|
|
updateHud();
|
|
</script>
|
|
</body>
|
|
</html>
|