:root {
  /* Telegram-supplied surface colors — these adapt to the user's actual
     Telegram theme (light/dark), applied at runtime in app.js. */
  --tg-bg: #ffffff;
  --tg-text: #1a1a2e;
  --tg-hint: #8a8a99;
  --tg-link: #1cb0f6;
  --tg-button: #58cc02;
  --tg-button-text: #ffffff;
  --tg-secondary-bg: #f7f7fb;

  /* Fixed brand colors — deliberately NOT theme-dependent, same idea as
     Duolingo's own UI: the green/red/gold accent colors stay constant
     whether the surrounding chrome is light or dark. */
  --c-green: #58cc02;
  --c-green-dark: #45a302;
  --c-red: #ff4b4b;
  --c-red-dark: #d43a3a;
  --c-blue: #1cb0f6;
  --c-blue-dark: #1899d6;
  --c-gold: #ffc800;
  --c-gold-dark: #e0ac00;
  --c-gray: #8a8a99;
  --c-gray-dark: #5c5c6b;

  --radius: 16px;
  --radius-sm: 10px;
}

* {
  box-sizing: border-box;
}

/* Mobile browsers draw a default blue tap-highlight/focus ring on <button>
   and <a> on press — a plain rectangle, so it visibly pokes out past the
   corners of every rounded button/pill/chip in this app the instant it's
   pressed, then vanishes on release. This app is touch-driven inside
   Telegram's WebView (no keyboard navigation to preserve an outline for),
   so it's turned off globally rather than patched per component. */
button, a {
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

/* Several classes below (.btn, .btn-icon, .badge, ...) set their own
   `display` unconditionally, which — being an author-stylesheet rule —
   silently overrides the browser's built-in `[hidden] { display: none }`
   default: toggling an element's `hidden` DOM property then does nothing
   visible at all on anything carrying one of those classes. `!important`
   here is deliberate and correct (this is MDN's own documented fix) —
   `hidden` must always mean "not displayed," full stop, regardless of
   whatever other classes happen to be on the same element. */
[hidden] {
  display: none !important;
}

/* ---- motion ----
   The app toggles most of its content via the `hidden` attribute rather
   than a JS animation library (see the rule above) — screens on nav,
   cards between reviews, reveal-on-tap translations, list re-renders.
   `display: none -> non-none` restarts a CSS animation on the element
   automatically (well-specified browser behavior, no JS involved), so
   giving each of those a keyframe animation is enough to turn every one
   of those snaps into a soft transition for free. fadeSlideIn is for
   things that replace a whole screen/section (feels like content
   arriving); fadeIn is for something appearing inside an already-settled
   container (the translation reveal, badges) where a vertical shift would
   look like the layout jumping instead of like motion. */
@keyframes fadeSlideIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

html, body {
  margin: 0;
  padding: 0;
  color: var(--tg-text);
  font-family: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* Plain solid background using Telegram's own theme color — this is what
   an actual Telegram chat looks like for the overwhelming majority of
   users (a solid or gradient fill from their chosen theme, no pattern),
   so matching that means NOT drawing anything of our own on top. --tg-bg
   already tracks the user's real theme live (see app.js's applyTheme),
   light or dark, so there's nothing else to do here. */
body {
  background-color: var(--tg-bg);
}

h2 {
  font-family: 'Baloo 2', 'Nunito', sans-serif;
  font-weight: 700;
  margin: 4px 0 16px;
  font-size: 22px;
}

#app {
  min-height: 100vh;
  padding: 16px 16px 84px;
}

.screen {
  max-width: 480px;
  margin: 0 auto;
  animation: fadeSlideIn .22s ease;
}

/* ---- generic bits ---- */

.status {
  text-align: center;
  color: var(--tg-hint);
  margin-top: 32px;
  font-size: 16px;
  font-weight: 600;
  animation: fadeSlideIn .25s ease;
}

.hint {
  color: var(--tg-hint);
  font-size: 14px;
  margin: 10px 2px;
  font-weight: 600;
}

.icon {
  width: 20px;
  height: 20px;
  display: inline-block;
  vertical-align: middle;
  flex-shrink: 0;
}

/* ---- learn screen ---- */

.topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
}

.streak-pill {
  display: flex;
  align-items: center;
  gap: 4px;
  background: var(--tg-secondary-bg);
  color: var(--c-gold-dark);
  font-weight: 800;
  font-family: 'Baloo 2', sans-serif;
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 16px;
  flex-shrink: 0;
}

.streak-pill .icon-flame {
  color: var(--c-gold);
}

.progress-track {
  flex: 1;
  height: 14px;
  background: var(--tg-secondary-bg);
  border-radius: 999px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  width: 0%;
  background: var(--c-blue);
  border-radius: 999px;
  transition: width .3s ease;
}

.mode-tabs {
  display: flex;
  gap: 4px;
  background: var(--tg-secondary-bg);
  border-radius: 999px;
  padding: 4px;
  margin-bottom: 14px;
}

.mode-tab {
  flex: 1;
  border: none;
  background: none;
  color: var(--tg-hint);
  font-size: 13px;
  font-weight: 700;
  padding: 8px 6px;
  border-radius: 999px;
  cursor: pointer;
  transition: background .15s ease, color .15s ease, transform .1s ease;
}

.mode-tab:active {
  transform: scale(.95);
}

.mode-tab.mode-tab-active {
  background: var(--c-blue);
  color: #fff;
}

.card {
  background: var(--tg-secondary-bg);
  border-radius: 20px;
  padding: 28px 22px;
  margin-top: 18px;
  animation: fadeSlideIn .25s ease;
}

.sentence-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 0 0 18px;
}

.sentence {
  flex: 1;
  font-size: 21px;
  line-height: 1.55;
  margin: 0;
  font-weight: 500;
}

.sentence strong {
  font-weight: 800;
  color: var(--c-blue);
}

.speak-btn {
  flex-shrink: 0;
  margin-top: 2px;
}

.translation {
  font-size: 17px;
  color: var(--tg-hint);
  margin: 0 0 18px;
  font-weight: 600;
  animation: fadeIn .2s ease;
}

.translation strong {
  font-weight: 800;
  color: var(--c-blue);
}

/* ---- answer buttons (Anki's four) ----
   The interval each button schedules is printed in its own row directly
   above the buttons, in the same four columns and with the same gap, so
   every label sits over the button it belongs to — the way Anki shows
   them. */
.answer-block {
  margin-top: 4px;
  animation: fadeIn .25s ease;
}

.answer-etas,
.answer-buttons {
  display: flex;
  gap: 6px;
}

.answer-eta {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  font-size: 12px;
  font-weight: 700;
  color: var(--tg-hint);
  white-space: nowrap;
}

.answer-buttons .btn {
  flex: 1 1 0;
  min-width: 0;
  /* Four buttons on one phone-width row: no icons, tighter padding, and a
     font size that survives "Переучивание"-length labels without wrapping. */
  padding: 13px 4px;
  font-size: 14px;
  margin-top: 2px;
}

.btn-gray {
  background: var(--c-gray);
  color: #fff;
  border-bottom-color: var(--c-gray-dark);
}

/* ---- buttons ---- */

.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 13px;
  border: none;
  border-radius: var(--radius);
  font-size: 15px;
  font-weight: 800;
  font-family: 'Baloo 2', 'Nunito', sans-serif;
  letter-spacing: .3px;
  cursor: pointer;
  margin-top: 10px;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

/* The "juicy" pressable-button look: a solid bottom border acts as a fake
   3D edge, which flattens on press — this is the single most recognizable
   piece of Duolingo's visual language. */
.btn-fat {
  border-bottom: 4px solid transparent;
  transition: transform .04s ease, border-bottom-width .04s ease, margin-top .04s ease;
}

.btn-fat:active {
  transform: translateY(2px);
  border-bottom-width: 0;
}

.btn-green {
  background: var(--c-green);
  color: #fff;
  border-bottom-color: var(--c-green-dark);
}

.btn-red {
  background: var(--c-red);
  color: #fff;
  border-bottom-color: var(--c-red-dark);
}

.btn-blue {
  background: var(--c-blue);
  color: #fff;
  border-bottom-color: var(--c-blue-dark);
}

.btn-outline {
  background: transparent;
  color: var(--tg-text);
  border: 2px solid var(--tg-secondary-bg);
}

.btn-outline-red {
  color: var(--c-red);
  border-color: var(--c-red);
  background: transparent;
}

.btn-icon {
  background: none;
  border: none;
  color: var(--tg-hint);
  cursor: pointer;
  padding: 6px;
  display: flex;
  align-items: center;
  border-radius: 999px;
  transition: background .1s ease, transform .1s ease;
}

.btn-icon:active {
  background: var(--tg-secondary-bg);
  transform: scale(.92);
}

/* ---- forms ---- */

textarea, input[type="email"], input[type="text"] {
  width: 100%;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--tg-secondary-bg);
  background: var(--tg-bg);
  color: var(--tg-text);
  font-size: 15px;
  font-family: inherit;
  font-weight: 600;
  margin-bottom: 8px;
}

textarea:focus, input[type="email"]:focus, input[type="text"]:focus {
  outline: none;
  border-color: var(--c-blue);
}

.inline-form {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 14px;
}

.inline-form input {
  margin-bottom: 0;
}

.btn-icon-primary {
  background: var(--c-blue);
  color: #fff;
  flex-shrink: 0;
}

.field {
  margin-bottom: 18px;
}

.field label {
  display: block;
  font-size: 13px;
  font-weight: 800;
  color: var(--tg-hint);
  text-transform: uppercase;
  letter-spacing: .5px;
  margin-bottom: 8px;
}

.field-hint {
  font-size: 13px;
  font-weight: 500;
  color: var(--tg-hint);
  margin: -4px 0 10px;
  line-height: 1.4;
}

/* ---- chips (settings pickers) ---- */

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  padding: 9px 16px;
  border-radius: 999px;
  border: 2px solid var(--tg-secondary-bg);
  background: var(--tg-secondary-bg);
  color: var(--tg-text);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease, transform .1s ease;
}

.chip:active {
  transform: scale(.95);
}

.chip-active {
  background: var(--c-blue);
  border-color: var(--c-blue);
  color: #fff;
}

/* ---- slider (settings: new words per day) ---- */

.slider-row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.slider {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 8px;
  border-radius: 999px;
  background: var(--tg-secondary-bg);
  outline: none;
  margin: 10px 0;
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--c-blue);
  border: 3px solid var(--tg-bg);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
  cursor: pointer;
}

.slider::-moz-range-thumb {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--c-blue);
  border: 3px solid var(--tg-bg);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
  cursor: pointer;
}

.slider-value {
  min-width: 40px;
  text-align: right;
  font-size: 18px;
  font-weight: 800;
  color: var(--c-blue);
  flex-shrink: 0;
}

/* ---- slider with 3 snap points (settings: sentence complexity) ---- */

.slider-ticks-wrap {
  position: relative;
  flex: 1;
  width: 100%;
}

/* Nested one level deeper than the new-words-per-day slider (which sits
   directly in a flex row, so its own `flex: 1` stretches it) — this one's
   parent is .slider-ticks-wrap, so `flex: 1` on the input itself has
   nothing to flex against and it fell back to the browser's cramped
   default range width. `width: 100%` makes it fill the wrapper instead. */
.slider-ticks-wrap .slider {
  width: 100%;
}

/* Ticks sit 13px in from each edge (half the 26px thumb, see .slider's
   thumb rules above) so they land exactly under the thumb's 3 resting
   positions instead of under the track's bare ends. */
.slider-ticks {
  position: absolute;
  left: 13px;
  right: 13px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  justify-content: space-between;
  pointer-events: none;
}

.slider-tick {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--tg-bg);
}

/* Same look as .slider-value (the number next to the new-words-per-day
   slider), just wide enough for a word + emoji ("🔴 Сложно") instead of a
   2-digit number. */
.slider-value-wide {
  min-width: 96px;
}

/* ---- lists ---- */

.list {
  margin-top: 8px;
}

.list-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 14px 4px;
  border-bottom: 2px solid var(--tg-secondary-bg);
  font-size: 15px;
  font-weight: 700;
  animation: fadeIn .2s ease;
}

.list-row:last-child {
  border-bottom: none;
}

.list-row-you {
  color: var(--c-blue);
}

.row-main {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.row-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 800;
  padding: 3px 9px;
  border-radius: 999px;
  flex-shrink: 0;
  text-transform: uppercase;
  letter-spacing: .3px;
  transition: background .15s ease, color .15s ease;
}

.badge-green {
  background: rgba(88, 204, 2, .18);
  color: var(--c-green-dark);
}

.badge-gray {
  background: rgba(138, 138, 153, .18);
  color: var(--c-gray-dark);
}

.badge-blue {
  background: rgba(28, 176, 246, .18);
  color: var(--c-blue-dark);
}

.card-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
  animation: fadeIn .2s ease;
}

.rank-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: var(--tg-secondary-bg);
  color: var(--tg-hint);
  font-weight: 800;
  font-size: 13px;
  flex-shrink: 0;
}

.rank-gold { background: rgba(255, 200, 0, .25); color: var(--c-gold-dark); }
.rank-silver { background: #e3e6ea; color: #8a8f98; }
.rank-bronze { background: rgba(205, 127, 50, .25); color: #a15a1f; }

/* toggle switch (topics on/off) */
.toggle {
  position: relative;
  width: 44px;
  height: 26px;
  border-radius: 999px;
  background: var(--tg-secondary-bg);
  border: none;
  cursor: pointer;
  flex-shrink: 0;
  transition: background .15s ease;
}

.toggle::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.25);
  transition: transform .15s ease;
}

.toggle-on {
  background: var(--c-blue);
}

.toggle-on::after {
  transform: translateX(18px);
}

/* ---- summary cards (progress, payment) ---- */

.summary-card {
  background: var(--tg-secondary-bg);
  border-radius: var(--radius);
  padding: 18px;
  margin-bottom: 16px;
  font-weight: 700;
  animation: fadeSlideIn .25s ease;
}

.summary-stats {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
}

.stat {
  display: flex;
  flex-direction: column;
}

.stat-value {
  font-family: 'Baloo 2', sans-serif;
  font-size: 22px;
  font-weight: 800;
}

.stat-label {
  font-size: 12px;
  color: var(--tg-hint);
  text-transform: uppercase;
  letter-spacing: .3px;
  font-weight: 700;
}

/* ---- pager ---- */

.pager {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 16px;
  margin-top: 16px;
}

.pager button {
  background: var(--tg-secondary-bg);
  border: none;
  border-radius: 999px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--tg-text);
  cursor: pointer;
  transition: opacity .15s ease, transform .1s ease;
}

.pager button:active:not(:disabled) {
  transform: scale(.9);
}

.pager button:disabled {
  opacity: .35;
}

.pager span {
  font-weight: 800;
  color: var(--tg-hint);
  font-size: 14px;
}

/* ---- manage screen: set list <-> set detail sub-views ---- */

.detail-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.detail-header h2 {
  margin: 0;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- settings -> support link row ---- */

.menu-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 4px;
  margin-top: 8px;
  color: var(--tg-text);
  text-decoration: none;
  font-weight: 700;
  border-top: 2px solid var(--tg-secondary-bg);
}

.menu-row span {
  flex: 1;
}

.icon-chevron {
  color: var(--tg-hint);
  width: 18px;
  height: 18px;
}

/* ---- bottom nav ---- */

#bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: space-around;
  background: var(--tg-bg);
  padding: 8px 2px calc(8px + env(safe-area-inset-bottom));
  border-top: 2px solid var(--tg-secondary-bg);
}

/* The brand only appears once the nav is a sidebar — on a phone the tab bar
   is at the bottom and Telegram already puts the app's name in its own
   header above the WebView. */
.nav-brand {
  display: none;
}

#bottom-nav a {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  text-decoration: none;
  color: var(--tg-hint);
  /* Seven tabs, not six (Практика joined them): on a 360px phone that's
     ~51px per tab, so the label has to be a notch smaller and can't be
     allowed to wrap — a wrapped label would grow the whole bar's height on
     one screen only. */
  font-size: 9px;
  font-weight: 800;
  padding: 4px 2px;
  border-radius: 12px;
  flex: 1;
  min-width: 0;
  transition: color .15s ease, transform .1s ease;
}

#bottom-nav a span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#bottom-nav a:active {
  transform: scale(.92);
}

#bottom-nav a.active {
  color: var(--c-blue);
}

.chart-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--tg-hint);
  margin-bottom: 10px;
}

.chart-legend {
  display: flex;
  gap: 16px;
  margin-bottom: 16px;
}

.chart-legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--tg-hint);
  font-weight: 600;
}

.chart-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  display: inline-block;
}

.chart-dot-reviewed {
  background: var(--c-gray);
}

.chart-dot-learned {
  background: var(--c-blue);
}

.chart-bars {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 100px;
}

.chart-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 100%;
}

.chart-col-bars {
  flex: 1;
  width: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 3px;
}

.chart-bar {
  width: 8px;
  min-height: 2px;
  border-radius: 4px 4px 0 0;
  transform-origin: bottom;
  animation: growUp .35s ease;
}

@keyframes growUp {
  from { transform: scaleY(0); }
  to { transform: scaleY(1); }
}

.chart-bar-reviewed {
  background: var(--c-gray);
}

.chart-bar-learned {
  background: var(--c-blue);
}

.chart-col-label {
  margin-top: 8px;
  font-size: 11px;
  font-weight: 700;
  color: var(--tg-hint);
}

/* ---- loading placeholders ----
   See app.js's showSkeleton/showSpinner for why these exist at all: every
   screen paints only once its request lands, and an empty container during
   that window reads as "there is nothing here", not as "loading".

   The pulse is an opacity animation on the app's own secondary surface
   rather than the usual left-to-right shimmer gradient. A shimmer needs a
   highlight color a shade off the base, and the base here is
   --tg-secondary-bg, i.e. whatever the user's Telegram theme happens to be
   — any fixed highlight would be invisible in one theme or glaring in the
   other. Opacity works against both. */
@keyframes skeletonPulse {
  0%, 100% { opacity: .5; }
  50% { opacity: 1; }
}

.skeleton {
  background: var(--tg-secondary-bg);
  border-radius: var(--radius-sm);
  animation: skeletonPulse 1.2s ease-in-out infinite;
}

.skeleton-row {
  height: 52px;
  margin-bottom: 8px;
}

.skeleton-chart {
  height: 168px;
  border-radius: 20px;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 3px solid var(--tg-secondary-bg);
  border-top-color: var(--c-blue);
  animation: spin .8s linear infinite;
}

.loading-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 26px 0;
  color: var(--tg-hint);
  font-size: 14px;
  font-weight: 700;
}

/* The "try again" button under a failed load — narrower than a normal
   full-width .btn, since it sits inside a centered status message. */
.btn-retry {
  width: auto;
  min-width: 140px;
  margin: 14px auto 0;
  padding: 9px 18px;
}

/* ---- screen layout wrappers ----
   Both are inert on a phone: .screen-main is a plain block and the document
   reads top to bottom exactly as it did before they were introduced. They
   only mean something from the desktop breakpoint down the file, where
   .screen becomes a grid and the rail moves into its own column.

   .screen-side-extra marks a rail that carries nothing the mobile layout
   doesn't already show elsewhere (learn's stats duplicate the topbar), so
   it's simply absent until there's room for it. A rail without that class
   holds real content and stays in the normal flow on a phone. */
.screen-side-extra {
  display: none;
}

.side-card-title {
  display: none;
}

.side-card-web {
  display: none;
}

/* ---- topic groups (manage screen) ----
   A parent row plus its sublists. Collapsed by default, which is why the
   caret starts rotated: one icon, two states, no second sprite entry. */
.topic-group-head {
  background: var(--tg-secondary-bg);
  border-radius: var(--radius-sm);
  border-bottom: none;
  padding: 12px 12px;
  margin-top: 4px;
}

.topic-group-toggle {
  flex: 1;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.icon-caret {
  width: 18px;
  height: 18px;
  color: var(--tg-hint);
  transform: rotate(-90deg);
  transition: transform .18s ease;
}

.topic-group-open .icon-caret {
  transform: rotate(0deg);
}

/* The children are indented behind a rail so it's obvious where the group
   ends, without needing a second background color for them. */
.topic-children {
  margin: 0 0 4px 12px;
  padding-left: 12px;
  border-left: 2px solid var(--tg-secondary-bg);
  animation: fadeIn .18s ease;
}

/* A group's switch has three states, not two: children can also be flipped
   individually, so "some of them are on" has to look different from both
   "all" and "none" — otherwise one enabled sublist is indistinguishable
   from an entirely disabled group. */
.toggle-label {
  display: none;
}

.toggle-partial {
  background: rgba(28, 176, 246, .45);
}

.toggle-partial::after {
  transform: translateX(9px);
}

/* ---- exercises screen ----
   Rendered from the list in screens/exercises.js. The grid is declared
   here rather than in the desktop block because it collapses to a single
   column on its own on a phone — one rule, both layouts. */
.screen-lead {
  font-size: 14px;
  font-weight: 600;
  color: var(--tg-hint);
  line-height: 1.5;
  margin: -8px 0 18px;
  max-width: 660px;
}

.exercise-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: 12px;
}

.exercise-card {
  display: flex;
  align-items: stretch;
  gap: 14px;
  text-align: left;
  background: var(--tg-secondary-bg);
  border: none;
  border-radius: var(--radius);
  padding: 18px;
  font-family: inherit;
  color: var(--tg-text);
  cursor: pointer;
  animation: fadeSlideIn .22s ease;
  transition: transform .1s ease, box-shadow .15s ease;
}

.exercise-card:active {
  transform: scale(.98);
}

.exercise-icon {
  display: flex;
  align-items: center;
  align-self: flex-start;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  background: var(--tg-bg);
  color: var(--c-blue);
  flex-shrink: 0;
}

.exercise-card-soon .exercise-icon {
  color: var(--tg-hint);
}

.exercise-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  min-width: 0;
  flex: 1;
}

.exercise-title {
  font-family: 'Baloo 2', 'Nunito', sans-serif;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.25;
}

/* Pushed to the bottom of the card so the marker lines up across a row of
   cards whose titles and blurbs are different lengths. */
.exercise-badge {
  margin-top: auto;
  padding-top: 3px;
}

.exercise-text {
  font-size: 13px;
  font-weight: 600;
  color: var(--tg-hint);
  line-height: 1.45;
}

/* ---- back link ----
   Shown on every screen reached from the exercise list. Inside Telegram the
   client's own back button does the same job, but it is a chrome affordance
   at the top of the window rather than part of the page, and on the website
   there is no back button at all — so the link stays in both. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  font-weight: 700;
  color: var(--tg-hint);
  text-decoration: none;
  margin-bottom: 12px;
}

.back-link .icon {
  width: 16px;
  height: 16px;
}

/* ---- exercise setup screen (see screens/setup.js) ----
   The step before an exercise: what it does, then the settings for it. */
.setup-how {
  background: var(--tg-secondary-bg);
  border-radius: var(--radius);
  padding: 16px 18px;
  margin-bottom: 22px;
}

.setup-how-title {
  font-size: 12px;
  font-weight: 800;
  color: var(--tg-hint);
  text-transform: uppercase;
  letter-spacing: .5px;
  margin-bottom: 10px;
}

.setup-steps {
  margin: 0;
  padding-left: 20px;
}

.setup-steps li {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
  margin-bottom: 7px;
}

.setup-steps li:last-child {
  margin-bottom: 0;
}

.setup-start {
  width: 100%;
  margin-top: 4px;
}

/* ---- plain flashcard ----
   Nothing but a word on each side, so it gets the size a sentence would
   have taken instead of floating in a card built for one. */
.card-plain .sentence {
  font-size: 30px;
  text-align: center;
}

.card-plain .translation {
  font-size: 22px;
  text-align: center;
}

/* The word takes only the room it needs so the speaker button sits beside
   it, instead of being pushed to the far edge of the card by a flex:1
   paragraph that is one word long. */
.card-plain .sentence-row {
  justify-content: center;
  align-items: center;
}

.card-plain .sentence {
  flex: 0 1 auto;
}

/* ---- text exercise (see screens/text.js) ---- */
.text-reader {
  animation: fadeSlideIn .25s ease;
}

.text-title {
  font-family: 'Baloo 2', 'Nunito', sans-serif;
  font-size: 19px;
  font-weight: 700;
  margin: 0 0 12px;
}

.text-content {
  font-size: 17px;
  line-height: 1.75;
  margin: 0;
  /* The text is one long paragraph of prose; letting it hyphenate keeps the
     ragged edge from tearing open around the highlighted words. */
  overflow-wrap: break-word;
}

/* A target word: a button, because it is one — everything else in the text
   is a plain text node and cannot be tapped at all (see renderSegments). */
.text-target {
  font: inherit;
  font-weight: 800;
  color: var(--tg-text);
  background: rgba(28, 176, 246, .16);
  border: none;
  border-bottom: 2px solid var(--c-blue);
  border-radius: 5px;
  padding: 1px 3px;
  margin: 0 -1px;
  cursor: pointer;
}

/* New words are marked apart from words being revisited: meeting a word for
   the very first time inside a text is a different act from recognising one
   you have seen before, and the reader deserves the warning. */
.text-target-new {
  background: rgba(255, 200, 0, .22);
  border-bottom-color: var(--c-gold);
}

.text-target-done {
  background: none;
  color: var(--tg-hint);
  border-bottom-color: transparent;
  font-weight: 600;
}

.text-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
}

.text-actions .hint {
  margin: 0;
}

/* .btn is full-width by default (it is usually the only thing on its row);
   here it shares a row with the progress line, so it sizes to its label and
   lets the counter have the rest. */
.text-actions .btn {
  width: auto;
  flex: 0 0 auto;
  margin-top: 0;
}

.text-word-row {
  width: 100%;
  background: none;
  border: none;
  border-bottom: 2px solid var(--tg-secondary-bg);
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/* Unlike learn's stats rail (which only repeats what the topbar already
   shows, and so is hidden below the desktop breakpoint), this one carries
   content that exists nowhere else — the list of words in the text. On a
   phone it simply follows the text down the page, and it keeps its heading
   at every width so it doesn't read as a stray list. */
#screen-text .side-card {
  margin-top: 20px;
}

#screen-text .side-card-title {
  display: block;
  font-size: 12px;
  font-weight: 800;
  color: var(--tg-hint);
  text-transform: uppercase;
  letter-spacing: .5px;
  margin-bottom: 6px;
}

/* ---- the tapped-word panel ----
   A bottom sheet: the text stays visible above it, which is the point —
   the word is being answered in its context, not on a card of its own. */
.word-panel {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, .45);
  animation: fadeIn .15s ease;
}

.word-panel-card {
  position: relative;
  width: 100%;
  max-width: 520px;
  background: var(--tg-bg);
  border-radius: 20px 20px 0 0;
  padding: 22px 18px calc(18px + env(safe-area-inset-bottom));
  animation: sheetUp .2s ease;
}

@keyframes sheetUp {
  from { transform: translateY(24px); }
  to { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .word-panel-card {
    animation: none;
  }
}

.word-panel-close {
  position: absolute;
  top: 10px;
  right: 10px;
}

.word-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-right: 36px;
}

.word-panel-word {
  font-family: 'Baloo 2', 'Nunito', sans-serif;
  font-size: 26px;
  font-weight: 700;
}

.word-panel-translation {
  font-size: 18px;
  font-weight: 600;
  margin: 10px 0 16px;
}

/* ---- desktop / website layout ----
   The website and the Mini App are the same document; what differs is the
   room available and the chrome around it. Everything below is additive:
   on a phone (and inside Telegram) nothing here applies and the layout is
   exactly what it always was.

   `.is-web` is set on <html> by app.js when there is no Telegram WebApp
   around us (see its bootstrap), so browser-only chrome — the brand header,
   the sign-out button — never shows up inside Telegram, where Telegram
   already provides its own. */

.web-header {
  display: none;
}

.is-web .web-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  max-width: 480px;
  margin: 0 auto 12px;
}

.web-brand {
  font-family: 'Baloo 2', 'Nunito', sans-serif;
  font-weight: 800;
  font-size: 18px;
  color: var(--tg-text);
}

.web-logout {
  background: none;
  border: none;
  color: var(--tg-hint);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 999px;
  font-family: inherit;
}

.web-logout:hover {
  background: var(--tg-secondary-bg);
  color: var(--tg-text);
}

/* A pointer means hover states are worth having: on a phone they either do
   nothing or stick after a tap, which is why they're gated this way rather
   than by width. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    filter: brightness(1.06);
  }

  .mode-tab:hover:not(.mode-tab-active) {
    color: var(--tg-text);
  }

  #bottom-nav a:hover {
    color: var(--tg-text);
  }

  .btn-icon:hover {
    background: var(--tg-secondary-bg);
    color: var(--tg-text);
  }

  .chip:hover:not(.chip-active) {
    border-color: var(--c-blue);
  }

  .toggle:hover {
    filter: brightness(.96);
  }

  .pager button:hover:not(:disabled) {
    color: var(--c-blue);
  }

  .menu-row:hover {
    color: var(--c-blue);
  }

  /* Card-shaped rows and tiles lift slightly. Deliberately only the
     card-grid variants: a plain bordered list row has no edge to lift. */
  .list-cards .list-row:hover,
  .exercise-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 18px rgba(0, 0, 0, .09);
  }

  .topic-group-toggle:hover .row-text {
    color: var(--c-blue);
  }
}

/* ---- tier 1: the tab bar becomes a sidebar ----
   From here up, the bottom tab bar becomes a left sidebar and the content
   gets the width a desktop screen actually has. The nav keeps its id and
   markup — router.js highlights `#bottom-nav a` and knows nothing about
   which shape it currently has.

   The other half of the job is what the freed-up width is spent on. A
   single 620px column centred in a 1900px window (what this used to be)
   spends it on nothing: the app looked like a phone screenshot pasted onto
   a blank page. So every list here becomes a card grid that fills the row,
   settings becomes two columns, and the screens with a natural sidebar of
   their own get one at tier 2 below. */
@media (min-width: 900px) {
  #app {
    padding: 32px 40px 40px 256px;
  }

  #bottom-nav {
    top: 0;
    bottom: 0;
    right: auto;
    width: 216px;
    flex-direction: column;
    justify-content: flex-start;
    gap: 4px;
    padding: 20px 16px;
    border-top: none;
    border-right: 2px solid var(--tg-secondary-bg);
  }

  .nav-brand {
    display: block;
    font-family: 'Baloo 2', 'Nunito', sans-serif;
    font-weight: 800;
    font-size: 19px;
    color: var(--tg-text);
    padding: 8px 14px 18px;
  }

  #bottom-nav a {
    flex: 0 0 auto;
    flex-direction: row;
    justify-content: flex-start;
    gap: 12px;
    width: 100%;
    font-size: 15px;
    padding: 11px 14px;
    border-radius: 12px;
  }

  #bottom-nav a .icon {
    width: 20px;
    height: 20px;
  }

  #bottom-nav a.active {
    background: var(--tg-secondary-bg);
  }

  #bottom-nav a:active {
    transform: none;
  }

  /* Per-screen widths: a screen is as wide as its content can actually use.
     Lists and grids take the whole area; a lone form (support, payment)
     stays a form and is centred rather than stretched into a banner. */
  .screen {
    max-width: 1180px;
    margin: 0 auto;
  }

  #screen-manage {
    max-width: 1600px;
  }

  #screen-support,
  #screen-payment {
    max-width: 660px;
  }

  #screen-settings {
    max-width: 1000px;
  }

  #screen-exercises {
    max-width: 1400px;
  }

  /* A setup screen is a short explanation and a few rows of chips: it reads
     as a form, so it is centred at a form's width rather than stretched. */
  #screen-setup {
    max-width: 720px;
  }

  .setup-start {
    width: auto;
    min-width: 220px;
  }

  /* The sidebar carries the brand once it exists, so the page header keeps
     only the thing the sidebar doesn't have — the sign-out button. */
  .is-web .web-header {
    max-width: none;
    margin: 0 0 4px;
    justify-content: flex-end;
  }

  .web-brand {
    display: none;
  }

  h2 {
    font-size: 26px;
    margin-bottom: 20px;
  }

  .card {
    padding: 32px 28px;
  }

  .sentence {
    font-size: 23px;
  }

  /* Room for the keyboard hints under the answer buttons. */
  .answer-buttons .btn {
    font-size: 15px;
    padding: 15px 6px;
  }

  /* A title and the form that feeds the list below it share one line once
     there's room; on a phone .screen-head is a plain block. */
  .screen-head {
    display: flex;
    align-items: center;
    gap: 20px;
    margin-bottom: 12px;
  }

  .screen-head h2 {
    margin: 0;
  }

  .screen-head .inline-form {
    margin: 0 0 0 auto;
    width: 360px;
  }

  /* Lists become card grids. One long column down the left of a wide
     window was the single biggest source of the empty space this whole
     block exists to fix — three set cards per row use it instead. */
  .list-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
    gap: 10px;
    align-items: start;
  }

  .list-cards .list-row {
    background: var(--tg-secondary-bg);
    border-bottom: none;
    border-radius: var(--radius);
    padding: 14px 16px;
    transition: transform .12s ease, box-shadow .15s ease;
  }

  /* In a fixed-width card the name is what should absorb the slack, and the
     controls are what should never move: without this both halves size to
     their content and every set ends up called "Oxford 10…". */
  .list-cards .list-row > .row-main:first-child {
    flex: 1;
    min-width: 0;
  }

  .list-cards .list-row > .row-main + .row-main {
    flex-shrink: 0;
  }

  /* Messages and empty states are not cards — they span the whole grid. */
  .list-cards .status,
  .list-cards .hint {
    grid-column: 1 / -1;
  }

  /* A word list is one short word per row, so it packs much tighter than the
     set list, whose rows carry a name, a counter and four controls. */
  #manage-own-list {
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  }

  /* The set list goes the other way: since the reset button joined the row,
     360px left the name column narrow enough to clip "Свои слова" itself. */
  #topics-list {
    grid-template-columns: repeat(auto-fill, minmax(410px, 1fr));
  }

  /* An expanded group takes the full row and lays its children out as their
     own grid inside it, so opening one never leaves a tall column next to
     two short ones. Collapsed, it's just another card in the flow. */
  .list-cards .topic-group-open {
    grid-column: 1 / -1;
  }

  .list-cards .topic-group-head {
    margin-top: 0;
    padding: 16px;
  }

  .list-cards .topic-children {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 10px;
    margin: 10px 0 4px 22px;
    padding: 0 0 0 18px;
  }

  /* Only while the group is open: expanded, its header is a full-width band
     where the switch ends up far from the title and needs the caption. A
     collapsed group is an ordinary card in the grid, where those same two
     words would eat the room the group's name needs. */
  .list-cards .topic-group-open .toggle-label {
    display: inline;
    font-size: 12px;
    font-weight: 800;
    color: var(--tg-hint);
    text-transform: uppercase;
    letter-spacing: .4px;
  }

  .list-cards .topic-children .list-row {
    background: var(--tg-bg);
    border: 2px solid var(--tg-secondary-bg);
  }

  /* Settings: two columns of fields instead of one tall stack. The support
     link stays a full-width row at the bottom, where it reads as "leaving
     this screen" rather than as another setting. */
  #settings-body {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: 4px 44px;
    align-items: start;
  }

  #settings-body .field {
    margin-bottom: 22px;
  }

  #settings-body .menu-row {
    grid-column: 1 / -1;
  }

  .exercise-grid {
    grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
    gap: 14px;
  }

  .exercise-card {
    min-height: 200px;
    padding: 22px;
  }

  /* The weekly chart is drawn for a phone: 8px bars 100px tall. Given a
     desktop column it should actually be readable, not the same postage
     stamp with more air around it. */
  #progress-chart .chart-bars {
    height: 190px;
    gap: 14px;
  }

  #progress-chart .chart-col-bars {
    gap: 8px;
  }

  #progress-chart .chart-bar {
    width: 22px;
    border-radius: 6px 6px 0 0;
  }

  #progress-chart .chart-title {
    font-size: 16px;
  }

  #progress-chart .chart-col-label {
    font-size: 13px;
  }

  /* Three headline numbers, three equal columns across the card, instead of
     all three huddled against its left edge. */
  #progress-summary {
    padding: 24px;
  }

  #progress-summary .summary-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
  }

  #progress-summary .stat-value {
    font-size: 30px;
  }
}

/* ---- tier 2: side rails ----
   Wide enough for a second column beside the main one: ~600px of content,
   a ~320px rail, the sidebar and the gutters. Below this the rails either
   fall back into the normal flow (progress's topic list) or disappear
   entirely (learn's stats, which the mobile topbar already shows). */
@media (min-width: 1200px) {
  #screen-learn,
  #screen-text,
  #screen-progress {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 28px;
    align-items: start;
  }

  #screen-text {
    max-width: 1120px;
  }

  /* In its own column the rail starts level with the text, and its heading
     gets the same breathing room every other rail card's does. */
  #screen-text .side-card {
    margin-top: 0;
  }

  #screen-text .side-card-title {
    margin-bottom: 14px;
  }

  /* The panel is a bottom sheet on a phone, where it belongs against the
     thumb. On a desktop that same sheet would sit a mile from the word just
     clicked, so it becomes a centred dialog instead. */
  .word-panel {
    align-items: center;
  }

  .word-panel-card {
    border-radius: 20px;
    padding: 26px 24px;
  }

  /* One flashcard is short, and pinned to the top of a 1000px-tall window
     it leaves the same emptiness underneath that the narrow column left
     beside it. Centring the row puts the card where the eye already is. */
  #screen-learn {
    max-width: 1080px;
    min-height: calc(100vh - 140px);
    align-content: center;
  }

  #screen-progress {
    max-width: 1120px;
  }

  /* The rail's own heading — hidden on a phone, where the same content is
     just the next thing down the page and needs no label. */
  .screen-side-extra {
    display: block;
  }

  .is-web .side-card-web {
    display: block;
  }

  .side-card {
    background: var(--tg-secondary-bg);
    border-radius: var(--radius);
    padding: 20px;
    margin-bottom: 14px;
    animation: fadeSlideIn .25s ease;
  }

  .side-card-title {
    display: block;
    font-size: 12px;
    font-weight: 800;
    color: var(--tg-hint);
    text-transform: uppercase;
    letter-spacing: .5px;
    margin-bottom: 14px;
  }

  .side-stats {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  .stat-value-gold {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--c-gold-dark);
  }

  .stat-value-gold .icon {
    color: var(--c-gold);
  }

  .side-progress {
    margin-top: 18px;
  }

  /* Rows inside a rail sit on the card's own surface, so their separator
     can't be --tg-secondary-bg any more — it would be invisible. */
  .side-card .list-row {
    padding: 11px 0;
    border-bottom: 2px solid var(--tg-bg);
    font-size: 14px;
  }

  .side-card .list-row:last-child {
    border-bottom: none;
  }

  .side-card .hint {
    margin: 12px 0 0;
    font-size: 13px;
  }

  .key-hint {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    font-weight: 600;
    color: var(--tg-hint);
    margin-bottom: 9px;
  }

  .key-hint:last-child {
    margin-bottom: 0;
  }

  .key-hint kbd {
    font-family: inherit;
    font-size: 12px;
    font-weight: 800;
    color: var(--tg-text);
    background: var(--tg-bg);
    border-radius: 7px;
    padding: 4px 9px;
    min-width: 26px;
    text-align: center;
  }
}

/* Keyboard hints: only meaningful where there is a keyboard, and only shown
   on the website (inside Telegram the card is driven by taps). */
.answer-key {
  display: none;
}

.is-web .answer-key {
  display: block;
  font-size: 11px;
  font-weight: 700;
  opacity: .75;
  margin-top: 2px;
}

.is-web .reveal-hint {
  display: block;
  text-align: center;
  color: var(--tg-hint);
  font-size: 12px;
  font-weight: 600;
  margin-top: 8px;
}

.reveal-hint {
  display: none;
}

/* ---- login page (see webapp_site.go's loginPage) ---- */

.login-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
  font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  color: var(--tg-text);
}

.login-card {
  max-width: 420px;
  text-align: center;
  background: var(--tg-secondary-bg);
  border-radius: 20px;
  padding: 36px 28px;
}

.login-card h1 {
  font-family: 'Baloo 2', 'Nunito', sans-serif;
  font-size: 30px;
  margin: 0 0 12px;
}

.login-lead {
  color: var(--tg-text);
  font-size: 15px;
  line-height: 1.5;
  margin: 0 0 24px;
}

.login-widget {
  display: flex;
  justify-content: center;
  min-height: 48px;
}

.login-note,
.login-error {
  color: var(--tg-hint);
  font-size: 13px;
  margin: 20px 0 0;
}

.login-error {
  color: var(--c-red);
}
