:root {
  --bg: #0b0c0e;
  --bg-elevated: #141518;
  --bg-card: #1a1b1f;
  --border: #2a2b30;
  --text: #e8e8ea;
  --text-dim: #8a8b92;
  --accent: #FA89A0;
  --accent-dim: #141518;
  --danger: #D73833;
  --radius: 10px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  text-transform: lowercase;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
  font-size: 16px;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap {
  max-width: 640px;
  margin: 0 auto;
  padding: 24px 20px 80px;
}

nav.topnav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--border);
  padding: 16px 20px;
}

nav.topnav .brand {
  font-weight: 700;
  letter-spacing: 0.5px;
  color: var(--text);
  font-size: 1.1rem;
}
nav.topnav .brand span { color: var(--accent); }

nav.topnav .links {
  display: flex;
  gap: 18px;
  align-items: center;
  font-size: 0.9rem;
}
nav.topnav .links a { color: var(--text-dim); }
nav.topnav .links a.active { color: var(--text); }
nav.topnav .user { color: var(--text-dim); font-size: 0.85rem; }

h1, h2, h3 { font-weight: 600; letter-spacing: 0.2px; }
h1 { font-size: 1.6rem; margin: 0 0 6px; }
.subtitle { color: var(--text-dim); margin: 0 0 24px; font-size: 0.95rem; }

.card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
  margin-bottom: 20px;
}

input[type=text], input[type=password], select {
  width: 100%;
  padding: 12px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 1rem;
  text-transform: lowercase;
}
input[type=text]::placeholder { color: var(--text-dim); }
input:focus, select:focus { outline: none; border-color: var(--accent); }

label { display: block; font-size: 0.85rem; color: var(--text-dim); margin: 16px 0 6px; }
label:first-child { margin-top: 0; }

input[type=file] {
  width: 100%;
  color: var(--text-dim);
  font-size: 0.9rem;
}

button, .btn {
  display: inline-block;
  background: var(--accent);
  color: #0b0c0e;
  border: none;
  border-radius: var(--radius);
  padding: 12px 20px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  text-transform: lowercase;
  margin-top: 16px;
}
button:hover, .btn:hover { opacity: 0.9; text-decoration: none; }
button.secondary {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
}
button.danger { background: var(--danger); color: #2a0a0a; }

.error { color: var(--danger); font-size: 0.9rem; margin-top: 10px; }
.success { color: var(--accent); font-size: 0.9rem; margin-top: 10px; }

.puzzle-media {
  display: flex;
  justify-content: center;
  margin-bottom: 20px;
  overflow: hidden; /* clips the warp/zoom fx variants to the frame */
}
.puzzle-media img {
  width: 100%;
  max-width: 480px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}
.puzzle-media audio { width: 100%; }

/* runtime obfuscation effects — each puzzle gets exactly one, always applied,
   consistently (never on the post-solve reveal). grayscale/invert/flip/
   rotate180 are baked into the image bytes server-side; these five are pure
   CSS, tuned to actually cost the viewer information moment-to-moment rather
   than just jostling the whole intact image around. */

/* rainbow — plain hue cycling, no extra color boost. */
.puzzle-media img.fx-hue-rotate { animation: fx-hue-rotate 4s linear infinite; }
@keyframes fx-hue-rotate {
  from { filter: hue-rotate(0deg); }
  to   { filter: hue-rotate(360deg); }
}

/* scanning slit — only a thin sliver of the image is visible at once,
   sliding smoothly top to bottom, so the viewer has to mentally stitch the
   whole thing together from a moving strip rather than seeing it all at once. */
.puzzle-media img.fx-scan-slit { animation: fx-scan-slit 3.2s ease-in-out infinite; }
@keyframes fx-scan-slit {
  0%, 100% { clip-path: inset(0% 0% 85% 0%); transform: scale(1.05); }
  50%      { clip-path: inset(85% 0% 0% 0%); transform: scale(1.05); }
}

/* mirror flip — continuously flips horizontally through a squashed
   mid-point rather than snapping, so proportions genuinely warp through
   the transition instead of just alternating between two clean states. */
.puzzle-media img.fx-mirror-flip { animation: fx-mirror-flip 3s ease-in-out infinite; }
@keyframes fx-mirror-flip {
  0%, 100% { transform: scaleX(1) scale(1.02); }
  50%      { transform: scaleX(-1) scale(1.02); }
}

/* zoom + pan — the image is blown up past the frame and drifts around it,
   so only ever a cropped slice is visible at any instant. */
.puzzle-media img.fx-zoom-pan { animation: fx-zoom-pan 5.5s ease-in-out infinite; }
@keyframes fx-zoom-pan {
  0%   { transform: scale(2) translate(12%, 12%); }
  25%  { transform: scale(2) translate(-14%, 9%); }
  50%  { transform: scale(2) translate(9%, -14%); }
  75%  { transform: scale(2) translate(-11%, -9%); }
  100% { transform: scale(2) translate(12%, 12%); }
}

/* glitch — color/geometry corruption that flows smoothly between states
   (eased, not stepped) so it reads as a fluid warping distortion rather
   than a choppy jump-cut. filter + transform only, so it renders correctly
   on fully opaque jpgs. */
.puzzle-media img.fx-glitch-shift { animation: fx-glitch-shift 3.2s ease-in-out infinite; }
@keyframes fx-glitch-shift {
  0%   { transform: translate(0, 0) scaleX(1); filter: contrast(1) saturate(1) hue-rotate(0deg) brightness(1); }
  20%  { transform: translate(-4%, 2%) scaleX(1.03); filter: contrast(1.8) saturate(0.3) brightness(1.15); }
  40%  { transform: translate(3%, -2%) scaleX(0.98); filter: contrast(1.5) saturate(2.2) hue-rotate(90deg); }
  60%  { transform: translate(-3%, -2%) scaleX(1.02); filter: contrast(2) saturate(0.4) brightness(0.85); }
  80%  { transform: translate(2%, 3%) scaleX(0.99); filter: contrast(1.4) saturate(1.8) hue-rotate(-70deg); }
  100% { transform: translate(0, 0) scaleX(1); filter: contrast(1) saturate(1) hue-rotate(0deg) brightness(1); }
}

.prompt {
  text-align: center;
  font-size: 1.1rem;
  color: var(--text-dim);
  margin-bottom: 18px;
}
.prompt strong { color: var(--text); }

.guess-row { display: flex; gap: 10px; margin-top: 10px; }
.guess-row input { flex: 1; }
.guess-row button { margin-top: 0; }

.attempts { color: var(--text-dim); font-size: 0.85rem; margin-top: 10px; }

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--border); font-size: 0.92rem; }
th { color: var(--text-dim); font-weight: 500; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; }
tr:last-child td { border-bottom: none; }
td.rank { color: var(--text-dim); width: 30px; }
tr.me td { color: var(--accent); }

.empty-state { text-align: center; color: var(--text-dim); padding: 40px 0; }

.result-banner {
  text-align: center;
  padding: 14px;
  border-radius: var(--radius);
  margin-top: 14px;
  font-weight: 600;
}
.result-banner.correct { background: var(--accent-dim); color: var(--accent); }
.result-banner.wrong { background: #3a1f1f; color: var(--danger); }
.uploader-credit { display: inline-block; margin-top: 4px; font-size: 0.8rem; font-weight: 400; opacity: 0.75; }
