:root {
  color-scheme: light;
  --font-ui: "Avenir Next", "Segoe UI Variable", "Helvetica Neue", Arial, sans-serif;
  --font-display: "Palatino Linotype", "Book Antiqua", Georgia, serif;
  --font-quote: "Al Bayan", "Geeza Pro", "Iowan Old Style", "Palatino Linotype", serif;
  --radius: 8px;
  --shadow: 0 24px 70px rgba(12, 24, 44, 0.12);
  --line: rgba(18, 32, 51, 0.12);
  --page-bg: linear-gradient(140deg, #eef4ff 0%, #fbfcff 42%, #eaf4ef 100%);
  --page-grid: rgba(41, 94, 255, 0.05);
  --surface: rgba(255, 255, 255, 0.76);
  --surface-strong: rgba(255, 255, 255, 0.94);
  --surface-soft: rgba(255, 255, 255, 0.54);
  --text: #122033;
  --muted: #627186;
  --soft: #8796a9;
  --accent: #295eff;
  --accent-2: #f4c55d;
  --accent-3: #43b98f;
  --button-bg: #295eff;
  --button-text: #ffffff;
  --button-soft: rgba(41, 94, 255, 0.1);
  --gold-1: #fff4bd;
  --gold-2: #d5a63c;
  --gold-3: #7f5513;
  --tool-original-bg: rgb(216, 245, 255);
}

body[data-theme="signal"] {
  color-scheme: dark;
  --page-bg: linear-gradient(140deg, #061311 0%, #071c18 44%, #111d43 100%);
  --page-grid: rgba(116, 240, 185, 0.06);
  --surface: rgba(7, 18, 17, 0.78);
  --surface-strong: rgba(5, 17, 15, 0.94);
  --surface-soft: rgba(116, 240, 185, 0.08);
  --text: #dff5ed;
  --muted: #95bdae;
  --soft: #789c91;
  --accent: #74f0b9;
  --accent-2: #f6c65d;
  --accent-3: #7ea0ff;
  --button-bg: #3ed39b;
  --button-text: #052018;
  --button-soft: rgba(116, 240, 185, 0.12);
  --line: rgba(116, 240, 185, 0.15);
  --shadow: 0 30px 88px rgba(0, 0, 0, 0.34);
}

body[data-theme="ledger"] {
  --page-bg: linear-gradient(150deg, #f7efe2 0%, #fbf8ef 35%, #e6f2f5 100%);
  --page-grid: rgba(23, 58, 109, 0.06);
  --surface: rgba(251, 247, 236, 0.83);
  --surface-strong: rgba(255, 252, 243, 0.96);
  --surface-soft: rgba(23, 58, 109, 0.06);
  --text: #2d2417;
  --muted: #70634f;
  --soft: #8a7d69;
  --accent: #8d6035;
  --accent-2: #173a6d;
  --accent-3: #2d9b91;
  --button-bg: #8d6035;
  --button-text: #fffaf0;
  --button-soft: rgba(141, 96, 53, 0.1);
  --line: rgba(78, 56, 24, 0.13);
  --shadow: 0 24px 66px rgba(87, 65, 36, 0.13);
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-ui);
  color: var(--text);
  background: var(--page-bg);
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background-image: linear-gradient(var(--page-grid) 1px, transparent 1px), linear-gradient(90deg, var(--page-grid) 1px, transparent 1px);
  background-size: 32px 32px;
  opacity: 0.8;
  z-index: -1;
}

button,
a,
input,
textarea {
  font: inherit;
}

button {
  cursor: pointer;
}

a {
  color: inherit;
}

.page-shell {
  width: min(1240px, calc(100% - 32px));
  margin: 0 auto;
  padding: 20px 0 56px;
}

.topbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px);
}

.brand-line {
  display: flex;
  gap: 14px;
  min-width: 0;
}

.brand-mark {
  width: 54px;
  height: 54px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(145deg, var(--surface-strong), var(--surface-soft));
}

.brand-mark span {
  width: 28px;
  height: 28px;
  display: block;
  clip-path: polygon(50% 0, 61% 34%, 96% 35%, 68% 56%, 79% 91%, 50% 70%, 21% 91%, 32% 56%, 4% 35%, 39% 34%);
  background: linear-gradient(145deg, var(--accent-2), var(--accent-3) 48%, var(--accent));
}

.brand-copy {
  min-width: 0;
}

.eyebrow,
.section-kicker {
  margin: 0 0 8px;
  color: var(--muted);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.site-title {
  width: fit-content;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.6vw, 2.35rem);
  line-height: 1.02;
  letter-spacing: 0;
  outline-offset: 4px;
}

.site-title:hover {
  color: var(--accent);
}

.site-intro {
  max-width: 68ch;
  margin: 9px 0 0;
  color: var(--muted);
  line-height: 1.56;
}

.top-controls {
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 250px;
}

.theme-picker {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
}

.theme-chip,
.icon-control,
.language-control,
.line-button,
.close-button,
.arrow-button,
.tool-link,
.svet-button,
.tm-button {
  appearance: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-strong);
  color: var(--text);
  transition: transform 0.16s ease, background 0.16s ease, border-color 0.16s ease, color 0.16s ease;
}

.theme-chip:hover,
.icon-control:hover,
.language-control:hover,
.line-button:hover,
.close-button:hover,
.arrow-button:hover,
.tool-link:hover,
.svet-button:hover,
.tm-button:hover,
.project-card:hover {
  transform: translateY(-1px);
}

.theme-chip {
  width: 38px;
  height: 38px;
  padding: 0;
  display: grid;
  place-items: center;
}

.theme-chip.is-active {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
}

.theme-swatch {
  width: 18px;
  height: 18px;
  display: block;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.62);
}

.theme-swatch-aurora {
  background: linear-gradient(135deg, #295eff, #67c6ff 48%, #f4c55d);
}

.theme-swatch-signal {
  background: linear-gradient(135deg, #061311, #74f0b9 52%, #7ea0ff);
}

.theme-swatch-ledger {
  background: linear-gradient(135deg, #f7efe2, #8d6035 48%, #173a6d);
}

.icon-control,
.language-control {
  min-height: 48px;
  padding: 0 12px;
}

.compact-control {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.compact-icon {
  width: 18px;
  height: 18px;
  display: block;
  background: linear-gradient(var(--accent), var(--accent)) 0 2px / 18px 2px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 0 8px / 18px 2px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 0 14px / 18px 2px no-repeat;
}

.control-label {
  font-size: 0.82rem;
  color: var(--muted);
}

.language-control {
  min-width: 54px;
  font-weight: 800;
  letter-spacing: 0.08em;
  background: var(--button-bg);
  color: var(--button-text);
  border-color: transparent;
}

.golden-gate {
  display: grid;
  place-items: center;
  padding: 26px 0 18px;
}

.gold-button {
  width: min(720px, 100%);
  min-height: 56px;
  padding: 14px 22px;
  border: 1px solid rgba(127, 85, 19, 0.42);
  border-radius: var(--radius);
  color: #3d2905;
  background: linear-gradient(92deg, #946915 0%, var(--gold-2) 16%, var(--gold-1) 48%, var(--gold-2) 78%, #8a5d12 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72), 0 18px 36px rgba(127, 85, 19, 0.16);
  font-weight: 800;
  letter-spacing: 0;
}

.gold-button:hover {
  transform: translateY(-1px);
}

.quotes-block,
.project-section,
.tool-section,
.secret-block {
  margin-top: 18px;
}

.quote-frame {
  width: min(860px, 100%);
  margin: 0 auto;
  position: relative;
  overflow: hidden;
  border: 1px solid rgba(127, 85, 19, 0.34);
  border-radius: var(--radius);
  padding: 24px;
  color: #3a2407;
  background:
    repeating-linear-gradient(90deg, rgba(127, 85, 19, 0.12) 0 1px, transparent 1px 18px),
    linear-gradient(180deg, #fff9df 0%, #fff3c5 48%, #f8e3a0 100%);
  box-shadow: 0 24px 64px rgba(127, 85, 19, 0.18);
}

.quote-frame::before,
.quote-frame::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  height: 10px;
  background: repeating-linear-gradient(90deg, #0f6f78 0 12px, #d7aa36 12px 24px, #b84436 24px 36px, #173a6d 36px 48px);
  opacity: 0.86;
}

.quote-frame::before {
  top: 12px;
}

.quote-frame::after {
  bottom: 12px;
}

.ornament-line {
  height: 1px;
  margin: 8px 0 18px;
  background: linear-gradient(90deg, transparent, rgba(127, 85, 19, 0.62), transparent);
}

.ornament-line-bottom {
  margin: 18px 0 8px;
}

.quote-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.quote-counter,
.quote-note {
  margin: 0;
  color: rgba(58, 36, 7, 0.72);
  font-size: 0.88rem;
}

.quote-text {
  display: block;
  width: 100%;
  min-height: 150px;
  margin: 14px 0;
  padding: 10px 16px;
  border: 0;
  background: transparent;
  color: #3a2407;
  font-family: var(--font-quote);
  font-size: clamp(1.35rem, 2.6vw, 2.25rem);
  line-height: 1.34;
  text-align: center;
}

.quote-actions {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 14px;
}

.arrow-button {
  width: 48px;
  height: 42px;
  padding: 0;
  border-color: rgba(127, 85, 19, 0.3);
  background: rgba(255, 255, 255, 0.38);
  color: #5b3908;
  font-size: 1.6rem;
  line-height: 1;
}

.secret-block {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 18px 0;
}

.secret-line {
  width: 76px;
  height: 3px;
  margin-bottom: 14px;
  background: linear-gradient(90deg, var(--accent-2), var(--accent), var(--accent-3));
}

.secret-title {
  display: inline-block;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2vw, 1.7rem);
  color: var(--accent);
  text-decoration: none;
}

.secret-title:hover {
  text-decoration: underline;
}

.secret-block p {
  max-width: 76ch;
  margin: 10px 0 0;
  color: var(--muted);
  line-height: 1.62;
}

.project-section {
  padding-top: 14px;
}

.section-heading {
  display: flex;
  justify-content: space-between;
  align-items: end;
  gap: 18px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line);
  padding-bottom: 12px;
}

.section-heading h2,
.tool-bar h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.2vw, 2rem);
  line-height: 1.1;
}

.project-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.project-card {
  min-height: 146px;
  display: grid;
  align-content: start;
  gap: 10px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  text-align: left;
  text-decoration: none;
  box-shadow: 0 10px 32px rgba(12, 24, 44, 0.05);
}

button.project-card {
  width: 100%;
}

.project-card strong {
  font-size: 1.04rem;
  line-height: 1.26;
}

.project-card span {
  color: var(--muted);
  line-height: 1.5;
}

.project-meta {
  margin-top: auto;
  display: inline-flex;
  align-items: center;
  width: fit-content;
  min-height: 28px;
  padding: 4px 9px;
  border-radius: var(--radius);
  background: var(--button-soft);
  color: var(--accent);
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.tool-section {
  border-top: 1px solid var(--line);
  padding-top: 18px;
}

.tool-bar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
}

.tool-actions {
  display: inline-flex;
  gap: 8px;
  align-items: center;
}

.line-button {
  min-height: 42px;
  padding: 9px 13px;
  background: var(--surface-strong);
}

.close-button {
  width: 42px;
  height: 42px;
  padding: 0;
  font-size: 1.3rem;
  line-height: 1;
}

.embedded-tool {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.tool-current {
  background: var(--surface);
  color: var(--text);
}

.tool-original.svet-tool {
  background: var(--tool-original-bg);
  color: #101820;
}

.tool-original.tm-tool {
  --tm-bg: rgb(216, 245, 255);
  --tm-text: #111;
  --tm-button-bg: #cbfacbb8;
  --tm-button-border: #5dac5d;
  --tm-card: #ffffed;
  --tm-input: #ffffff;
  --tm-flip-bg: #2c3e50;
  --tm-flip-text: #ecf0f1;
  background: var(--tm-bg);
  color: var(--tm-text);
}

.tool-current.tm-tool {
  --tm-bg: transparent;
  --tm-text: var(--text);
  --tm-button-bg: var(--button-bg);
  --tm-button-border: transparent;
  --tm-card: var(--surface-strong);
  --tm-input: var(--surface-strong);
  --tm-flip-bg: color-mix(in srgb, var(--accent) 24%, #172238);
  --tm-flip-text: #ffffff;
}

.svet-layout,
.tm-layout {
  padding: 22px;
}

.svet-layout {
  display: grid;
  gap: 18px;
  text-align: center;
}

.svet-original .svet-button,
.tool-original.svet-tool .svet-button {
  background: #cbfacbb8;
  color: #101820;
  border-color: rgba(0, 0, 0, 0.45);
}

.tool-current.svet-tool .svet-button,
.tm-button {
  background: var(--button-bg);
  color: var(--button-text);
  border-color: transparent;
}

.svet-title {
  margin: 0;
  color: inherit;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2vw, 1.7rem);
}

.svet-lead {
  margin: 0 auto;
  max-width: 70ch;
  color: currentColor;
  opacity: 0.78;
  line-height: 1.52;
}

.svet-king-link,
.tool-link {
  justify-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: 10px 15px;
  text-decoration: none;
}

.svet-king-link {
  border: 2px solid currentColor;
  background: #cbfacbb8;
  color: darkblue;
  border-radius: var(--radius);
}

.tool-current .svet-king-link {
  border-color: var(--line);
  background: var(--button-soft);
  color: var(--accent);
}

.svet-exercises {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.svet-exercise {
  display: grid;
  gap: 12px;
  align-content: start;
  min-height: 220px;
  padding: 16px;
  border: 1px solid rgba(0, 0, 0, 0.16);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.34);
}

.tool-current .svet-exercise {
  border-color: var(--line);
  background: var(--surface-soft);
}

.svet-exercise h3,
.tm-card h3 {
  margin: 0;
  font-size: 1rem;
}

.svet-exercise p {
  margin: 0;
  color: currentColor;
  opacity: 0.74;
  line-height: 1.45;
}

.svet-button,
.tm-button {
  justify-self: center;
  min-height: 44px;
  padding: 10px 16px;
}

.svet-countdown,
.svet-result {
  min-height: 36px;
  display: grid;
  place-items: center;
  font-size: 1.1rem;
}

.svet-result strong {
  color: var(--accent);
}

.svet-legal {
  border-top: 1px solid rgba(0, 0, 0, 0.24);
  padding-top: 14px;
  display: grid;
  gap: 10px;
}

.tool-current .svet-legal {
  border-color: var(--line);
}

.svet-legal-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
}

.svet-legal-text {
  max-width: 76ch;
  margin: 0 auto;
  text-align: left;
  color: currentColor;
  opacity: 0.78;
  line-height: 1.55;
}

.tm-layout {
  min-height: 560px;
  display: grid;
  place-items: center;
  background: var(--tm-bg);
  color: var(--tm-text);
  text-align: center;
}

.tm-screen {
  width: min(760px, 100%);
  display: grid;
  justify-items: center;
  gap: 16px;
}

.tm-screen[hidden] {
  display: none;
}

.tm-screen h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.45rem, 2.4vw, 2.1rem);
}

.tm-hint {
  max-width: 58ch;
  margin: 0;
  color: currentColor;
  opacity: 0.72;
  line-height: 1.5;
}

.tm-button {
  background: var(--tm-button-bg);
  color: var(--button-text);
  border-color: var(--tm-button-border);
}

.tool-original.tm-tool .tm-button {
  color: #0b1a0b;
}

.tm-form {
  width: min(420px, 100%);
  display: grid;
  gap: 10px;
}

.tm-form textarea,
.tm-form input {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 12px;
  background: var(--tm-input);
  color: var(--tm-text);
}

.tm-form textarea {
  min-height: 86px;
  resize: vertical;
}

.tm-timer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin: 8px 0;
}

.flip-container {
  display: flex;
  gap: 7px;
}

.flip-digit {
  position: relative;
  width: 70px;
  height: 96px;
  perspective: 400px;
  border-radius: var(--radius);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);
}

.flip-digit::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 2px;
  background: rgba(0, 0, 0, 0.48);
  z-index: 10;
  transform: translateY(-1px);
}

.digit-top,
.digit-bottom,
.flip-card-front,
.flip-card-back {
  position: absolute;
  width: 100%;
  overflow: hidden;
  color: var(--tm-flip-text);
  font-family: "Courier New", monospace;
  font-size: 62px;
  font-weight: 800;
}

/* Digit halves are 50% of the digit container */
.digit-top,
.digit-bottom {
  height: 50%;
}

/* Flip card faces fill their parent .flip-card (which is already 50% of digit) */
.flip-card-front,
.flip-card-back {
  height: 100%;
}

.digit-top,
.flip-card-front {
  top: 0;
  background: linear-gradient(180deg, color-mix(in srgb, var(--tm-flip-bg) 72%, #ffffff), var(--tm-flip-bg));
  border-radius: var(--radius) var(--radius) 0 0;
}

.digit-bottom {
  bottom: 0;
  background: linear-gradient(180deg, var(--tm-flip-bg), color-mix(in srgb, var(--tm-flip-bg) 72%, #000000));
  border-radius: 0 0 var(--radius) var(--radius);
}

/* Back face sits at top:0 inside flip-card; rotateX(180deg) makes it the back */
.flip-card-back {
  top: 0;
  background: linear-gradient(180deg, var(--tm-flip-bg), color-mix(in srgb, var(--tm-flip-bg) 72%, #000000));
  border-radius: 0 0 var(--radius) var(--radius);
}

.digit-top::after,
.digit-bottom::after,
.flip-card-front::after,
.flip-card-back::after {
  content: attr(data-value);
  position: absolute;
  left: 0;
  width: 100%;
  height: 200%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.digit-top::after,
.flip-card-front::after {
  top: 0;
}

.digit-bottom::after,
.flip-card-back::after {
  bottom: 0;
}

.flip-card {
  position: absolute;
  top: 0;
  width: 100%;
  height: 50%;
  transform-style: preserve-3d;
  transform-origin: center bottom;
  z-index: 5;
}

.flip-card-front,
.flip-card-back {
  backface-visibility: hidden;
}

.flip-card-back {
  transform: rotateX(180deg);
}

.flip-card.flipping {
  animation: flip-card 0.58s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

.timer-separator {
  font-size: 54px;
  font-weight: 800;
  color: currentColor;
  animation: blink 1s infinite;
}

.timer-separator.stopped {
  animation: none;
}

.tm-card {
  position: relative;
  width: min(420px, 100%);
  display: none;
  overflow: hidden;
  border: 2px solid var(--line);
  border-radius: var(--radius);
  background: var(--tm-card);
  color: var(--tm-text);
  text-align: left;
}

.tm-card-refresh {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid rgba(0, 0, 0, 0.18);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.72);
  color: #333;
  display: grid;
  place-items: center;
  cursor: pointer;
  opacity: 0.72;
  transition: opacity 0.15s, transform 0.25s;
}

.tm-card-refresh:hover {
  opacity: 1;
  transform: rotate(35deg);
}

.tool-original.tm-tool .tm-card-refresh {
  background: rgba(255, 255, 237, 0.88);
  border-color: rgba(0, 0, 0, 0.22);
  color: #1a3a1a;
}

.tool-current .tm-card-refresh {
  background: var(--surface-strong);
  border-color: var(--line);
  color: var(--text);
}

.tm-card.is-visible {
  display: block;
}

.tm-card img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  display: block;
}

.tm-card-content {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  justify-content: space-between;
  padding: 16px;
}

.tm-card-info p {
  margin: 4px 0;
}

.tm-card-comment {
  font-weight: 800;
}

.tm-stamp {
  width: 98px;
  height: 98px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  margin-left: 0;
  border: 2px solid #3b82f6;
  border-radius: 50%;
  color: #3b82f6;
  text-align: center;
  font-weight: 800;
  transform: translateX(clamp(72px, 18vw, 106px)) rotate(-8deg);
}

.tm-stamp span {
  display: block;
  transform: rotate(-16deg);
  font-size: 0.86rem;
  line-height: 1.25;
}

.tm-stamp-focused {
  font-size: 1em;
}

.tm-stamp .tm-stamp-focused-ru {
  font-size: 0.8em;
}

body[data-density="compact"] .page-shell {
  width: min(1180px, calc(100% - 20px));
  padding-top: 10px;
}

body[data-density="compact"] .topbar,
body[data-density="compact"] .project-card,
body[data-density="compact"] .quote-frame,
body[data-density="compact"] .svet-layout,
body[data-density="compact"] .tm-layout {
  padding: 12px;
}

body[data-density="compact"] .site-intro,
body[data-density="compact"] .svet-lead,
body[data-density="compact"] .tm-hint {
  display: none;
}

body[data-density="compact"] .project-card {
  min-height: 116px;
}

body[data-density="compact"] .svet-exercises {
  gap: 8px;
}

@keyframes flip-card {
  to {
    transform: rotateX(-180deg);
  }
}

@keyframes blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0.35; }
}

@media (max-width: 900px) {
  .topbar,
  .tool-bar,
  .section-heading {
    align-items: stretch;
    flex-direction: column;
  }

  .top-controls {
    justify-content: flex-start;
    min-width: 0;
  }

  .project-list,
  .svet-exercises {
    grid-template-columns: 1fr;
  }

  .quote-head,
  .tm-card-content {
    flex-direction: column;
  }

  .tm-card-content {
    align-items: center;
    text-align: center;
  }
}

@media (max-width: 560px) {
  .page-shell {
    width: min(100% - 20px, 1240px);
    padding-bottom: 32px;
  }

  .brand-line {
    flex-direction: column;
  }

  .control-label {
    display: none;
  }

  .gold-button {
    min-height: 64px;
  }

  .quote-text {
    min-height: 190px;
    font-size: 1.22rem;
  }

  .flip-digit {
    width: 42px;
    height: 64px;
  }

  .digit-top,
  .digit-bottom,
  .flip-card-front,
  .flip-card-back {
    font-size: 40px;
  }

  .tm-timer {
    gap: 6px;
  }

  .timer-separator {
    font-size: 38px;
  }

  .tm-stamp {
    width: 86px;
    height: 86px;
  }
}
*** Add File: /Users/w/Documents/Projects/intu-btn/zu.ax/app.js
const STORAGE_KEY = "zuax.portal.state.v1";
const TIMER_MINUTES = 25;

const defaultState = {
  lang: "en",
  theme: "aurora",
  compact: false,
  quotesOpen: false,
  quoteIndex: 0,
  secretVisible: false,
  blocks: {
    svet: false,
    tm: false,
  },
  skins: {
    svet: "original",
    tm: "original",
  },
  svetLegal: "",
  tm: {
    running: false,
    startedAt: null,
    durationSeconds: TIMER_MINUTES * 60,
    timeLeft: TIMER_MINUTES * 60,
    comment: "",
    distractions: "",
    cardVisible: false,
    card: null,
  },
};

const copy = {
  en: {
    documentTitle: "ZU.AX - Intuition Practice",
    eyebrow: "Intuition | Training | Formation",
    title: "ZU.AX Intuition Practice",
    intro: "A compact hub for intuition trainers, formation practice and focus sessions, blended from Aurora, Signal, Ledger and the ZU.AX portfolio style.",
    compact: "Compact",
    compactOn: "Compact on",
    quoteButtonClosed: "Golden M. S. Norbekov Quotes About Intuition",
    quoteButtonOpen: "Hide Golden M. S. Norbekov Quotes About Intuition",
    quotesKicker: "Golden practice lines",
    quoteNote: "Authorial practice formulations inspired by the requested themes, not verbatim excerpts from books.",
    projectKicker: "Project links",
    projectHeading: "Practice Tools And Games",
    external: "Open site",
    internal: "Open block",
    hideBlock: "Hide block",
    secretTitle: "3D intuition game trainer Path of Heroes",
    secretText: "A secret project we cannot show yet is already in production: the 3D intuition game trainer Path of Heroes. It will be available in 2026. Watch for announcements.",
    svetPanelKicker: "Embedded practice",
    svetPanelTitle: "Svet - intuition and formation",
    tmPanelKicker: "Embedded focus tool",
    tmPanelTitle: "W. Go TM Tool",
    useOriginal: "Original design",
    useCurrent: "Current theme",
    switchToOriginal: "Switch to original",
    switchToCurrent: "Switch to current theme",
    languageHint: "Click 5 times in 3 seconds to unlock Russian.",
    themes: {
      aurora: "Aurora Castle",
      signal: "Signal Moon",
      ledger: "Ledger Silk",
    },
    links: {
      path: {
        title: "Path of Heroes",
        desc: "Intuition development game trainer with a journey structure and clear practice loop.",
      },
      roulette: {
        title: "Multiplayer Roulette Simulator",
        desc: "A multiplayer training simulator for game roulette decisions and probability attention.",
      },
      king: {
        title: "King of the Hill",
        desc: "A simple intuition training game from the M. S. Norbekov portal tradition.",
      },
      svet: {
        title: "Intuition and formation practice page Svet",
        desc: "Open the original exercises inside this page: car number, emotional state and dice formation.",
      },
      tm: {
        title: "W. Go TM Tool",
        desc: "A flip-card focus session timer with a completion card, images and distraction notes.",
      },
    },
    svet: {
      title: "Formation and intuition practice",
      lead: "Train the first inner signal, then compare it with a clean random result.",
      kingLink: "Intuition game King of the Hill",
      carTitle: "Car formation",
      carDesc: "Number digits from 000 to 999. Colors: red, orange, yellow, green, cyan, blue, violet, white, black.",
      form: "Form",
      color: "Car color",
      number: "Number",
      emotionTitle: "One of the states",
      emotionDesc: "Fear, tenderness, victory or anger.",
      answer: "Answer",
      state: "State",
      diceTitle: "Dice combinations",
      diceDesc: "Form five dice before the result appears.",
      roll: "Roll dice",
      privacy: "Privacy Policy",
      terms: "Terms of Use",
      privacyText: "We respect privacy and use local data only to keep this practice page convenient on your device.",
      termsText: "Use the exercises lawfully, calmly and as a practice tool. Results are random and are not a promise of prediction accuracy.",
      colors: ["red", "orange", "yellow", "green", "cyan", "blue", "violet", "white", "black"],
      emotions: ["fear", "tenderness", "victory", "anger"],
    },
    tm: {
      startTitle: "W. Go TM Tool",
      startHint: "Start a 25-minute focus session. At the end, create a small completion card.",
      start: "Start timer",
      focusTitle: "Focus timer",
      stop: "Stop timer",
      summary: "Session summary",
      comment: "Add a comment...",
      distractions: "Number of distractions",
      taskCompleted: "Task completed",
      focus: "Focus",
      distractionsLabel: "Distractions",
      veryGood: "Very good!",
      good: "Good!",
      focused: "Focused",
      done: "Done",
    },
  },
  de: {
    documentTitle: "ZU.AX - Intuitionspraxis",
    eyebrow: "Intuition | Training | Formung",
    title: "ZU.AX Intuitionspraxis",
    intro: "Ein kompakter Hub für Intuitionstraining, Formungspraxis und Fokus-Sessions, gemischt aus Aurora, Signal, Ledger und dem ZU.AX-Portfolio-Stil.",
    compact: "Kompakt",
    compactOn: "Kompakt an",
    quoteButtonClosed: "Goldene Zitate von M. S. Norbekov über Intuition",
    quoteButtonOpen: "Goldene Zitate von M. S. Norbekov ausblenden",
    quotesKicker: "Goldene Praxislinien",
    quoteNote: "Autorische Praxisformulierungen nach den gewünschten Themen, keine wörtlichen Buchauszüge.",
    projectKicker: "Projektlinks",
    projectHeading: "Praxiswerkzeuge und Spiele",
    external: "Seite öffnen",
    internal: "Block öffnen",
    hideBlock: "Block ausblenden",
    secretTitle: "3D-Intuitionsspiel Path of Heroes",
    secretText: "Ein geheimes Projekt, das wir noch nicht zeigen können, ist bereits in Arbeit: das 3D-Intuitionsspiel Path of Heroes. Es wird 2026 verfügbar sein. Achte auf Ankündigungen.",
    svetPanelKicker: "Eingebettete Praxis",
    svetPanelTitle: "Svet - Intuition und Formung",
    tmPanelKicker: "Eingebettetes Fokuswerkzeug",
    tmPanelTitle: "W. Go TM Tool",
    useOriginal: "Originaldesign",
    useCurrent: "Aktuelles Thema",
    switchToOriginal: "Zum Original wechseln",
    switchToCurrent: "Zum aktuellen Thema wechseln",
    languageHint: "5 Klicks in 3 Sekunden schalten Russisch frei.",
    themes: {
      aurora: "Aurora Burg",
      signal: "Signal Mond",
      ledger: "Ledger Seide",
    },
    links: {
      path: {
        title: "Path of Heroes",
        desc: "Intuitionsspiel mit Reiseaufbau und klarem Trainingsloop.",
      },
      roulette: {
        title: "Multiplayer-Roulette-Simulator",
        desc: "Ein Multiplayer-Trainingssimulator für Roulette-Entscheidungen und Wahrscheinlichkeitsaufmerksamkeit.",
      },
      king: {
        title: "König des Berges",
        desc: "Ein einfaches Intuitionstrainingsspiel aus der Portal-Tradition von M. S. Norbekov.",
      },
      svet: {
        title: "Praxis-Seite für Intuition und Formung Svet",
        desc: "Öffnet die ursprünglichen Übungen in dieser Seite: Autonummer, emotionaler Zustand und Würfelformung.",
      },
      tm: {
        title: "W. Go TM Tool",
        desc: "Ein Flip-Card-Fokustimer mit Abschlusskarte, Bildern und Ablenkungsnotizen.",
      },
    },
    svet: {
      title: "Training von Formung und Intuition",
      lead: "Trainiere zuerst das innere Signal und vergleiche es dann mit einem sauberen Zufallsergebnis.",
      kingLink: "Intuitionsspiel König des Berges",
      carTitle: "Autoformung",
      carDesc: "Ziffern von 000 bis 999. Farben: rot, orange, gelb, grün, türkis, blau, violett, weiß, schwarz.",
      form: "Formen",
      color: "Autofarbe",
      number: "Nummer",
      emotionTitle: "Einer der Zustände",
      emotionDesc: "Angst, Zärtlichkeit, Sieg oder Wut.",
      answer: "Antwort",
      state: "Zustand",
      diceTitle: "Würfelkombinationen",
      diceDesc: "Forme fünf Würfel, bevor das Ergebnis erscheint.",
      roll: "Würfeln",
      privacy: "Datenschutz",
      terms: "Nutzungsbedingungen",
      privacyText: "Wir respektieren Privatsphäre und nutzen lokale Daten nur, um diese Praxis-Seite auf deinem Gerät bequem zu halten.",
      termsText: "Nutze die Übungen rechtmäßig, ruhig und als Praxiswerkzeug. Ergebnisse sind zufällig und versprechen keine Vorhersagegenauigkeit.",
      colors: ["rot", "orange", "gelb", "grün", "türkis", "blau", "violett", "weiß", "schwarz"],
      emotions: ["Angst", "Zärtlichkeit", "Sieg", "Wut"],
    },
    tm: {
      startTitle: "W. Go TM Tool",
      startHint: "Starte eine 25-minütige Fokus-Session. Am Ende entsteht eine kleine Abschlusskarte.",
      start: "Timer starten",
      focusTitle: "Fokustimer",
      stop: "Timer stoppen",
      summary: "Sitzungsübersicht",
      comment: "Kommentar hinzufügen...",
      distractions: "Anzahl der Ablenkungen",
      taskCompleted: "Aufgabe abgeschlossen",
      focus: "Fokus",
      distractionsLabel: "Ablenkungen",
      veryGood: "Sehr gut!",
      good: "Gut!",
      focused: "Fokussiert",
      done: "Fertig",
    },
  },
  ru: {
    documentTitle: "ZU.AX - практикум интуиции",
    eyebrow: "Интуиция | тренировка | формирование",
    title: "ZU.AX Практикум Интуиции",
    intro: "Компактный узел для тренажеров интуиции, формирования и focus-сессий, собранный из Aurora, Signal, Ledger и портфолио-стиля ZU.AX.",
    compact: "Компактно",
    compactOn: "Компактно включено",
    quoteButtonClosed: "Золотые цитаты М. С. Норбекова про интуицию",
    quoteButtonOpen: "Скрыть золотые цитаты М. С. Норбекова про интуицию",
    quotesKicker: "Золотые практические строки",
    quoteNote: "Авторские формулировки по мотивам заявленных тем, не дословные выдержки из книг.",
    projectKicker: "Ссылки проекта",
    projectHeading: "Практики и игры",
    external: "Открыть сайт",
    internal: "Открыть блок",
    hideBlock: "Скрыть блок",
    secretTitle: "3D-игра-тренажер развития интуиции Путь Героев",
    secretText: "Секретный проект, который мы пока не можем показать, но над которым уже идет работа - это 3D-игра-тренажер развития интуиции Путь Героев. Она будет доступна в 2026 году. Следите за анонсами!",
    svetPanelKicker: "Встроенный практикум",
    svetPanelTitle: "Svet - интуиция и формирование",
    tmPanelKicker: "Встроенный focus-инструмент",
    tmPanelTitle: "W. Go TM Tool",
    useOriginal: "Оригинальный дизайн",
    useCurrent: "Текущая тема",
    switchToOriginal: "Переключить на оригинал",
    switchToCurrent: "Переключить на текущую тему",
    languageHint: "5 кликов за 3 секунды включают русский.",
    themes: {
      aurora: "Aurora Castle",
      signal: "Signal Moon",
      ledger: "Ledger Silk",
    },
    links: {
      path: {
        title: "Путь Героев",
        desc: "Игра-тренажер развития интуиции Path of Heroes с маршрутом и ясным циклом практики.",
      },
      roulette: {
        title: "Мультиплеерный тренажер-симулятор игровой рулетки",
        desc: "Тренажер для игровых решений, внимания к вероятности и спокойной проверки первого импульса.",
      },
      king: {
        title: "Царь Горы",
        desc: "Простая игра-тренажер развития интуиции с портала М. С. Норбекова.",
      },
      svet: {
        title: "Страница-практикум интуиции и формирования Svet",
        desc: "Открывает упражнения прямо внутри этой страницы: авто, состояние и комбинации кубиков.",
      },
      tm: {
        title: "W. Go TM Tool",
        desc: "Перелистывающийся таймер focus-сессий с карточкой результата, изображениями и счетчиком отвлечений.",
      },
    },
    svet: {
      title: "Тренировка формирования и интуиции",
      lead: "Сначала тренируйте внутренний первый сигнал, затем спокойно сравнивайте его с чистым случайным результатом.",
      kingLink: "Игра на интуицию Царь Горы",
      carTitle: "Формирование авто",
      carDesc: "Цифры номера: от 000 до 999. Цвета: красный, оранжевый, желтый, зеленый, голубой, синий, фиолетовый, белый, черный.",
      form: "Сформировать",
      color: "Цвет машины",
      number: "Номер",
      emotionTitle: "Одно из состояний",
      emotionDesc: "Страх, нежность, победа или злость.",
      answer: "Ответ",
      state: "Состояние",
      diceTitle: "Формирование комбинаций кубиков",
      diceDesc: "Сформируйте пять кубиков до появления результата.",
      roll: "Бросить кубики",
      privacy: "Политика конфиденциальности",
      terms: "Условия использования",
      privacyText: "Мы уважаем конфиденциальность и используем локальные данные только для удобства этой страницы на вашем устройстве.",
      termsText: "Используйте упражнения законно, спокойно и как инструмент практики. Результаты случайны и не обещают точности предсказаний.",
      colors: ["красный", "оранжевый", "желтый", "зеленый", "голубой", "синий", "фиолетовый", "белый", "черный"],
      emotions: ["страх", "нежность", "победа", "злость"],
    },
    tm: {
      startTitle: "W. Go TM Tool",
      startHint: "Запустите 25-минутную focus-сессию. В конце появится мини-карточка завершения.",
      start: "Start Timer",
      focusTitle: "Focus timer",
      stop: "Stop timer",
      summary: "Итог сессии",
      comment: "Добавьте комментарий...",
      distractions: "Количество отвлечений",
      taskCompleted: "Задача завершена",
      focus: "Фокус",
      distractionsLabel: "Отвлечения",
      veryGood: "Очень хорошо!",
      good: "Хорошо!",
      focused: "Focused",
      done: "Done",
    },
  },
};

const quotes = [
  {
    ru: "Интуиция крепнет не от ожидания чуда, а от ежедневной проверки тихого первого импульса.",
    en: "Intuition grows not from waiting for a miracle, but from checking the quiet first impulse every day.",
    de: "Intuition wächst nicht durch Warten auf ein Wunder, sondern durch tägliches Prüfen des leisen ersten Impulses.",
  },
  {
    ru: "Сначала настройте тело: прямая спина, мягкая улыбка, спокойное дыхание. Потом спрашивайте внутренний экран.",
    en: "Tune the body first: straight back, soft smile, calm breath. Then ask the inner screen.",
    de: "Stimme zuerst den Körper: gerader Rücken, weiches Lächeln, ruhiger Atem. Dann frage den inneren Bildschirm.",
  },
  {
    ru: "Тренировка интуиции любит краткость: лучше десять чистых минут каждый день, чем час раз в месяц.",
    en: "Intuition training loves brevity: ten clean minutes daily beat one hour once a month.",
    de: "Intuitionstraining liebt Kürze: zehn klare Minuten täglich sind stärker als eine Stunde im Monat.",
  },
  {
    ru: "Формирование события начинается с состояния, а не с напряженного желания получить результат любой ценой.",
    en: "Event formation begins with state, not with tense desire to get a result at any cost.",
    de: "Ereignisformung beginnt mit dem Zustand, nicht mit dem verkrampften Wunsch nach Ergebnis um jeden Preis.",
  },
  {
    ru: "Перед упражнением спросите себя: что я почувствовал первым? После результата запишите без оправданий.",
    en: "Before the exercise ask: what did I feel first? After the result, write it down without excuses.",
    de: "Frage vor der Übung: Was habe ich zuerst gespürt? Nach dem Ergebnis notiere es ohne Ausreden.",
  },
  {
    ru: "Не спорьте с первым сигналом во время практики; спорить можно позже, когда появится статистика.",
    en: "Do not argue with the first signal during practice; argue later, when statistics appear.",
    de: "Streite während der Praxis nicht mit dem ersten Signal; später, mit Statistik, darfst du prüfen.",
  },
  {
    ru: "Интуиция не любит суеты: три спокойных вдоха часто точнее двадцати тревожных догадок.",
    en: "Intuition dislikes fuss: three calm breaths are often sharper than twenty anxious guesses.",
    de: "Intuition mag keine Hektik: drei ruhige Atemzüge sind oft klarer als zwanzig ängstliche Vermutungen.",
  },
  {
    ru: "Формируйте маленькие события: цвет, число, настроение. Малые цели учат систему попадать без тяжести.",
    en: "Form small events: a color, a number, a mood. Small targets teach the system to hit without heaviness.",
    de: "Forme kleine Ereignisse: Farbe, Zahl, Stimmung. Kleine Ziele lehren das Treffen ohne Schwere.",
  },
  {
    ru: "Пятнадцать минут практики в день в течение сорока дней создают привычку слышать тоньше.",
    en: "Fifteen minutes a day for forty days builds the habit of hearing more finely.",
    de: "Fünfzehn Minuten täglich über vierzig Tage schaffen die Gewohnheit, feiner zu hören.",
  },
  {
    ru: "Результаты приходят волнами: сначала ясность ощущений, затем точность, затем спокойная уверенность.",
    en: "Results arrive in waves: first clarity of sensation, then accuracy, then calm confidence.",
    de: "Ergebnisse kommen in Wellen: zuerst Klarheit der Empfindung, dann Genauigkeit, dann ruhige Sicherheit.",
  },
  {
    ru: "Если ошиблись, благодарите тренировку: ошибка показывает, где шум был громче внутреннего знания.",
    en: "If you miss, thank the practice: an error shows where noise was louder than inner knowing.",
    de: "Wenn du dich irrst, danke der Übung: der Fehler zeigt, wo Lärm lauter war als inneres Wissen.",
  },
  {
    ru: "Не превращайте интуицию в лотерею. Превратите ее в ремесло: повтор, запись, сравнение, вывод.",
    en: "Do not turn intuition into a lottery. Make it a craft: repeat, record, compare, conclude.",
    de: "Mache Intuition nicht zur Lotterie. Mache sie zum Handwerk: wiederholen, notieren, vergleichen, schließen.",
  },
  {
    ru: "Лучшее время для тренировки - когда вы бодры, доброжелательны и не пытаетесь доказать себе величие.",
    en: "The best time to practice is when you are awake, kind and not trying to prove greatness to yourself.",
    de: "Die beste Trainingszeit ist, wenn du wach, freundlich und nicht damit beschäftigt bist, Größe zu beweisen.",
  },
  {
    ru: "Событие формируется легче, когда цель ясна, образ короток, а отношение к результату свободно.",
    en: "An event forms more easily when the goal is clear, the image is brief and the attitude is free.",
    de: "Ein Ereignis formt sich leichter, wenn das Ziel klar, das Bild kurz und die Haltung frei ist.",
  },
  {
    ru: "После каждого подхода оставляйте одну строку дневника: дата, задача, первый сигнал, факт.",
    en: "After each round leave one diary line: date, task, first signal, fact.",
    de: "Nach jedem Durchgang schreibe eine Tagebuchzeile: Datum, Aufgabe, erstes Signal, Tatsache.",
  },
  {
    ru: "Тренируйтесь не до усталости, а до свежей собранности. На пике ясности остановиться тоже мастерство.",
    en: "Practice not until fatigue, but until fresh collectedness. Stopping at clarity is also mastery.",
    de: "Übe nicht bis zur Müdigkeit, sondern bis zur frischen Sammlung. Bei Klarheit zu stoppen ist auch Können.",
  },
  {
    ru: "Когда ум кричит, задайте вопрос телу: где ответ стал легче, теплее, спокойнее?",
    en: "When the mind shouts, ask the body: where did the answer become lighter, warmer, calmer?",
    de: "Wenn der Verstand schreit, frage den Körper: Wo wurde die Antwort leichter, wärmer, ruhiger?",
  },
  {
    ru: "Формирование не отменяет действия. Оно настраивает внимание, состояние и шаги в одну сторону.",
    en: "Formation does not replace action. It aligns attention, state and steps in one direction.",
    de: "Formung ersetzt Handlung nicht. Sie richtet Aufmerksamkeit, Zustand und Schritte in eine Richtung aus.",
  },
  {
    ru: "Три недели дают первые заметные сдвиги, если практика ежедневная и честная перед фактами.",
    en: "Three weeks bring the first visible shifts when practice is daily and honest with facts.",
    de: "Drei Wochen bringen erste sichtbare Verschiebungen, wenn die Praxis täglich und ehrlich zu Fakten ist.",
  },
  {
    ru: "Не ищите громкий голос. Часто точный ответ приходит как скромное: вот это.",
    en: "Do not search for a loud voice. Often the accurate answer arrives as a modest: this one.",
    de: "Suche keine laute Stimme. Oft kommt die genaue Antwort als schlichtes: dieses hier.",
  },
  {
    ru: "Интуиция становится надежнее, когда вы одинаково спокойно встречаете попадание и промах.",
    en: "Intuition becomes more reliable when you meet a hit and a miss with the same calm.",
    de: "Intuition wird zuverlässiger, wenn du Treffer und Fehlgriff mit gleicher Ruhe empfängst.",
  },
  {
    ru: "Для формирования задайте образ, улыбнитесь ему, отпустите и сделайте ближайшее реальное действие.",
    en: "For formation, set the image, smile to it, release it and take the nearest real action.",
    de: "Für Formung setze das Bild, lächle ihm zu, lass los und tue die nächste reale Handlung.",
  },
  {
    ru: "Длительность важна меньше регулярности: тело верит тому, что вы повторяете без драматизма.",
    en: "Duration matters less than regularity: the body trusts what you repeat without drama.",
    de: "Dauer zählt weniger als Regelmäßigkeit: der Körper glaubt dem, was du ohne Drama wiederholst.",
  },
  {
    ru: "Каждая практика спрашивает не только угадай, но и стань человеком, который видит тоньше.",
    en: "Every practice asks not only to guess, but to become a person who sees more finely.",
    de: "Jede Praxis fragt nicht nur nach Erraten, sondern danach, ein Mensch mit feinerem Sehen zu werden.",
  },
  {
    ru: "Хорошая интуиция - это дисциплина внимания, радость проверки и уважение к реальности.",
    en: "Good intuition is discipline of attention, joy of testing and respect for reality.",
    de: "Gute Intuition ist Disziplin der Aufmerksamkeit, Freude am Prüfen und Respekt vor der Wirklichkeit.",
  },
];

const projects = [
  { id: "path", href: "https://www.hero.yt" },
  { id: "roulette", href: "https://www.hero.yt/r/" },
  { id: "king", href: "https://www.hero.yt/king/" },
  { id: "svet", action: "svet" },
  { id: "tm", action: "tm" },
];

const tmImages = [
  "tm/img/01.avif",
  "tm/img/02.avif",
  "tm/img/03.avif",
  "tm/img/04.avif",
  "tm/img/05.avif",
  "tm/img/06.avif",
  "tm/img/07.avif",
  "tm/img/08.avif",
  "tm/img/09.jpg",
  "tm/img/10.avif",
  "tm/img/11.avif",
];

const diceArt = {
  1: "⚀",
  2: "⚁",
  3: "⚂",
  4: "⚃",
  5: "⚄",
  6: "⚅",
};

let state = loadState();
let languageClicks = [];
let titleClicks = [];
let tmTimer = null;
let lastTmDigits = {};

document.addEventListener("DOMContentLoaded", () => {
  buildStaticTools();
  bindEvents();
  renderAll();
  resumeTimerIfNeeded();
});

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

function mergeDefaults(base, value) {
  const output = clone(base);
  if (!value || typeof value !== "object") {
    return output;
  }
  Object.keys(value).forEach((key) => {
    if (value[key] && typeof value[key] === "object" && !Array.isArray(value[key]) && output[key] && typeof output[key] === "object") {
      output[key] = mergeDefaults(output[key], value[key]);
      return;
    }
    output[key] = value[key];
  });
  return output;
}

function loadState() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    return raw ? mergeDefaults(defaultState, JSON.parse(raw)) : clone(defaultState);
  } catch {
    return clone(defaultState);
  }
}

function saveState() {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
}

function t(path) {
  return path.split(".").reduce((value, key) => value?.[key], copy[state.lang]) ?? path.split(".").reduce((value, key) => value?.[key], copy.en) ?? path;
}

function bindEvents() {
  document.getElementById("themePicker").addEventListener("click", (event) => {
    const button = event.target.closest("[data-theme-value]");
    if (!button) return;
    state.theme = button.dataset.themeValue;
    saveState();
    renderAll();
  });

  document.getElementById("compactToggle").addEventListener("click", () => {
    state.compact = !state.compact;
    saveState();
    renderAll();
  });

  document.getElementById("languageToggle").addEventListener("click", handleLanguageClick);
  document.getElementById("siteTitle").addEventListener("click", handleTitleClick);
  document.getElementById("siteTitle").addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      handleTitleClick();
    }
  });

  document.getElementById("quoteButton").addEventListener("click", () => {
    state.quotesOpen = !state.quotesOpen;
    saveState();
    renderQuotes();
  });
  document.getElementById("quoteText").addEventListener("click", () => stepQuote(1));
  document.getElementById("quotePrev").addEventListener("click", () => stepQuote(-1));
  document.getElementById("quoteNext").addEventListener("click", () => stepQuote(1));

  document.getElementById("projectList").addEventListener("click", (event) => {
    const button = event.target.closest("[data-open-tool]");
    if (!button) return;
    const tool = button.dataset.openTool;
    state.blocks[tool] = true;
    saveState();
    renderTools();
    document.getElementById(`${tool}Panel`).scrollIntoView({ behavior: "smooth", block: "start" });
  });

  document.getElementById("svetSkinToggle").addEventListener("click", () => toggleSkin("svet"));
  document.getElementById("tmSkinToggle").addEventListener("click", () => toggleSkin("tm"));
  document.getElementById("svetClose").addEventListener("click", () => closeTool("svet"));
  document.getElementById("tmClose").addEventListener("click", () => closeTool("tm"));
}

function handleLanguageClick() {
  const now = Date.now();
  languageClicks = languageClicks.filter((time) => now - time <= 3000);
  languageClicks.push(now);

  if (languageClicks.length >= 5) {
    state.lang = state.lang === "ru" ? "en" : "ru";
    languageClicks = [];
    saveState();
    renderAll();
    return;
  }

  if (state.lang !== "ru") {
    state.lang = state.lang === "en" ? "de" : "en";
    saveState();
    renderAll();
  }
}

function handleTitleClick() {
  const now = Date.now();
  titleClicks = titleClicks.filter((time) => now - time <= 3000);
  titleClicks.push(now);
  if (titleClicks.length >= 5) {
    state.secretVisible = !state.secretVisible;
    titleClicks = [];
    saveState();
    renderSecret();
  }
}

function stepQuote(delta) {
  state.quoteIndex = (state.quoteIndex + delta + quotes.length) % quotes.length;
  saveState();
  renderQuotes();
}

function toggleSkin(tool) {
  state.skins[tool] = state.skins[tool] === "original" ? "current" : "original";
  saveState();
  renderTools();
}

function closeTool(tool) {
  state.blocks[tool] = false;
  saveState();
  renderTools();
}

function renderAll() {
  document.documentElement.lang = state.lang;
  document.body.dataset.theme = state.theme;
  document.body.dataset.density = state.compact ? "compact" : "comfortable";
  renderThemePicker();
  renderCopy();
  renderQuotes();
  renderSecret();
  renderProjects();
  renderTools();
  renderSvetText();
  renderTmText();
  updateDocumentTitle();
}

function renderThemePicker() {
  const picker = document.getElementById("themePicker");
  picker.innerHTML = ["aurora", "signal", "ledger"].map((theme) => `
    <button class="theme-chip${state.theme === theme ? " is-active" : ""}" type="button" data-theme-value="${theme}" aria-label="${t(`themes.${theme}`)}" title="${t(`themes.${theme}`)}">
      <span class="theme-swatch theme-swatch-${theme}" aria-hidden="true"></span>
    </button>
  `).join("");
}

function renderCopy() {
  document.getElementById("eyebrow").textContent = t("eyebrow");
  document.getElementById("siteTitle").textContent = t("title");
  document.getElementById("siteIntro").textContent = t("intro");
  document.getElementById("projectKicker").textContent = t("projectKicker");
  document.getElementById("projectHeading").textContent = t("projectHeading");
  document.getElementById("compactLabel").textContent = state.compact ? t("compactOn") : t("compact");
  document.getElementById("compactToggle").setAttribute("aria-pressed", String(state.compact));
  const languageToggle = document.getElementById("languageToggle");
  languageToggle.textContent = state.lang.toUpperCase();
  languageToggle.title = t("languageHint");
  languageToggle.setAttribute("aria-label", t("languageHint"));
}

function renderQuotes() {
  document.getElementById("quoteButton").textContent = state.quotesOpen ? t("quoteButtonOpen") : t("quoteButtonClosed");
  const block = document.getElementById("quotesBlock");
  block.hidden = !state.quotesOpen;
  const index = Math.min(Math.max(state.quoteIndex, 0), quotes.length - 1);
  state.quoteIndex = index;
  document.getElementById("quotesKicker").textContent = t("quotesKicker");
  document.getElementById("quoteCounter").textContent = `${index + 1} / ${quotes.length}`;
  document.getElementById("quoteText").textContent = quotes[index][state.lang] || quotes[index].en;
  document.getElementById("quoteNote").textContent = t("quoteNote");
}

function renderSecret() {
  const block = document.getElementById("secretBlock");
  block.hidden = !state.secretVisible;
  document.getElementById("secretLink").textContent = t("secretTitle");
  document.getElementById("secretText").textContent = t("secretText");
}

function renderProjects() {
  const list = document.getElementById("projectList");
  list.innerHTML = projects.map((project) => {
    const data = t(`links.${project.id}`);
    if (project.href) {
      return `
        <a class="project-card" href="${project.href}" target="_blank" rel="noreferrer noopener">
          <strong>${data.title}</strong>
          <span>${data.desc}</span>
          <span class="project-meta">${t("external")}</span>
        </a>
      `;
    }
    return `
      <button class="project-card" type="button" data-open-tool="${project.action}">
        <strong>${data.title}</strong>
        <span>${data.desc}</span>
        <span class="project-meta">${state.blocks[project.action] ? t("hideBlock") : t("internal")}</span>
      </button>
    `;
  }).join("");
}

function renderTools() {
  renderToolShell("svet");
  renderToolShell("tm");
}

function renderToolShell(tool) {
  const panel = document.getElementById(`${tool}Panel`);
  const mount = document.getElementById(`${tool}Mount`);
  panel.hidden = !state.blocks[tool];
  mount.classList.toggle("tool-original", state.skins[tool] === "original");
  mount.classList.toggle("tool-current", state.skins[tool] === "current");
  document.getElementById(`${tool}PanelKicker`).textContent = t(`${tool}PanelKicker`);
  document.getElementById(`${tool}PanelTitle`).textContent = t(`${tool}PanelTitle`);
  const skinToggle = document.getElementById(`${tool}SkinToggle`);
  skinToggle.textContent = state.skins[tool] === "original" ? t("useOriginal") : t("useCurrent");
  skinToggle.title = state.skins[tool] === "original" ? t("switchToCurrent") : t("switchToOriginal");
}

function buildStaticTools() {
  buildSvet();
  buildTm();
}

function buildSvet() {
  const mount = document.getElementById("svetMount");
  mount.innerHTML = `
    <div class="svet-layout">
      <h3 id="svetTitle" class="svet-title"></h3>
      <p id="svetLead" class="svet-lead"></p>
      <a id="svetKingLink" class="svet-king-link" href="https://www.hero.yt/king/" target="_blank" rel="noreferrer noopener"></a>
      <div class="svet-exercises">
        <article class="svet-exercise">
          <h3 id="svetCarTitle"></h3>
          <p id="svetCarDesc"></p>
          <button id="svetCarButton" class="svet-button" type="button"></button>
          <div id="svetCarCountdown" class="svet-countdown" hidden></div>
          <div id="svetCarResult" class="svet-result"></div>
        </article>
        <article class="svet-exercise">
          <h3 id="svetEmotionTitle"></h3>
          <p id="svetEmotionDesc"></p>
          <button id="svetEmotionButton" class="svet-button" type="button"></button>
          <div id="svetEmotionCountdown" class="svet-countdown" hidden></div>
          <div id="svetEmotionResult" class="svet-result"></div>
        </article>
        <article class="svet-exercise">
          <h3 id="svetDiceTitle"></h3>
          <p id="svetDiceDesc"></p>
          <button id="svetDiceButton" class="svet-button" type="button"></button>
          <div id="svetDiceCountdown" class="svet-countdown" hidden></div>
          <div id="svetDiceResult" class="svet-result"></div>
        </article>
      </div>
      <footer class="svet-legal">
        <div class="svet-legal-actions">
          <button id="svetPrivacyButton" class="tool-link" type="button"></button>
          <button id="svetTermsButton" class="tool-link" type="button"></button>
        </div>
        <p id="svetLegalText" class="svet-legal-text" hidden></p>
      </footer>
    </div>
  `;

  document.getElementById("svetCarButton").addEventListener("click", runSvetCar);
  document.getElementById("svetEmotionButton").addEventListener("click", runSvetEmotion);
  document.getElementById("svetDiceButton").addEventListener("click", runSvetDice);
  document.getElementById("svetPrivacyButton").addEventListener("click", () => toggleSvetLegal("privacy"));
  document.getElementById("svetTermsButton").addEventListener("click", () => toggleSvetLegal("terms"));
}

function renderSvetText() {
  const svet = t("svet");
  document.getElementById("svetTitle").textContent = svet.title;
  document.getElementById("svetLead").textContent = svet.lead;
  document.getElementById("svetKingLink").textContent = svet.kingLink;
  document.getElementById("svetCarTitle").textContent = svet.carTitle;
  document.getElementById("svetCarDesc").textContent = svet.carDesc;
  document.getElementById("svetCarButton").textContent = svet.form;
  document.getElementById("svetEmotionTitle").textContent = svet.emotionTitle;
  document.getElementById("svetEmotionDesc").textContent = svet.emotionDesc;
  document.getElementById("svetEmotionButton").textContent = svet.answer;
  document.getElementById("svetDiceTitle").textContent = svet.diceTitle;
  document.getElementById("svetDiceDesc").textContent = svet.diceDesc;
  document.getElementById("svetDiceButton").textContent = svet.roll;
  document.getElementById("svetPrivacyButton").textContent = svet.privacy;
  document.getElementById("svetTermsButton").textContent = svet.terms;
  renderSvetLegalText();
}

function runSvetCar() {
  const svet = t("svet");
  runCountdown("svetCarCountdown", "svetCarResult", () => {
    const color = randomItem(svet.colors);
    const number = String(Math.floor(Math.random() * 1000)).padStart(3, "0");
    document.getElementById("svetCarResult").innerHTML = `${svet.color}: <strong>${color}</strong>. ${svet.number}: <strong>п${number}св</strong>`;
  });
}

function runSvetEmotion() {
  const svet = t("svet");
  runCountdown("svetEmotionCountdown", "svetEmotionResult", () => {
    document.getElementById("svetEmotionResult").innerHTML = `${svet.state}: <strong>${randomItem(svet.emotions)}</strong>`;
  });
}

function runSvetDice() {
  runCountdown("svetDiceCountdown", "svetDiceResult", () => {
    const dice = Array.from({ length: 5 }, () => diceArt[Math.floor(Math.random() * 6) + 1]);
    document.getElementById("svetDiceResult").innerHTML = `<span style="font-size: 3.4rem; line-height: 1;">${dice.join(" ")}</span>`;
  });
}

function runCountdown(countdownId, resultId, done) {
  const countdown = document.getElementById(countdownId);
  const result = document.getElementById(resultId);
  let value = 2;
  result.textContent = "";
  countdown.hidden = false;
  countdown.textContent = String(value);
  const interval = window.setInterval(() => {
    value -= 1;
    if (value > 0) {
      countdown.textContent = String(value);
      return;
    }
    window.clearInterval(interval);
    countdown.hidden = true;
    done();
  }, 1000);
}

function toggleSvetLegal(kind) {
  state.svetLegal = state.svetLegal === kind ? "" : kind;
  saveState();
  renderSvetLegalText();
}

function renderSvetLegalText() {
  const text = document.getElementById("svetLegalText");
  if (!state.svetLegal) {
    text.hidden = true;
    text.textContent = "";
    return;
  }
  text.hidden = false;
  text.textContent = state.svetLegal === "privacy" ? t("svet.privacyText") : t("svet.termsText");
}

function buildTm() {
  const mount = document.getElementById("tmMount");
  mount.innerHTML = `
    <div class="tm-layout">
      <div id="tmStartScreen" class="tm-screen">
        <h3 id="tmStartTitle"></h3>
        <p id="tmStartHint" class="tm-hint"></p>
        <button id="tmStartButton" class="tm-button" type="button"></button>
      </div>
      <div id="tmGameScreen" class="tm-screen" hidden>
        <h3 id="tmFocusTitle"></h3>
        <div class="tm-timer" aria-live="polite">
          <div class="flip-container">
            <div class="flip-digit" id="tmMinuteTens"><div class="digit-top" data-value="2"></div><div class="digit-bottom" data-value="2"></div></div>
            <div class="flip-digit" id="tmMinuteOnes"><div class="digit-top" data-value="5"></div><div class="digit-bottom" data-value="5"></div></div>
          </div>
          <span id="tmSeparator" class="timer-separator">:</span>
          <div class="flip-container">
            <div class="flip-digit" id="tmSecondTens"><div class="digit-top" data-value="0"></div><div class="digit-bottom" data-value="0"></div></div>
            <div class="flip-digit" id="tmSecondOnes"><div class="digit-top" data-value="0"></div><div class="digit-bottom" data-value="0"></div></div>
          </div>
        </div>
        <button id="tmStopButton" class="tm-button" type="button"></button>
        <div class="tm-form">
          <h3 id="tmSummaryTitle"></h3>
          <textarea id="tmComment"></textarea>
          <input id="tmDistractions" type="number" min="0" inputmode="numeric">
        </div>
        <article id="tmCard" class="tm-card">
          <img id="tmCardImage" src="" alt="Session image">
          <div class="tm-card-content">
            <div class="tm-card-info">
              <p id="tmCardComment" class="tm-card-comment"></p>
              <p id="tmSessionTime"></p>
              <p id="tmCardDate"></p>
              <p id="tmCardTime"></p>
              <p id="tmCardDistractions"></p>
            </div>
            <div class="tm-stamp"><span id="tmStampText"></span></div>
          </div>
        </article>
      </div>
    </div>
  `;

  document.getElementById("tmStartButton").addEventListener("click", startTmTimer);
  document.getElementById("tmStopButton").addEventListener("click", () => stopTmTimer(false));
  document.getElementById("tmComment").addEventListener("input", (event) => {
    state.tm.comment = event.target.value;
    saveState();
  });
  document.getElementById("tmDistractions").addEventListener("input", (event) => {
    state.tm.distractions = event.target.value;
    saveState();
  });
}

function renderTmText() {
  const tm = t("tm");
  document.getElementById("tmStartTitle").textContent = tm.startTitle;
  document.getElementById("tmStartHint").textContent = tm.startHint;
  document.getElementById("tmStartButton").textContent = tm.start;
  document.getElementById("tmFocusTitle").textContent = tm.focusTitle;
  document.getElementById("tmStopButton").textContent = tm.stop;
  document.getElementById("tmSummaryTitle").textContent = tm.summary;
  document.getElementById("tmComment").placeholder = tm.comment;
  document.getElementById("tmDistractions").placeholder = tm.distractions;
  document.getElementById("tmComment").value = state.tm.comment;
  document.getElementById("tmDistractions").value = state.tm.distractions;
  renderTmScreen();
  renderTmCard();
}

function startTmTimer() {
  window.clearInterval(tmTimer);
  state.tm.running = true;
  state.tm.startedAt = Date.now();
  state.tm.durationSeconds = TIMER_MINUTES * 60;
  state.tm.timeLeft = TIMER_MINUTES * 60;
  state.tm.cardVisible = false;
  state.tm.card = null;
  lastTmDigits = {};
  saveState();
  renderTmScreen();
  updateTmTimer();
  tmTimer = window.setInterval(updateTmTimer, 1000);
}

function resumeTimerIfNeeded() {
  renderTmScreen();
  if (state.tm.running) {
    updateTmTimer();
    tmTimer = window.setInterval(updateTmTimer, 1000);
  } else {
    setTmDigits(getTmTimeLeft());
  }
}

function getTmTimeLeft() {
  if (!state.tm.running || !state.tm.startedAt) {
    return Math.max(0, Number(state.tm.timeLeft) || 0);
  }
  const elapsed = Math.floor((Date.now() - state.tm.startedAt) / 1000);
  return Math.max(0, state.tm.durationSeconds - elapsed);
}

function updateTmTimer() {
  const left = getTmTimeLeft();
  state.tm.timeLeft = left;
  setTmDigits(left);
  updateDocumentTitle();
  if (left <= 0) {
    stopTmTimer(true);
    return;
  }
  saveState();
}

function stopTmTimer(completed) {
  window.clearInterval(tmTimer);
  tmTimer = null;
  state.tm.timeLeft = completed ? 0 : getTmTimeLeft();
  state.tm.running = false;
  const now = new Date();
  const startedAt = state.tm.startedAt ? new Date(state.tm.startedAt) : now;
  const elapsedSeconds = Math.max(1, state.tm.durationSeconds - state.tm.timeLeft);
  const focusMinutes = Math.max(1, Math.round(elapsedSeconds / 60));
  const distractions = state.tm.distractions.trim() || "0";
  const distractionsNumber = Number.parseInt(distractions, 10) || 0;
  state.tm.cardVisible = true;
  state.tm.card = {
    comment: state.tm.comment.trim() || t("tm.taskCompleted"),
    focusMinutes,
    date: now.toLocaleDateString(),
    time: `${formatTime(startedAt)} - ${formatTime(now)}`,
    distractions,
    stamp: distractionsNumber <= 1 ? t("tm.veryGood") : t("tm.good"),
    image: randomItem(tmImages),
  };
  saveState();
  renderTmScreen();
  renderTmCard();
  updateDocumentTitle();
}

function setTmDigits(totalSeconds) {
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  const values = {
    tmMinuteTens: String(Math.floor(minutes / 10)),
    tmMinuteOnes: String(minutes % 10),
    tmSecondTens: String(Math.floor(seconds / 10)),
    tmSecondOnes: String(seconds % 10),
  };
  Object.entries(values).forEach(([id, value]) => updateDigit(id, value));
}

function updateDigit(id, value) {
  const digit = document.getElementById(id);
  if (!digit) return;
  const top = digit.querySelector(".digit-top");
  const bottom = digit.querySelector(".digit-bottom");
  const previous = top.getAttribute("data-value");
  if (previous === value && lastTmDigits[id] === value) return;
  lastTmDigits[id] = value;
  if (previous !== value) {
    const existing = digit.querySelector(".flip-card");
    if (existing) existing.remove();
    const flip = document.createElement("div");
    flip.className = "flip-card";
    flip.innerHTML = `<div class="flip-card-front" data-value="${previous}"></div><div class="flip-card-back" data-value="${value}"></div>`;
    digit.appendChild(flip);
    top.setAttribute("data-value", value);
    requestAnimationFrame(() => flip.classList.add("flipping"));
    window.setTimeout(() => bottom.setAttribute("data-value", value), 280);
    window.setTimeout(() => flip.remove(), 620);
    return;
  }
  top.setAttribute("data-value", value);
  bottom.setAttribute("data-value", value);
}

function renderTmScreen() {
  const gameVisible = state.tm.running || state.tm.cardVisible || state.tm.startedAt;
  document.getElementById("tmStartScreen").hidden = gameVisible;
  document.getElementById("tmGameScreen").hidden = !gameVisible;
  document.getElementById("tmSeparator").classList.toggle("stopped", !state.tm.running);
  setTmDigits(getTmTimeLeft());
}

function renderTmCard() {
  const card = document.getElementById("tmCard");
  card.classList.toggle("is-visible", Boolean(state.tm.cardVisible && state.tm.card));
  if (!state.tm.card) return;
  const tm = t("tm");
  document.getElementById("tmCardImage").src = state.tm.card.image;
  document.getElementById("tmCardComment").textContent = state.tm.card.comment;
  document.getElementById("tmSessionTime").textContent = `✓ ${tm.focus}: ${state.tm.card.focusMinutes} min`;
  document.getElementById("tmCardDate").textContent = `Date: ${state.tm.card.date}`;
  document.getElementById("tmCardTime").textContent = `Time: ${state.tm.card.time}`;
  document.getElementById("tmCardDistractions").textContent = `${tm.distractionsLabel}: ${state.tm.card.distractions}`;
  document.getElementById("tmStampText").innerHTML = `${tm.done}<br>${tm.focused}<br><small>${state.tm.card.stamp}</small>`;
}

function updateDocumentTitle() {
  if (state.tm.running) {
    const left = getTmTimeLeft();
    const minutes = Math.floor(left / 60);
    const seconds = String(left % 60).padStart(2, "0");
    document.title = `${minutes}:${seconds} - ${t("tm.startTitle")}`;
    return;
  }
  document.title = t("documentTitle");
}

function randomItem(items) {
  return items[Math.floor(Math.random() * items.length)];
}

function formatTime(date) {
  return `${date.getHours()}:${String(date.getMinutes()).padStart(2, "0")}`;
}