/* Self-hosted Noto Sans for MapLibre local label rendering (see static/fonts/). */
@font-face {
  font-family: 'Noto Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/NotoSans-Regular-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Noto Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/NotoSans-Regular-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
    U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Noto Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/NotoSans-Regular-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

html, body {
  margin: 0;
  height: 100%;
}

.profile-page {
  min-height: 100%;
  display: flex;
  /* Column so the admin usage block stacks under the card instead of beside
     it; with a single child this renders exactly as it did before. */
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 24px;
  padding: 48px 16px;
  box-sizing: border-box;
  background: #f5f7fa;
}

.profile-card {
  width: 100%;
  max-width: 480px;
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 16px;
  padding: 32px;
  text-align: center;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
}

.profile-avatar {
  width: 128px;
  height: 128px;
  margin: 0 auto 20px;
  border-radius: 50%;
  overflow: hidden;
  background: #e2e8f0;
}

.profile-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.profile-avatar-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 3rem;
  font-weight: 600;
  color: #64748b;
}

.profile-name {
  margin: 0 0 12px;
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 1.75rem;
  color: #1e293b;
}

.profile-description {
  margin: 0 0 20px;
  color: #475569;
  line-height: 1.5;
  white-space: pre-line;
}

.profile-contacts {
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}

.profile-contact a {
  display: inline-block;
  padding: 8px 16px;
  border: 1px solid #cbd5e1;
  border-radius: 999px;
  color: #2b5f92;
  text-decoration: none;
  font-size: 0.95rem;
}

.profile-contact a:hover {
  background: #eef2f7;
}

.profile-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-bottom: 20px;
}

.profile-edit,
.profile-editor,
.profile-logout {
  display: inline-block;
  padding: 8px 14px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  font-family: inherit;
}

.profile-edit {
  background: #2b5f92;
  border: 1px solid #2b5f92;
  color: #fff;
}

.profile-edit:hover {
  background: #24507d;
  border-color: #24507d;
}

.profile-editor {
  background: #fff;
  border: 1px solid #2b5f92;
  color: #2b5f92;
}

.profile-editor:hover {
  background: #eef4fb;
}

.profile-logout {
  background: #fff;
  border: 1px solid #c0392b;
  color: #c0392b;
}

.profile-logout:hover {
  background: #fdecec;
}

.profile-admin-link {
  margin: 0 0 20px;
  text-align: center;
}

.profile-admin-link a {
  color: #2b5f92;
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
}

.profile-admin-link a:hover {
  text-decoration: underline;
}

.profile-back {
  margin: 0;
  padding-top: 24px;
  border-top: 1px solid #e2e8f0;
  text-align: center;
}

.profile-back a {
  color: #2b5f92;
  text-decoration: none;
}

.profile-back a:hover {
  text-decoration: underline;
}

/* Technical block styles kept for historical snapshots; the live tariff UI
   lives in the editor (UserUsagePanel). */
.usage-block {
  width: 100%;
  max-width: 480px;
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 16px;
  padding: 24px;
  box-sizing: border-box;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
  font-size: 14px;
  color: #2d3b47;
}

.usage-title {
  margin: 0 0 16px;
  font-size: 16px;
  font-weight: 600;
}

.usage-plan,
.usage-period,
.usage-outcomes,
.usage-empty {
  margin: 0 0 8px;
}

.usage-period.is-lapsed {
  color: #c0392b;
  font-weight: 600;
}

.usage-resource {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 4px 12px;
  margin-bottom: 10px;
}

.usage-resource-figures {
  font-variant-numeric: tabular-nums;
  color: #5a6b78;
}

.usage-bar {
  grid-column: 1 / -1;
  height: 6px;
  border-radius: 3px;
  background: #e2e8f0;
  overflow: hidden;
}

.usage-bar-fill {
  display: block;
  height: 100%;
  background: #2b5f92;
}

.usage-bar-fill.is-exhausted {
  background: #c0392b;
}

.usage-table {
  width: 100%;
  border-collapse: collapse;
  margin: 12px 0;
}

.usage-table th,
.usage-table td {
  padding: 4px 6px;
  text-align: right;
  border-bottom: 1px solid #eef2f6;
  font-variant-numeric: tabular-nums;
}

.usage-table th:first-child,
.usage-table td:first-child {
  text-align: left;
  font-variant-numeric: normal;
}

.usage-table th {
  font-weight: 600;
  color: #5a6b78;
}

.usage-history {
  margin: 12px 0;
  color: #5a6b78;
}

.usage-history summary {
  cursor: pointer;
}

.usage-history-row {
  padding: 4px 0 0 12px;
}

.usage-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid #e2e8f0;
}

.usage-upgrade {
  margin: 0 0 8px;
  padding: 6px 10px;
  border-radius: 8px;
  background: #eef4fb;
  color: #24507d;
}

.usage-date {
  padding: 7px 8px;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  font-family: inherit;
  font-size: 14px;
}

.usage-plan-select {
  padding: 7px 8px;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  font-family: inherit;
  font-size: 14px;
}

.usage-btn {
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid #2b5f92;
  background: #fff;
  color: #2b5f92;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.usage-btn:hover {
  background: #eef4fb;
}

.usage-btn-danger {
  border-color: #c0392b;
  color: #c0392b;
}

.usage-btn-danger:hover {
  background: #fdecec;
}

.usage-error {
  margin: 12px 0 0;
  color: #c0392b;
}

#map {
  position: absolute;
  inset: 0;
}

.lang-hint {
  display: block;
  margin-top: 2px;
  font-size: 0.85em;
  color: #5a6b78;
}

.copy-hint {
  display: block;
  margin-top: 2px;
  font-size: 0.8em;
  color: #8a97a2;
}

#copy-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  z-index: 40;
  transform: translate(-50%, 8px);
  padding: 9px 16px;
  border-radius: 20px;
  background: rgba(26, 42, 56, 0.92);
  color: #fff;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 14px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

#copy-toast.open {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Site logo: desktop float (hover 50→100) vs mobile-in-menu (fixed 50). */
.site-logo {
  line-height: 0;
  text-decoration: none;
  opacity: 0.75;
}

.site-logo img {
  display: block;
  width: 50px;
  height: 50px;
}

.site-logo--float {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 15;
  width: 40px;
  height: 40px;
  transform-origin: top left;
  transition: transform 0.2s ease;
}

.site-logo--float img {
  width: 40px;
  height: 40px;
}

@media (hover: hover) and (pointer: fine) {
  .site-logo--float:hover {
    /* 40px → 100px */
    transform: scale(2.5);
  }
}

.site-logo--menu {
  display: none;
  flex: 0 0 auto;
  width: 50px;
  height: 50px;
}

#map-menu .site-logo--menu {
  padding: 10px 14px;
  width: auto;
  height: auto;
  border-bottom: 1px solid #eef2f6;
}

/* Loading: spin the logo image (not the <a>, which uses scale on hover). */
.site-logo.is-logo-spinning img {
  animation: similarity-spin 3.2s linear infinite;
}

.site-logo.is-logo-stopping img {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .site-logo.is-logo-spinning img,
  .site-logo.is-logo-stopping img {
    animation: none;
    transition: none;
  }
}

#toolbar {
  position: absolute;
  top: 12px;
  left: 64px;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.95);
  border-radius: 8px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#toolbar label {
  font-size: 13px;
  color: #333;
}

/* Keep a label and its field together so they never wrap separately. */
.field-group {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The collapse toggle is only relevant on small screens. */
#toolbar-toggle {
  display: none;
  border: 1px solid #ccc;
  border-radius: 6px;
  background: #fff;
  color: #333;
  cursor: pointer;
  line-height: 1;
}

#word {
  font-size: 14px;
  padding: 6px 10px;
  border: 1px solid #ccc;
  border-radius: 6px;
  outline: none;
  min-width: 200px;
}

#word:focus {
  border-color: #4a90d9;
  box-shadow: 0 0 0 2px rgba(74, 144, 217, 0.2);
}

#threshold,
#cluster-basis,
#user-country {
  font-size: 14px;
  padding: 6px 8px;
  border: 1px solid #ccc;
  border-radius: 6px;
  outline: none;
  background: #fff;
  color: #333;
  cursor: pointer;
}

#threshold:focus,
#cluster-basis:focus,
#user-country:focus {
  border-color: #4a90d9;
  box-shadow: 0 0 0 2px rgba(74, 144, 217, 0.2);
}

#translate-btn {
  font-size: 14px;
  padding: 6px 14px;
  border: 1px solid #4a90d9;
  border-radius: 6px;
  background: #4a90d9;
  color: #fff;
  cursor: pointer;
}

#translate-btn:hover {
  background: #3b7dc0;
  border-color: #3b7dc0;
}

#clusterize-btn {
  font-size: 14px;
  padding: 6px 14px;
  border: 1px solid #4a90d9;
  border-radius: 6px;
  background: #fff;
  color: #2b5f92;
  cursor: pointer;
}

#clusterize-btn:hover:not(:disabled) {
  background: #eef4fb;
}

#clusterize-btn:disabled {
  border-color: #ccc;
  color: #9aa5ae;
  background: #f2f3f5;
  cursor: not-allowed;
}

/* Group the База field, state lamp and Кластеризовать button inside a
   framed box with a little breathing room on each side. */
.cluster-box {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 8px;
  padding: 4px 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
}

/* Clustering-state indicator ("lamp") next to the Кластеризовать button.
   Dim when clustering is off; lit (and clickable to exit) when it is on. */
.cluster-lamp {
  width: 16px;
  height: 16px;
  padding: 0;
  flex: 0 0 auto;
  border: 1px solid #b9c2cb;
  border-radius: 50%;
  background: #e4e8ec;
  cursor: default;
  transition: background 0.15s, box-shadow 0.15s, border-color 0.15s;
}

.cluster-lamp.on {
  border-color: #2e9e5b;
  background: #35c46b;
  box-shadow: 0 0 6px rgba(53, 196, 107, 0.7);
  cursor: pointer;
}

#export-btn {
  font-size: 14px;
  padding: 6px 14px;
  border: 1px solid #4a90d9;
  border-radius: 6px;
  background: #fff;
  color: #2b5f92;
  cursor: pointer;
}

#export-btn:hover {
  background: #eef4fb;
}

#map-edit-toggle {
  font-size: 15px;
  line-height: 1;
  padding: 7px 12px;
  border: 1px solid #4a90d9;
  border-radius: 6px;
  background: #fff;
  color: #2b5f92;
  cursor: pointer;
}

#map-edit-toggle:hover {
  background: #eef4fb;
}

#share-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  padding: 6px 10px;
  border: 1px solid #4a90d9;
  border-radius: 6px;
  background: #fff;
  color: #2b5f92;
  cursor: pointer;
}

#share-btn:hover {
  background: #eef4fb;
}

#similarity-spinner {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: none;
  align-items: center;
  justify-content: center;
  /* Transparent overlay that also intercepts clicks while active. */
  background: rgba(255, 255, 255, 0.05);
  cursor: progress;
}

#similarity-spinner.open {
  display: flex;
}

#similarity-spinner .spinner {
  width: 54px;
  height: 54px;
  border: 5px solid rgba(74, 144, 217, 0.25);
  border-top-color: #4a90d9;
  border-radius: 50%;
  animation: similarity-spin 0.8s linear infinite;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}

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

#export-modal {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.35);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#export-modal.open {
  display: flex;
}

#export-card {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
  padding: 20px 22px;
  width: 320px;
  max-width: calc(100vw - 32px);
}

#export-card h2 {
  margin: 0 0 12px;
  font-size: 17px;
  color: #222;
}

#export-card label {
  display: block;
  font-size: 13px;
  color: #444;
  margin-bottom: 6px;
}

#export-scale {
  width: 100%;
  box-sizing: border-box;
  font-size: 14px;
  padding: 8px 10px;
  border: 1px solid #ccc;
  border-radius: 6px;
  outline: none;
}

#export-hint {
  font-size: 12px;
  color: #777;
  margin: 8px 0 16px;
}

#export-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

#export-actions button {
  font-size: 14px;
  padding: 7px 14px;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid #ccc;
  background: #f4f4f4;
  color: #333;
}

#export-confirm {
  border-color: #4a90d9 !important;
  background: #4a90d9 !important;
  color: #fff !important;
}

#export-confirm:hover {
  background: #3b7dc0 !important;
  border-color: #3b7dc0 !important;
}

/* --- API / WebSocket error modal --- */
/* Окно ожидания повторного подключения: вид тот же, что у ошибки, поэтому
   селекторы идут парой. Разница только в поведении -- закрыть его нельзя. */
#waiting-modal,
#error-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.35);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#waiting-modal.open,
#error-modal.open {
  display: flex;
}

#waiting-card,
#error-card {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
  padding: 20px 22px;
  width: 360px;
  max-width: calc(100vw - 32px);
}

#waiting-card h2,
#error-card h2 {
  margin: 0 0 12px;
  font-size: 17px;
  color: #222;
}

#waiting-modal-message,
#error-modal-message {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.45;
  color: #333;
  white-space: pre-wrap;
  word-break: break-word;
}

#error-modal-actions {
  display: flex;
  justify-content: flex-end;
}

#error-modal-ok {
  font-size: 14px;
  padding: 7px 18px;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid #4a90d9;
  background: #4a90d9;
  color: #fff;
}

#error-modal-ok:hover {
  background: #3b7dc0;
  border-color: #3b7dc0;
}

/* --- Confirm modal (Mapus.showConfirmModal) --- */
#confirm-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.35);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#confirm-modal.open {
  display: flex;
}

#confirm-card {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
  padding: 20px 22px;
  width: 360px;
  max-width: calc(100vw - 32px);
}

#confirm-card h2 {
  margin: 0 0 12px;
  font-size: 17px;
  color: #222;
}

#confirm-modal-message {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.45;
  color: #333;
  white-space: pre-wrap;
  word-break: break-word;
}

#confirm-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

#confirm-modal-actions button {
  font-size: 14px;
  padding: 7px 14px;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid #ccc;
  background: #f4f4f4;
  color: #333;
}

#confirm-modal-actions button:hover {
  background: #eaeaea;
}

#confirm-modal-ok {
  border-color: #4a90d9 !important;
  background: #4a90d9 !important;
  color: #fff !important;
}

#confirm-modal-ok:hover {
  background: #3b7dc0 !important;
  border-color: #3b7dc0 !important;
}

/* --- POI detail modal --- */
#poi-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#poi-modal.open {
  display: flex;
}

#poi-card {
  position: relative;
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
  padding: 20px 22px;
  width: 420px;
  max-width: calc(100vw - 32px);
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}

#poi-close {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  background: transparent;
  font-size: 24px;
  line-height: 1;
  color: #666;
  cursor: pointer;
}

#poi-close:hover {
  color: #222;
}

#poi-title {
  margin: 0 0 10px;
  font-size: 19px;
  color: #1a2a38;
  padding-right: 28px;
}

#poi-description {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.5;
  color: #384652;
  white-space: pre-wrap;
}

#poi-link {
  display: inline-block;
  margin: 0 0 14px;
  font-size: 14px;
  color: #2f6fed;
  word-break: break-all;
}

#poi-gallery {
  position: relative;
  margin: 0 0 16px;
  background: #f2f4f7;
  border-radius: 8px;
  overflow: hidden;
}

#poi-image {
  display: block;
  width: 100%;
  max-height: 260px;
  object-fit: contain;
  background: #f2f4f7;
}

#poi-prev,
#poi-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(26, 42, 56, 0.55);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

#poi-prev:hover,
#poi-next:hover {
  background: rgba(26, 42, 56, 0.8);
}

#poi-prev {
  left: 10px;
}

#poi-next {
  right: 10px;
}

#poi-counter {
  position: absolute;
  right: 10px;
  bottom: 8px;
  padding: 2px 8px;
  border-radius: 10px;
  background: rgba(26, 42, 56, 0.6);
  color: #fff;
  font-size: 12px;
}

#poi-data {
  margin: 0;
  display: grid;
  grid-template-columns: minmax(90px, auto) 1fr;
  gap: 6px 14px;
  font-size: 13px;
}

#poi-data dt {
  font-weight: 600;
  color: #55636e;
  word-break: break-word;
}

#poi-data dd {
  margin: 0;
  color: #1a2a38;
  word-break: break-word;
}

#about-btn {
  position: fixed;
  left: 12px;
  bottom: 12px;
  z-index: 10;
  font-size: 14px;
  padding: 8px 14px;
  border: 1px solid #4a90d9;
  border-radius: 6px;
  background: #fff;
  color: #2b5f92;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}

#about-btn:hover {
  background: #eef4fb;
}

.map-footer-bar {
  position: fixed;
  left: 12px;
  bottom: 12px;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 30px;
}

/* Hamburger menu is mobile-only; shown via the max-width media query. */
#map-menu-wrap {
  display: none;
}

/* AI dialog: compact, collapsible dock centered at the bottom of the map.
   Sits between the footer bar (bottom-left) and the legend (bottom-right).
   Draggable within #map (see map_ai.js); above persistent map chrome. */
/* Ширина и потолок ленты -- переменные, потому что их задаёт человек мышью.
   Значения по умолчанию -- ровно те, что были до ресайза: стартовый размер и
   есть минимальный. */
.ai-dock {
  position: fixed;
  left: 50%;
  bottom: 12px;
  transform: translateX(-50%);
  z-index: 50;
  width: var(--ai-dock-w, min(588px, calc(100vw - 24px)));
  border: 1px solid #d4dde5;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.ai-dock--moved {
  right: auto;
  bottom: auto;
  transform: none;
}

.ai-dock--returning {
  transition: left 2s ease, top 2s ease;
}

.ai-dock--dragging,
.ai-dock--resizing {
  user-select: none;
}

/* Ручка ресайза -- в правом ВЕРХНЕМ углу, и это не вкусовщина: док прижат к
   низу экрана, и место у него есть только вверх. Курсор nesw соответствует
   тому, куда тянуть: вверх-вправо. */
.ai-dock-resize {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 2;
  width: 22px;
  height: 22px;
  /* Отступ стрелки от кромок дока. На экране выходит на пиксель больше:
     рамка дока в один пиксель -- тоже его край, и она входит в счёт. Линии
     иконки доведены до кромок её поля (см. шаблон), поэтому это число и то,
     что видно, расходятся ровно на рамку и больше ни на что. */
  padding: 4px 4px 0 0;
  border: none;
  border-radius: 0 8px 0 6px;
  background: transparent;
  color: rgba(255, 255, 255, 0.75);
  cursor: nesw-resize;
  /* Ручка лежит на шапке, а шапка -- хват для перетаскивания. Свой
     touch-action, чтобы жест не уехал в прокрутку страницы. */
  touch-action: none;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
}

.ai-dock-resize:hover,
.ai-dock-resize:focus-visible {
  color: #fff;
}

.ai-dock.collapsed .ai-dock-resize {
  display: none;
}

/* Пока размер не задан, всё как было: лента упирается в 40vh, а высота дока
   идёт за содержимым. Стоит потянуть ручку -- высота становится ЗАДАННОЙ, и
   ленте отдаётся всё, что осталось от поля ввода и кнопок.

   Разница не косметическая, и найдена она замером: потолок, до которого
   содержимому далеко, не меняет ничего. Разговор в две реплики -- лента 94 px
   при потолке 432, ручка тянется, док стоит на месте. Ручка, которая не
   двигает то, за что её тянут, сломана. */
.ai-dock--sized .ai-dock-body {
  height: var(--ai-dock-body-h);
  box-sizing: border-box;
}


.ai-dock--sized .ai-feed-wrap {
  flex: 1 1 auto;
  /* Без этого flex-элемент не даёт себя сжать ниже содержимого, и лента
     выталкивает поле ввода за нижнюю кромку. */
  min-height: 0;
}

.ai-dock--sized .ai-feed {
  max-height: none;
  height: 100%;
  box-sizing: border-box;
}

/* Разговора ещё нет -- лишнюю высоту забирает поле ввода. Тянуть ручку и не
   видеть ничего хуже, чем не иметь ручки вовсе. */
.ai-dock--sized .ai-feed-wrap[hidden] ~ .ai-composer {
  flex: 1 1 auto;
}

.ai-dock-toggle,
#ai-dock-toggle {
  display: block;
  width: 100%;
  padding: 8px 14px;
  border: none;
  border-radius: 8px;
  background: #2b5f92;
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  cursor: grab;
  touch-action: none;
}

.ai-dock--dragging #ai-dock-toggle {
  cursor: grabbing;
}

.ai-dock:not(.collapsed) #ai-dock-toggle {
  border-radius: 8px 8px 0 0;
}

#ai-dock-toggle::before {
  content: "\25B2\00A0";
  font-size: 10px;
}

.ai-dock.collapsed #ai-dock-toggle::before {
  content: "\25BC\00A0";
}

.ai-dock-body {
  /* Снизу на 5 px больше, чем сверху: под полем ввода теперь нет строки
     кнопок, и его нижняя кромка подходила к рамке дока вплотную. */
  padding: 10px 12px 15px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  /* Прокрутки тут больше НЕТ, и это не откат, а следствие: она появилась,
     когда журнал статусов лежал в потоке и вылезал за нижнюю кромку. Журнал
     с тех пор стал окном поверх дока -- а `overflow` обрезает абсолютно
     позиционированного потомка, даже если тот висит выше своего контейнера.
     Окно открывалось пустым куском рамки, и история в нём не показывалась. */
}

.ai-dock-body[hidden] {
  display: none;
}

/* Композитор: рамка одна, и она у контейнера, а не у поля. Внутри — текст
   сверху и полоса управления снизу, как во вводе Claude Desktop. Так место под
   полем освобождается совсем, а кнопка стоит там, куда рука идёт после
   набора. */
/* Нижний предел поля ввода. Арифметика, а не круглое число: столбец справа --
   «Сброс» (22) + зазор (4) + отправка (28) = 54; поле в две строки -- 40 плюс
   6 своих отступов; рамка композитора -- 12. Итого 112, и это тот размер, ниже
   которого ручка дока поле не уводит: меньше -- и текст оказывается в щели
   между кнопками. */
:root {
  --ai-composer-min: 112px;
  /* Высота строки управления. Одно число на всё, что в ней стоит: плюс,
     статус и круглые кнопки. Раньше каждый элемент имел свою -- значок лотка
     16, статус 18, кнопки 24, -- и центры сходились только на глаз. */
  --ai-bar-h: 24px;
}

.ai-composer {
  display: flex;
  flex-direction: column;
  min-height: var(--ai-composer-min);
  border: 1px solid #d4dde5;
  border-radius: 8px;
  background: #fff;
  /* Полоса прижата к нижней кромке рамки, поэтому внутренние отступы задаёт
     сам композитор, а не поле: иначе между текстом и кнопкой появилась бы
     двойная пустота. */
  padding: 6px 6px 6px 8px;
}

/* Рамка светится там же, где курсор: иначе с рамкой на контейнере фокус в поле
   ничем не отмечен, и не видно, куда попадёт набранное. */
.ai-composer:focus-within {
  border-color: #4a90d9;
}

#ai-prompt {
  width: 100%;
  box-sizing: border-box;
  flex: 1 1 auto;
  /* Две строки текста -- ровно столько же, сколько занимают две кнопки в
     столбце справа. Меньше -- и поле перестаёт быть полем. */
  min-height: 40px;
  /* Собственная ручка размера снята намеренно: она жила в правом нижнем углу —
     ровно там, где теперь кнопка, — и у дока уже есть своя, в правом верхнем
     углу шапки. Двух ручек на одно окно не бывает. */
  resize: none;
  padding: 2px 2px 4px;
  border: 0;
  outline: none;
  background: transparent;
  font: inherit;
  font-size: 14px;
}

/* Полоса управления: лоток слева, отправка справа. Между ними распорка, а не
   `justify-content: space-between` — при отсутствующем лотке (тариф без
   вложений) кнопка иначе уехала бы влево. */
.ai-composer-bar {
  /* Система координат для окна истории: оно висит НАД полосой, прижатое к ней
     нижней кромкой. */
  position: relative;
  display: flex;
  align-items: center;
  /* Переносится, а не сжимается: с тремя кнопками результата полоса на узком
     доке не помещается в строку, и сжатие превратило бы подписи в многоточия. */
  flex-wrap: wrap;
  /* Ни один элемент полосы не растягивается по высоте: у всех она одна
     (`--ai-bar-h`), и растяжение сдвинуло бы содержимое относительно соседей. */
  align-content: center;
  gap: 6px;
  margin-top: 2px;
}

.ai-composer-bar .ai-tray {
  margin: 0;
  /* Та же высота, что у статуса и кнопок: иначе значок лотка (16 px) висит
     выше их середины на четыре пикселя. */
  display: flex;
  align-items: center;
  height: var(--ai-bar-h);
}

/* Кнопки результата в полосе композитора -- круглые значки без подписей, в
   ряд слева, сразу за лотком. Подписи здесь стоили бы всей ширины полосы:
   четыре слова заняли бы столько же, сколько само поле ввода. Смысл несут
   значок, `title` и `aria-label` -- последний обязателен, иначе кнопка
   безымянна для чтения с экрана. */
.ai-round {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--ai-bar-h);
  height: var(--ai-bar-h);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #64748b;
  cursor: pointer;
  /* Приглушены, пока на них не навели: это действия над готовым результатом,
     а не то, ради чего открывают док. */
  opacity: 0.65;
  transition: opacity 0.15s, color 0.15s, background 0.15s;
}

.ai-round:hover,
.ai-round:focus-visible {
  opacity: 1;
  background: #eef4fb;
  color: #2b5f92;
}

.ai-round[hidden] {
  display: none;
}

.ai-round:disabled {
  opacity: 0.3;
  cursor: default;
}

/* Красный -- и «Сброс», и корзина: обе теряют сделанное, разница лишь в том,
   что теряется -- карта или переписка. Цвет появляется на наведении, а не
   лежит на полосе постоянно: четыре красных значка подряд читались бы как
   предупреждение обо всём сразу. */
.ai-round--reset:hover,
.ai-round--reset:focus-visible,
.ai-feed-clear:hover,
.ai-feed-clear:focus-visible {
  background: #fdecea;
  color: #b91c1c;
}

.ai-send {
  margin-left: auto;
  /* Она выше остальных (28 против 24) -- значит, центрируется по общей линии,
     а не тянется вместе с полосой. */
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #4a90d9;
  color: #fff;
  cursor: pointer;
}

.ai-send:hover:not(:disabled) {
  background: #3b7cc4;
}

.ai-send:disabled {
  opacity: 0.45;
  cursor: default;
}

/* Работа — квадрат вместо стрелки и красный вместо синего: состояние читается
   и формой, и цветом, а не одним из двух. */
.ai-send.is-busy {
  background: #c0392b;
}

.ai-send.is-busy:hover:not(:disabled) {
  background: #a93226;
}

.ai-send-icon--stop,
.ai-send.is-busy .ai-send-icon--go {
  display: none;
}

.ai-send.is-busy .ai-send-icon--stop {
  display: block;
}

/* Collapsible tray between the prompt field and the actions. The markup only
   exists when the viewer's plan puts something in it, so there is no empty
   state to style. */
.ai-tray {
  margin: 6px 0 0;
}

.ai-tray-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: var(--ai-bar-h);
  /* No vertical padding: the row is already spaced by the dock's own gap,
     and doubling it pushed the tray away from the field it belongs to. */
  padding: 0 5px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: #7d92a3;
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}

.ai-tray-toggle:hover {
  background: #eef2f5;
  color: #41525f;
}

.ai-tray-toggle[aria-expanded='true'] {
  background: #eef2f5;
  color: #2f6fed;
}

/* + becomes × when the tray is open. Colour alone would carry the state, but
   not for anyone who cannot separate these two greys. */
.ai-tray-icon {
  transition: transform 0.15s ease;
}

.ai-tray-toggle[aria-expanded='true'] .ai-tray-icon {
  transform: rotate(45deg);
}

@media (prefers-reduced-motion: reduce) {
  .ai-tray-icon {
    transition: none;
  }
}

.ai-tray-toggle:focus-visible {
  outline: 2px solid #2f6fed;
  outline-offset: 2px;
}

/* Shown only when files are attached, so a collapsed tray still says the
   prompt is carrying something. */
.ai-tray-count {
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 7px;
  background: #2f6fed;
  color: #fff;
  font-size: 10px;
  line-height: 14px;
  text-align: center;
}

.ai-tray-count[hidden] {
  display: none;
}

.ai-tray-body[hidden] {
  display: none;
}

/* Prompt attachments (tariff feature prompt_upload). The wrap is a grid so the
   file list can span the full width under the button and the hint. */
.ai-upload-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin: 8px 0 0;
  font-size: 13px;
  color: #5a6b78;
}


.ai-upload-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px dashed #b6c2cc;
  border-radius: 6px;
  cursor: pointer;
  color: #41525f;
  background: #fbfcfd;
  transition: border-color 0.15s, background 0.15s;
}

.ai-upload-button:hover {
  border-color: #7d92a3;
  background: #f2f6f9;
}

/* Off-screen rather than display:none: a hidden input is not focusable, and
   the control would drop out of the keyboard order entirely. */
.ai-upload-button input[type='file'] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Focus lives on the input, so the ring has to be drawn on the label. */
.ai-upload-button:focus-within {
  outline: 2px solid #2f6fed;
  outline-offset: 2px;
}

.ai-upload-label {
  flex-shrink: 0;
}

.ai-upload-hint {
  color: #8494a1;
  font-size: 12px;
}

.ai-upload-list {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ai-upload-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  padding: 3px 6px 3px 9px;
  border: 1px solid #dbe3ea;
  border-radius: 999px;
  background: #fff;
  font-size: 12px;
  color: #41525f;
}

.ai-upload-item__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 180px;
}

.ai-upload-item__meta {
  color: #8494a1;
  flex-shrink: 0;
}

/* A file the model will only partly see must say so on the chip itself:
   the user is entitled to know before spending the request. */
.ai-upload-item--truncated {
  border-color: #e3c98f;
  background: #fdf8ec;
}

.ai-upload-item--pending {
  opacity: 0.6;
}

.ai-upload-item__remove {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #eef2f5;
  color: #5a6b78;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}

.ai-upload-item__remove:hover {
  background: #dde5eb;
  color: #24323d;
}

.ai-upload-error {
  flex-basis: 100%;
  margin: 0;
  color: #b3261e;
  font-size: 12px;
}

.ai-upload-error[hidden] {
  display: none;
}

/* Live saga progress: an append-only journal, collapsed to its newest line
   until the user opens it. Capped in height so a six-task fan-out cannot push
   the dock off screen once open. */
/* Статус живёт в полосе ввода, между плюсом и кнопками результата. Растёт по
   ширине, а не по высоте: полоса одна на всё, и вторая строка в ней означала
   бы прыжок поля ввода при каждом сообщении сборки. */
.ai-progress-dock {
  flex: 1 1 auto;
  /* Без этого длинный статус распирает полосу и выдавливает кнопки за рамку:
     flex-элемент не сжимается ниже своего содержимого, пока ему не сказать. */
  min-width: 0;
}

.ai-progress-dock[hidden] {
  display: none;
}

/* The whole summary line is the disclosure control, so the hit area matches
   what it looks like it opens. */
.ai-progress-toggle {
  display: flex;
  /* Треугольник -- в левом углу видимой области статуса, а не там, где его
     оставит выравнивание содержимого: строка занимает всю ширину блока, и без
     этого короткий статус собрал бы её к центру. */
  justify-content: flex-start;
  /* Centre, not baseline: an empty summary has its baseline at the bottom of
     its reserved box, so baseline alignment would add the caret's descender on
     top of it and make the empty row *taller* than the filled one. */
  align-items: center;
  gap: 4px;
  width: 100%;
  min-width: 0;
  /* Одна строка зарезервирована всегда: полоса не должна прыгать, когда
     приходит первое сообщение сборки и когда уходит последнее. Высота -- та
     же, что у плюса и кнопок, поэтому текст статуса стоит на одной линии с
     их значками, а не выше. */
  height: var(--ai-bar-h);
  border: 0;
  background: none;
  padding: 0;
  text-align: left;
  font: inherit;
  font-size: 12px;
  color: #334155;
  cursor: pointer;
}

.ai-progress-caret {
  flex: 0 0 auto;
  /* Треугольник рисуется глифом, а у глифа своя левая полуприкладка: без
     фиксированной ширины и выравнивания он отходит от кромки на пару
     пикселей, и «в левом углу» перестаёт быть правдой. */
  width: 10px;
  margin-left: -1px;
  text-align: left;
  color: #94a3b8;
  font-size: 10px;
  line-height: 1;
}

.ai-progress-last {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Same box empty or full: 1.5 x 12px is exactly the filled line box, so
     setting and clearing the text cannot move anything below it. Высота при
     этом задаётся строкой-кнопкой (`--ai-bar-h`), и своей у текста нет: две
     разные высоты в одном месте и разъезжались бы по-разному. */
  line-height: 1.5;
}

/* Окно истории статусов: висит НАД полосой ввода, прижатое к ней нижней
   кромкой -- то есть ровно к той строке, из которой открылось. Не в потоке, а
   поверх дока: иначе оно двигало бы поле ввода из-под курсора в тот момент,
   когда в него смотрят. */
.ai-progress-panel {
  position: absolute;
  left: 0;
  bottom: calc(100% + 6px);
  /* Ширина по содержимому: строки журнала короткие («OSM: Дунай — готово»),
     и окно во всю полосу было пустым на две трети. Пол держит заголовок с
     идентификатором запроса, потолок -- сама полоса: шире неё окно вылезло бы
     за рамку дока. */
  width: max-content;
  min-width: 240px;
  max-width: 100%;
  z-index: 3;
  display: flex;
  flex-direction: column;
  /* Пять строк журнала -- обещанный минимум, и он посчитан, а не выбран:
     строка задачи это 12 px шрифта при line-height 1.5 плюс 2 px отступов,
     то есть 20; пять таких -- 100; шапка с идентификатором и крестиком -- 28;
     отступы окна -- 12. Итого 140, и это ПОЛ окна, а не потолок: меньше пяти
     строк оно не покажет даже в пустом журнале. */
  min-height: 140px;
  /* Потолок -- вдвое больше: примерно пятнадцать строк. Выше него журнал
     закрывал бы собой карту, ради которой открыт док. */
  max-height: 300px;
  padding: 6px 8px;
  border: 1px solid #d4dde5;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.18);
}

.ai-progress-panel[hidden] {
  display: none;
}

.ai-progress-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-bottom: 4px;
}

/* Крестик закрытия -- та же круглая кнопка, что и остальные в доке, только
   прижата вправо. */
.ai-progress-close {
  margin-left: auto;
}

/* Opaque request id above the journal. Monospace so a paste-target is
   recognisable; click copies and appends «(скопировано)». */
.ai-progress-request-id {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: none;
  padding: 0 0 2px;
  text-align: left;
  font: inherit;
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: #64748b;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ai-progress-request-id[hidden] {
  display: none;
}

.ai-progress-request-id:hover {
  color: #334155;
}

.ai-progress {
  margin: 0;
  /* Right padding keeps the row's last control -- the ✕ -- clear of the
     scrollbar; scrollbar-gutter additionally stops the rows shifting sideways
     at the moment the list starts to scroll. */
  padding: 0 10px 0 0;
  scrollbar-gutter: stable;
  list-style: none;
  font-size: 12px;
  /* Задан явно, а не оставлен на `normal`: на нём держится расчёт высоты окна
     (пять строк), а `normal` зависит от гарнитуры и на разных системах даёт
     разное. */
  line-height: 1.5;
  /* Fixed, not max-height: an arriving task must scroll the journal, never
     resize the dock under the cursor. The cost is empty space early in a run;
     the alternative is the panel growing line by line while it is read.

     Внутри окна высота не задаётся числом: окно и так ограничено сверху
     (`max-height`), а список забирает остаток и прокручивается. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.ai-progress[hidden] {
  display: none;
}

/* A line about the answer rather than about one task -- today, that some of
   the data could not be fetched. Amber, because it is a caveat on the result,
   not a failure of the request. */
.ai-progress-note {
  color: #92400e;
  padding: 2px 0;
}

/* Фраза терпения -- это интерфейс, а не доклад саги, и выглядеть она должна
   тише: цвет заметки означает «новость об ответе», а тут новостей нет. */
.ai-progress-note.is-patience {
  color: #6b7280;
  font-style: italic;
}

.ai-progress-task {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 1px 0;
  color: #334155;
}

/* Fixed-width mark and right-aligned status, so rows do not jump sideways as
   statuses change under them. */
.ai-progress-mark {
  width: 1em;
  flex: 0 0 1em;
  text-align: center;
}

.ai-progress-label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ai-progress-status {
  flex: 0 0 auto;
  color: #64748b;
  font-size: 11px;
}

/* Empty for the statuses the mark already conveys; the margin would otherwise
   leave a gap between the label and the ✕. */
.ai-progress-status:empty {
  display: none;
}

/* Sits immediately right of the status, last in the row. */
/* Отмена ОДНОЙ задачи -- не то же самое, что закрытие всего окна, и выглядеть
   одинаково они не должны: раньше и там и там стоял крестик, и промах стоил бы
   отменённой задачи вместо закрытого окна. Здесь квадрат «стоп» -- тот же
   знак, что у кнопки отправки во время работы, и означает он то же самое:
   остановить идущее. */
/* Кнопка, а не отметка. Квадрат в конце строки читался как маркер состояния
   -- и читался так не зря: у него не было ни фона, ни подписи, ничего, чем
   элемент управления отличается от значка. За один вечер это стоило восьми
   отменённых задач, которых никто не заказывал (саги 540, 543, 544, 549). */
.ai-progress-cancel {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 16px;
  padding: 0 5px;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  background: #f1f5f9;
  color: #64748b;
  cursor: pointer;
  margin-left: 4px;
  font-size: 10px;
  line-height: 1;
}

.ai-progress-cancel:hover,
.ai-progress-cancel:focus-visible {
  border-color: #b91c1c;
  color: #b91c1c;
}

.ai-progress-cancel:hover:not(:disabled) {
  color: #b91c1c;
}

.ai-progress-cancel:disabled {
  opacity: 0.4;
  cursor: default;
}

.ai-progress-task.is-queued {
  color: #94a3b8;
}

.ai-progress-task.is-processing .ai-progress-mark,
.ai-progress-task.is-assigned .ai-progress-mark {
  display: inline-block;
  animation: similarity-spin 1.2s linear infinite;
}

.ai-progress-task.is-completed .ai-progress-mark {
  color: #15803d;
}

.ai-progress-task.is-failed .ai-progress-mark {
  color: #b91c1c;
}

.ai-progress-task.is-requeued .ai-progress-mark {
  color: #b45309;
}

/* Cancellation is a neutral outcome the user asked for, not an error; a row
   left unknown by a dropped connection is not one either -- it is greyed
   rather than marked ✕ because the task may well have gone on to finish. */
.ai-progress-task.is-cancelled,
.ai-progress-task.is-skipped,
.ai-progress-task.is-unknown {
  color: #94a3b8;
}

/* Обёртка ленты: система координат для корзины, которая лежит поверх. */
.ai-feed-wrap {
  position: relative;
}

/* Корзина. Поверх ленты, слева сверху — там, куда смотрят, когда решают
   переписку убрать. Своя подложка, потому что под ней ПРОКРУЧИВАЕТСЯ текст:
   без неё кнопка читалась бы поверх букв. */
/* Размер и приглушённость -- от `.ai-round`: корзина стала одной из четырёх
   круглых кнопок ряда, и держать ей отдельный вид больше незачем. Красный на
   наведении -- общий с «Сбросом», выше. */

/* Лента переписки. Потолок высоты, а не бесконечный рост: док стоит поверх
   карты, и разговор, вытесняющий карту с экрана, отнимает то, ради чего он
   ведётся. Прокрутка внутри — своя. */
.ai-feed {
  margin: 0 0 8px;
  padding: 0;
  list-style: none;
  max-height: var(--ai-feed-max, 40vh);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Первая реплика начинается ниже корзины: иначе кнопка ложится на её первые
   слова, и прочесть нельзя ни то, ни другое. */
.ai-feed > .ai-feed-turn:first-child {
  margin-top: 26px;
}

.ai-feed-turn {
  font-size: 13px;
  line-height: 1.45;
  white-space: pre-wrap;
  padding: 6px 9px;
  border-radius: 10px;
  max-width: 92%;
}

/* Реплика человека прижата вправо, ответ — влево: кто говорит, видно по
   стороне, а не по чтению первых слов. */
.ai-feed-human {
  align-self: flex-end;
  background: #e2e8f0;
  color: #0f172a;
}

.ai-feed-assistant {
  align-self: flex-start;
  background: #f1f5f9;
  color: #334155;
}

/* Разметка внутри реплики. Модель отвечает выделением, списками и ссылками —
   стили тут скромные: реплика чата, а не статья. */
.ai-feed-turn strong { font-weight: 600; }
.ai-feed-turn em { font-style: italic; }

.ai-feed-turn code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  background: rgba(15, 23, 42, 0.06);
  border-radius: 4px;
  padding: 0 3px;
}

.ai-feed-turn a {
  color: #1d4ed8;
  text-decoration: underline;
  /* Длинная ссылка не должна расталкивать док шире карты. */
  overflow-wrap: anywhere;
}

/* Строка состояния в ленте: отмена, исчерпанный лимит. Не слова модели, и
   выглядит иначе -- по центру и приглушённо. */
.ai-feed-note {
  align-self: center;
  background: transparent;
  color: #64748b;
  font-size: 12px;
  padding: 2px 0;
}

.ai-rag-message {
  margin: 0;
  font-size: 13px;
  color: #92400e;
  background: #fffbeb;
  border: 1px solid #fcd34d;
  border-radius: 6px;
  padding: 8px 10px;
  white-space: pre-wrap;
}

.ai-rag-message[hidden] {
  display: none;
}

.ai-error {
  margin: 0;
  font-size: 13px;
  color: #b3261e;
}

.ai-error:empty {
  display: none;
}

/* Viewer badge, top-right next to the map zoom control (right: 10px). */
.user-badge {
  position: fixed;
  top: 12px;
  right: 52px;
  z-index: 12;
}

.user-badge-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  overflow: hidden;
  background: #2b5f92;
  color: #fff;
  font-weight: 600;
  font-size: 16px;
  text-decoration: none;
  border: 2px solid #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

.user-badge-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.user-badge-login {
  display: inline-block;
  padding: 7px 12px;
  border-radius: 6px;
  background: #fff;
  color: #2b5f92;
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* Page gradient behind the globe — always on, never fades with the canvas. */
.gate-page-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: #000;
}

.gate-globe {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  background: transparent;
  opacity: 0;
}

.gate-globe--ready {
  opacity: 1;
  transition: opacity 5s ease;
}

.gate-globe canvas {
  display: block;
}

.gate-globe-marker {
  position: relative;
  width: 0;
  height: 0;
  pointer-events: none;
}

.gate-globe-dot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: #ffd68c;
  box-shadow: 0 0 0 2px rgba(255, 214, 140, 0.35), 0 0 8px rgba(255, 214, 140, 0.55);
}

.gate-globe-label {
  position: absolute;
  left: 50%;
  bottom: 10px;
  transform: translateX(-50%);
  text-align: center;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.75);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  max-width: 180px;
  line-height: 1.25;
  pointer-events: none;
  white-space: normal;
}

.gate-globe-label-title {
  font-size: 12px;
  font-weight: 600;
}

.gate-globe-label-caption {
  font-size: 11px;
  opacity: 0.85;
  margin-top: 2px;
}

/* Same 640 boundary as `useCaptions` in gate_globe.js, which drops the caption
   here: what is left is one line, so it is given less room to run across a
   screen that is 375px wide in the first place. Both must move together. */
@media (max-width: 640px) {
  .gate-globe-label {
    max-width: 120px;
  }

  .gate-globe-label-title {
    font-size: 11px;
  }
}

.home-gate {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: 24px;
  box-sizing: border-box;
  background: transparent;
  color: #fff;
  text-align: center;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* Same gradient as before, opacity 0.3 over the globe; content stays opaque. */
.home-gate::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(160deg, #0e2c57 10%, #5fa3f1 40%, #020913 100%); 
  opacity: 0.3;
}

.home-gate > * {
  position: relative;
  z-index: 1;
}

/* Full opaque gradient when globe is off (load failure / no WebGL). */
.home-gate--no-globe::before {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .gate-globe {
    display: none !important;
  }

  .home-gate::before {
    opacity: 1;
  }
}

.home-gate-logo {
  display: block;
  width: min(340px, 78vw);
  height: auto;
  max-height: 120px;
  margin: 0 auto 32px;
  object-fit: contain;
  /* Same shadow as .home-gate-login, but drop-shadow rather than box-shadow:
     the logo is a transparent SVG, so box-shadow would trace the image's
     rectangle instead of the mark itself. drop-shadow follows the alpha. */
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.25));
}

.home-gate-title {
  margin: 0 0 8px;
  font-size: 34px;
  line-height: 1.2;
  font-weight: 600;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}

.home-gate-text {
  margin: 0 0 20px;
  max-width: 32em;
  font-size: 18px;
  line-height: 1.45;
}

.home-gate-text a,
.home-gate-text a:visited,
.home-gate-text a:hover {
  color: inherit;
  text-decoration: underline;
}

.home-gate-login {
  display: inline-block;
  padding: 10px 24px;
  border: none;
  border-radius: 6px;
  background: #fff;
  color: #2b5f92;
  font-weight: 600;
  font-size: 16px;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  font-family: inherit;
}

.home-gate-login:hover {
  background: #f4f7fb;
}

/* --------------------------------------------------------------------------
   Landing below the gate

   The gate used to be exactly one screen, so html/body { height: 100% } was
   free. It is not any more: that caps the box and the document stops growing
   with its content. Scoped with :has() so the map, the editor and the profile
   keep the height they rely on.
   -------------------------------------------------------------------------- */
html:has(.landing),
body:has(.landing) {
  height: auto;
  min-height: 100%;
}

/* The globe is position: fixed, so the hero scrolls off it and everything
   below simply covers it. That is the whole trick -- no scroll listener, no
   second canvas, and the globe keeps turning behind an opaque page. */
.landing {
  position: relative;
  z-index: 1;
  /* 160deg is the angle the hero's overlay already uses, so the diagonal reads
     as the same light rather than a second one. It starts at the colour the
     hero fades into -- the seam has to stay invisible -- and opens toward blue
     as the reader descends. The far end is #14406b and no lighter: past that
     the muted footer line drops under 4.5:1 (it is #9db2cb for the same
     reason, 4.89:1 where it actually sits). */
  background: linear-gradient(160deg, #050d18 0%, #0a2340 45%, #14406b 100%);
  color: #dce6f2;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Reads as the horizon the hero sits on rather than a seam. */
  box-shadow: 0 -60px 90px -40px rgba(5, 13, 24, 0.95);
}

.gate-scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: rgba(255, 255, 255, 0.72);
  font-size: 13px;
  letter-spacing: 0.04em;
  text-decoration: none;
}

.gate-scroll-cue:hover {
  color: #fff;
}

.gate-scroll-cue-arrow {
  width: 10px;
  height: 10px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  animation: gate-cue-nudge 2.2s ease-in-out infinite;
}

@keyframes gate-cue-nudge {
  0%, 100% { transform: rotate(45deg) translate(0, 0); }
  50%      { transform: rotate(45deg) translate(3px, 3px); }
}

@media (prefers-reduced-motion: reduce) {
  .gate-scroll-cue-arrow {
    animation: none;
  }
}

/* The typed prompt line, first thing past the globe. Two rows are reserved
   whatever is on screen: the line grows as it is typed, and a container that
   grew with it would push the whole page down on every keystroke. */
/* Fixed height, not a floor: the area must not change size for any prompt, at
   any point in typing. Four rows — one being typed, the rest on their way
   out — and everything past them is clipped. */
.gate-typing-window {
  /* The size lives here so `em` below means rows of typed text. On the
     container it would have meant rows of the landing's 16px, and the window
     came out at 86px — barely one line of the 27px it actually holds. */
  font-size: clamp(18px, 2.6vw, 27px);
  position: relative;
  height: 5.4em;
  overflow: hidden;
}

/* Pinned to the floor, so the stack grows *upward* past the top edge and the
   line being typed is always the one standing on it.
   `justify-content: flex-end` was tried first and is a trap: it bottom-aligns
   only while the content still fits. The moment the sent prompts outgrow the
   four rows — which is after the second one — an in-flow stack starts at the
   window's top edge and grows down, and the live line goes under the floor
   and out of sight. Taking the stack out of flow is what makes the anchor
   hold at any height, and it also puts the window's height beyond the reach
   of its contents for good. */
.gate-typing-stack {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  will-change: transform;
}

.gate-typing-window p {
  margin: 0;
  line-height: 1.35;
  font-weight: 500;
}

/* The live line holds two rows of its own, so a prompt wrapping onto a second
   one does not jerk the stack under it. */
.gate-typing-line {
  color: #eaf1fa;
  min-height: 2.7em;
}

/* Sent prompts, fading with distance from the line being typed: the eye
   should land on what is happening now, and the topmost is already invisible
   by the time the window clips it. */
.gate-typing-sent {
  color: #6f8bab;
  transition: opacity 0.45s ease;
}

.gate-typing-sent:nth-last-child(2) { opacity: 0.55; }
.gate-typing-sent:nth-last-child(3) { opacity: 0.28; }
.gate-typing-sent:nth-last-child(4) { opacity: 0.1; }
.gate-typing-sent:nth-last-child(n + 5) { opacity: 0; }

/* The upward slide: the stack is dropped by exactly the height of the row
   just added and then released, so it reads as a scroll rather than a jump —
   inside a clipped window, so no scrollbar and no page movement. */
.gate-typing-stack.is-scrolling {
  transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
  transform: translateY(0) !important;
}

.gate-typing-caret {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 2px;
  vertical-align: -0.15em;
  background: #6ea8f0;
  animation: gate-caret-blink 1.06s steps(1) infinite;
}

/* A real caret holds solid while the keys are moving and resumes blinking
   when the hand stops. Blinking straight through is the detail that gives a
   fake one away. */
.gate-typing.is-typing .gate-typing-caret {
  animation: none;
}

@keyframes gate-caret-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* Reduced motion gets one prompt, still: a blinking bar is itself the kind of
   movement this setting asks to be spared. */
@media (prefers-reduced-motion: reduce) {
  .gate-typing-caret {
    display: none;
  }
}

.gate-typing.is-still .gate-typing-caret {
  display: none;
}

.landing-section {
  max-width: 1080px;
  margin: 0 auto;
  padding: 96px 24px;
  box-sizing: border-box;
}

.landing-section + .landing-section {
  border-top: 1px solid rgba(255, 255, 255, 0.07);
}

/* A .landing-section, so width and horizontal padding come from that rule.
   Only the vertical padding differs: this section is one moving line, and the
   96px the reading sections use reads as a gap around nothing. */
.landing-section--typing {
  padding-top: 30px;
  padding-bottom: 30px;
}

/* The hairline between reading sections would land directly under this one,
   where it reads as the end of a block that has not begun. */
.landing-section--typing + .landing-section {
  border-top: 0;
}


.landing-eyeword {
  margin: 0 0 10px;
  color: #6ea8f0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.landing-title {
  margin: 0 0 16px;
  color: #fff;
  font-size: clamp(26px, 3.4vw, 40px);
  line-height: 1.15;
  font-weight: 650;
}

.landing-lead {
  margin: 0 0 40px;
  max-width: 62ch;
  font-size: 17px;
  line-height: 1.6;
  color: #a9bed6;
}

.landing-cards,
.landing-cases {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 20px;
}

/* Counts, not auto-fit: four cards and three cases divide evenly, and auto-fit
   left an orphan on the row below at exactly the widths a laptop uses. */
.landing-cards {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.landing-cases {
  grid-template-columns: 1fr;
}

/* Two, not four: there are four cases and their paragraphs run long enough
   that a four-across row would be a set of narrow ribbons. Three would drop
   the fourth onto a row of its own -- the orphan the cards above avoid. */
@media (min-width: 700px) {
  .landing-cases {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Нечётный последний -- во всю ширину. Случаев стало пять («Мечтатели»),
     и пятый встал бы сиротой рядом с пустотой: та самая беда, ради которой
     колонки выше заданы счётчиком, а не auto-fit. Правило считает позицию,
     а не число случаев, поэтому переживёт и шестой, и седьмой. */
  .landing-case:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

@media (min-width: 1000px) {
  .landing-cards {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .landing-cards {
    grid-template-columns: 1fr;
  }
}

.landing-card,
.landing-case {
  padding: 22px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
}

/* The mark sits beside the text, not above it: these cards are read down a
   column, and a glyph on its own line would push every heading further from
   the one before it. Aligned to the top so it lines up with the title however
   long the paragraph runs. */
.landing-case {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.landing-case-icon {
  display: block;
  width: 28px;
  height: 28px;
  /* Optical, not arithmetic: level with the cap height of the title beside it
     rather than with the top of its line box. */
  margin-top: 2px;
}

.landing-case-body {
  min-width: 0;
}

.landing-card-title,
.landing-case-title,
.landing-slide-title {
  margin: 0 0 8px;
  color: #fff;
  font-size: 17px;
  font-weight: 600;
}

.landing-card-text {
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  color: #a9bed6;
}

/* Publications are a reading list, not cards: no box, and the title carries the
   link so the whole line reads as one thing to click. */
.landing-pubs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 24px;
}

@media (min-width: 700px) {
  .landing-pubs {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 32px;
  }
}

/* Platform mark on the left, running the full height of the entry, so the
   reader sees where the link goes before reading a word of the title. The
   column is a fixed width and the mark contains itself inside it, which keeps
   the row steady whatever shape the next platform's logo turns out to be. */
.landing-pub {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 20px;
  padding-left: 16px;
  border-left: 2px solid rgba(110, 168, 240, 0.45);
}

/* The box stretches to the row, and `contain` fits the mark inside it without
   distorting whatever shape the next platform's logo happens to be. The column
   is about as wide as an entry is tall, so a square mark like Habr's ends up
   filling the height rather than sitting in a letterbox. */
.landing-pub-logo {
  width: 96px;
  height: 100%;
  align-self: stretch;
  object-fit: contain;
  opacity: 0.8;
  /* The file is a single black path; this lifts it onto the dark background. */
  filter: invert(1);
}

.landing-pub-body {
  min-width: 0;
}

.landing-pub-link {
  display: inline-block;
  margin-bottom: 8px;
  color: #fff;
  font-size: 17px;
  font-weight: 600;
  line-height: 1.3;
  text-decoration: none;
  border-bottom: 1px solid rgba(143, 192, 255, 0.5);
}

.landing-pub-link:hover {
  color: #8fc0ff;
  border-bottom-color: #8fc0ff;
}

.landing-final {
  text-align: center;
}

.landing-final .landing-lead {
  margin-left: auto;
  margin-right: auto;
}

.landing-footnote {
  margin: 20px 0 0;
  font-size: 14px;
}

.landing-footnote a,
.landing-footnote a:visited {
  color: #8fc0ff;
}

.landing-footer {
  max-width: 1080px;
  margin: 0 auto;
  padding: 40px 24px 56px;
  box-sizing: border-box;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  text-align: center;
  font-size: 14px;
}

.landing-footer-links {
  margin: 0 0 12px;
}

.landing-footer-links a,
.landing-footer-links a:visited {
  color: #8fc0ff;
}

.landing-footer-copy {
  margin: 0;
  color: #9db2cb;
}

/* --------------------------------------------------------------------------
   Examples carousel
   -------------------------------------------------------------------------- */

/* Before tiny-slider takes over, the slides are a plain column. Capping the
   height stops seven full-width screenshots from unrolling down the page in
   the moment between paint and init -- and it is also what a visitor without
   JS is left with. */
.landing-carousel:not(.tns-slider) {
  display: flex;
  gap: 20px;
  overflow: hidden;
  max-height: 420px;
}

.landing-carousel:not(.tns-slider) > .landing-slide {
  flex: 0 0 calc(33.333% - 14px);
}

.landing-slide {
  margin: 0;
}

/* tiny-slider sets the duration inline and leaves the timing function alone,
   so the ease-in-out lives here: the row gathers speed and settles instead of
   starting and stopping at full tilt. */
.landing-examples .tns-slider {
  transition-timing-function: cubic-bezier(0.33, 0, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) {
  .landing-examples .tns-slider {
    transition-duration: 0ms !important;
  }
}

/* A button, not a div: the full-screen view has to be reachable from the
   keyboard, and this is the control that opens it. */
.landing-slide-shot {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  overflow: hidden;
  background: #0b1a2e;
  cursor: zoom-in;
}

.landing-slide-shot:hover {
  border-color: rgba(143, 192, 255, 0.55);
}

.landing-slide-shot:focus-visible {
  outline: 2px solid #8fc0ff;
  outline-offset: 2px;
}

/* Muted at rest so the row reads as one texture rather than seven competing
   pictures, and the one under the cursor is the one being looked at. Only the
   hovered image lifts -- the caption stays legible either way. */
.landing-slide-image {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0.62;
  filter: saturate(0.65);
  transition: opacity 0.25s ease, filter 0.25s ease;
}

.landing-slide-shot:hover .landing-slide-image,
.landing-slide-shot:focus-visible .landing-slide-image {
  opacity: 1;
  filter: none;
}

@media (prefers-reduced-motion: reduce) {
  .landing-slide-image {
    transition: none;
  }
}

.landing-slide-body {
  padding: 14px 2px 0;
}

.landing-slide-title {
  margin: 0 0 6px;
  color: #fff;
  font-size: 17px;
  font-weight: 600;
}

/* The phrase that produced the map, quoted -- it is the argument the section
   is making, so it reads as speech rather than as another caption. */
.landing-slide-prompt {
  margin: 0 0 8px;
  color: #8fc0ff;
  font-size: 14px;
  line-height: 1.45;
}

/* tiny-slider's own controls, dressed to match the page and moved to the sides.
   Padding on .tns-outer is not an option -- the library sets `padding: 0
   !important` on it -- so the arrows step outside the track with negative
   offsets, and only where the page has margin to lend them. Narrower than that
   they sit on the edges of the outer slides, which is the lesser evil against
   being clipped off-screen. */
.landing-examples .tns-outer {
  position: relative;
}

.landing-examples .tns-controls {
  position: static;
}

.landing-examples .tns-controls button {
  position: absolute;
  /* Centred on the picture, not on the slide: the captions run to different
     lengths, and an arrow that drifts with them looks unmoored. */
  top: 88px;
  transform: translateY(-50%);
  z-index: 2;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 50%;
  background: rgba(8, 22, 40, 0.85);
  color: #dce6f2;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

.landing-examples .tns-controls button:first-child {
  left: 0;
}

.landing-examples .tns-controls button:last-child {
  right: 0;
}

/* Room to stand outside the track only once the page has margin to spare:
   1080 of content in 1200 of viewport leaves 60 a side. */
@media (min-width: 1200px) {
  .landing-examples .tns-controls button:first-child {
    left: -52px;
  }

  .landing-examples .tns-controls button:last-child {
    right: -52px;
  }
}

.landing-examples .tns-controls button:hover:not(:disabled) {
  border-color: #8fc0ff;
  color: #fff;
}

.landing-examples .tns-controls button:disabled {
  opacity: 0.35;
  cursor: default;
}

.landing-examples .tns-nav {
  margin-top: 10px;
  text-align: center;
}

.landing-examples .tns-nav button {
  width: 8px;
  height: 8px;
  padding: 0;
  margin: 0 4px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.25);
  cursor: pointer;
}

.landing-examples .tns-nav button.tns-nav-active {
  background: #8fc0ff;
}

/* --------------------------------------------------------------------------
   Full-screen example
   -------------------------------------------------------------------------- */

.landing-lightbox {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  box-sizing: border-box;
  background: rgba(3, 8, 16, 0.94);
  cursor: zoom-out;
}

.landing-lightbox-frame {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  max-width: 100%;
  max-height: 100%;
  cursor: default;
}

.landing-lightbox-image {
  max-width: 100%;
  /* Leaves the caption its line: 100% here would push it off the bottom on a
     short window. */
  max-height: calc(100% - 34px);
  border-radius: 8px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}

.landing-lightbox-caption {
  margin: 0;
  color: #c3d3e6;
  font-size: 15px;
  text-align: center;
}

/* Sit against the edges rather than beside the picture: it is as wide as the
   window allows, so there is no gutter to stand in. */
.landing-lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 50%;
  background: rgba(8, 22, 40, 0.75);
  color: #dce6f2;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}

.landing-lightbox-nav:hover {
  border-color: #8fc0ff;
  color: #fff;
}

.landing-lightbox-nav.is-prev {
  left: 16px;
}

.landing-lightbox-nav.is-next {
  right: 16px;
}

@media (max-width: 620px) {
  .landing-lightbox-nav {
    width: 40px;
    height: 40px;
    font-size: 22px;
  }

  .landing-lightbox-nav.is-prev {
    left: 6px;
  }

  .landing-lightbox-nav.is-next {
    right: 6px;
  }
}

.landing-lightbox-close {
  position: absolute;
  top: 16px;
  right: 20px;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}

.landing-lightbox-close:hover {
  background: rgba(255, 255, 255, 0.22);
}

@media (max-width: 720px) {
  .landing-section {
    padding: 64px 20px;
  }

  .landing-carousel:not(.tns-slider) > .landing-slide {
    flex: 0 0 100%;
  }

  /* 96px of mark against a 335px row left the title in a gutter. */
  .landing-pub {
    grid-template-columns: 56px minmax(0, 1fr);
    gap: 14px;
  }

  .landing-pub-logo {
    width: 56px;
  }
}

.map-footer-bar #about-btn {
  position: static;
  left: auto;
  bottom: auto;
}

.map-footer-bar #export-btn,
.map-footer-bar #map-edit-toggle {
  flex: 0 0 auto;
  padding: 8px 14px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}

.map-footer-bar #share-btn {
  flex: 0 0 auto;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}

.map-author {
  font-size: 14px;
  color: #2b3b47;
  white-space: nowrap;
}

.map-author a {
  color: #2b5f92;
  text-decoration: none;
}

#map-author-link {
  text-shadow:
    -1px -1px 0 #fff,
    1px -1px 0 #fff,
    -1px 1px 0 #fff,
    1px 1px 0 #fff,
    0 -1px 0 #fff,
    0 1px 0 #fff,
    -1px 0 0 #fff,
    1px 0 0 #fff;
}

.map-author a:hover {
  text-decoration: underline;
}

/* Clustered-point donut markers (DOM overlays, see map_point_layer.js). */
.cluster-donut {
  cursor: pointer;
}

/* POI view=marker clouds (maplibregl.Marker DOM, see map_poi_layer.js).
   Outer .poi-marker anchors at bottom (tip tip = lng/lat); card clips media. */
.poi-marker {
  --poi-marker-accent: #2f6fed;
  /* The card's top bar is this view's border, so bw/bc drive it. Resolution
     is lazy at the use site, so without bc the colour still follows accent. */
  --poi-marker-border-width: 3px;
  --poi-marker-border-color: var(--poi-marker-accent);
  position: relative;
  width: 140px;
  max-width: 160px;
  /* Room for the tip inside the box so Marker anchor:bottom hits the tip. */
  padding-bottom: 8px;
  cursor: pointer;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.18));
}

.poi-marker__card {
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid #d4dde5;
  border-top: var(--poi-marker-border-width) solid var(--poi-marker-border-color);
  border-radius: 6px;
  overflow: hidden;
}

/* Diamond tip: lowest vertex sits on the Marker lng/lat (anchor bottom). */
.poi-marker::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 1px;
  z-index: 1;
  width: 10px;
  height: 10px;
  margin-left: -5px;
  background: #fff;
  border-right: 1px solid #d4dde5;
  border-bottom: 1px solid #d4dde5;
  transform: rotate(45deg);
  pointer-events: none;
}

.poi-marker__img {
  display: block;
  width: 100%;
  height: 72px;
  object-fit: cover;
  background: #eef2f5;
}

.poi-marker__text {
  padding: 6px 8px 8px;
  font-size: 12px;
  line-height: 1.25;
  color: #1a2a38;
  overflow-wrap: anywhere;
}

/* Compact bubble when there is no photo (or image failed to load). */
.poi-marker--text {
  width: auto;
  max-width: 180px;
}

.poi-marker--text .poi-marker__text {
  padding: 8px 10px;
}

.poi-marker-cluster {
  min-width: 28px;
  height: 28px;
  padding: 0 7px;
  border-radius: 14px;
  background: #2f6fed;
  color: #fff;
  font: 600 12px/28px system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  text-align: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  cursor: pointer;
  border: 2px solid #fff;
}

#map-legend {
  position: fixed;
  right: 44px;
  bottom: 12px;
  z-index: 10;
  max-width: 360px;
  max-height: 50vh;
  overflow-y: auto;
  padding: 10px 12px;
  border: 1px solid #d4dde5;
  border-radius: 6px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 13px;
  color: #2b3b47;
}

#map-legend[hidden] {
  display: none;
}

/* Kept in markup for a11y hooks; not used as a drag/collapse control. */
#map-legend-toggle {
  display: none;
}

#map-legend.legend--moved {
  right: auto;
  bottom: auto;
}

#map-legend.legend--returning {
  transition: left 2s ease, top 2s ease;
}

#map-legend.legend--dragging {
  user-select: none;
}

/* Keep the attribution "i" control clickable above the legend. */
.maplibregl-ctrl-bottom-right {
  z-index: 11;
}

/* Zoom/compass (fixed) + separate vertical scale in the top-right stack. */
.mapus-zoom-scale {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  background: transparent;
  box-shadow: none;
}

.mapus-zoom-scale .maplibregl-ctrl-group {
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
}

.mapus-zoom-scale .maplibregl-ctrl-group button {
  width: 29px;
}

.mapus-vscale {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  box-sizing: border-box;
  padding: 6px 8px;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
  color: #333;
  font-size: 10px;
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
}

.mapus-vscale__ruler {
  width: 8px;
  box-sizing: border-box;
  border-color: #333;
  border-style: solid solid solid none;
  border-width: 2px;
}

.mapus-vscale__label {
  text-align: center;
}

/* Vendored maplibre-gl-scale-ratio, nested under .mapus-zoom-scale. */
.mapus-zoom-scale .scale-ratio-control {
  display: flex;
  align-items: center;
  gap: 2px;
  box-sizing: border-box;
  width: auto;
  min-width: 72px;
  max-width: 110px;
  padding: 4px 6px;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
  font-size: 11px;
  line-height: 1.2;
  color: #333;
}

.mapus-zoom-scale .scale-ratio-control.maplibregl-ctrl-group {
  background: #fff;
}

.mapus-zoom-scale .scale-ratio-control__label {
  margin: 0;
  flex: 0 0 auto;
}

.mapus-zoom-scale .scale-ratio-control__input {
  width: 4.5em;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  font: inherit;
  color: inherit;
  outline: none;
}

.mapus-zoom-scale .scale-ratio-control__input:focus {
  box-shadow: inset 0 -1px 0 #4a90d9;
}

.legend-title {
  font-family: "Playfair Display", Georgia, "Times New Roman", serif;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.25;
  color: #1a2a38;
  margin-bottom: 8px;
  cursor: grab;
  touch-action: none;
}

#map-legend.legend--dragging .legend-title {
  cursor: grabbing;
}

.legend-title:not(:last-child) {
  padding-bottom: 8px;
  border-bottom: 1px solid #e2e8ee;
}

/* Spacing in em, not px: the credit is a footnote to the rows above it, and
   the distance that reads as "separate" is a function of the text size, not of
   the screen. Wider above the rule than below -- the gap belongs to the legend
   it is leaving, and the padding keeps the rule from crowding the line. */
.legend-source {
  margin-top: 1.5em;
  padding-top: 0.9em;
  border-top: 1px solid #e2e8ee;
  font-size: 12px;
  line-height: 1.35;
  color: #6b7a88;
}

/* Легенда без цветных строк: только заголовок и подпись об источниках.
   Так выглядит карта одного объекта по ключу -- реки, водосбора, озера, -- у
   которого цвета в легенде нет вовсе.

   Оба серых правила здесь отчёркивают пустоту: под заголовком нет списка, над
   источниками нет строк, от которых их надо отделять. Правило, которое ничего
   не разделяет, читается как случайная черта, поэтому обе снимаются, а отступ
   между двумя строками остаётся один -- тот, что принадлежит подписи.

   Верхний отступ 0,7em, а не 1,5em: полтора рассчитаны на расстояние от
   СПИСКА, а от заголовка на таком расстоянии подпись отрывается и повисает
   сама по себе. */
#map-legend.legend--no-rows .legend-title:not(:last-child) {
  padding-bottom: 0;
  border-bottom: none;
  margin-bottom: 0;
}

#map-legend.legend--no-rows .legend-source {
  margin-top: 0.7em;
  padding-top: 0;
  border-top: none;
}

.legend-source a {
  color: inherit;
  text-decoration: underline;
}

.legend-source a:hover {
  color: #1a2a38;
}

.legend-row {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  padding: 2px 4px;
  margin: 0 -4px;
  border-radius: 4px;
  transition: opacity 0.15s ease, background-color 0.15s ease;
}

.legend-row + .legend-row {
  margin-top: 4px;
}

.legend-row:hover {
  background: #eef4fb;
}

.legend-row.active {
  background: #e2edf9;
  font-weight: 600;
}

.legend-row.inactive {
  opacity: 0.45;
}

.legend-swatch {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, 0.25);
}

#about-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.35);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#about-modal.open {
  display: flex;
}

#about-card {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
  padding: 20px 22px;
  width: 360px;
  max-width: calc(100vw - 32px);
}

#about-card h2 {
  margin: 0 0 12px;
  font-size: 17px;
  color: #222;
}

p.about-text {
  margin: 0 0 16px;
  font-size: 14px;
  line-height: 1.5;
  color: #444;
}

#about-actions {
  display: flex;
  justify-content: flex-end;
}

#about-actions button {
  font-size: 14px;
  padding: 7px 14px;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid #4a90d9;
  background: #4a90d9;
  color: #fff;
}

#about-actions button:hover {
  background: #3b7dc0;
  border-color: #3b7dc0;
}

#login-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(15, 23, 42, 0.45);
  /* Pushes the map back so the card reads as the only live thing on screen. */
  backdrop-filter: blur(2px);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#login-modal.open {
  display: flex;
}

#login-card {
  background: #fff;
  border-radius: 14px;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.28);
  padding: 26px 26px 22px;
  width: 360px;
  max-width: calc(100vw - 32px);
  overflow: visible;
  animation: login-card-in 160ms ease-out;
}

@keyframes login-card-in {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

/* Motion is decoration here; the card is just as usable without it. */
@media (prefers-reduced-motion: reduce) {
  #login-card { animation: none; }
}

/* Field-anchored validation tips: absolute overlays, no layout shift. */
.field-wrap {
  position: relative;
  margin-bottom: 12px;
}

.field-wrap > label {
  display: block;
  margin-bottom: 4px;
  font-size: 13px;
  color: #444;
}

.field-wrap > input,
.field-wrap .password-field > input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 0;
  padding: 10px 12px;
  font-size: 14px;
  border: 1px solid #ccd6df;
  border-radius: 8px;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

/* A ring, not just a border tint: on a white card a one-pixel colour change
   is not enough to find the caret with the keyboard. */
.field-wrap > input:focus,
.field-wrap .password-field > input:focus {
  outline: none;
  border-color: #4a90d9;
  box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.18);
}

.field-wrap > input.field-invalid,
.field-wrap .password-field > input.field-invalid {
  border-color: #c0392b;
  box-shadow: 0 0 0 2px rgba(192, 57, 43, 0.18);
}

/* Password fields carry a reveal button inside the box, so the input needs
   room on the right for it and the two share a positioning context. */
.password-field {
  position: relative;
}

/* Specific enough to beat `.field-wrap .password-field > input`, which sets the
   padding shorthand a few rules above and would otherwise put the text back
   under the button. */
.field-wrap .password-field > input {
  padding-right: 44px;
}

/* The eye is drawn in CSS rather than shipped as an icon: an ellipse and a
   pupil, struck through once the password is visible. One less request, and it
   inherits the field's colour. */
.password-reveal {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: #7c8b99;
  cursor: pointer;
}

.password-reveal:hover {
  color: #33475b;
  background: rgba(74, 144, 217, 0.08);
}

.password-reveal:focus-visible {
  outline: 2px solid #4a90d9;
  outline-offset: 1px;
}

.password-reveal::before {
  content: "";
  position: absolute;
  inset: 9px 5px;
  border: 1.6px solid currentColor;
  /* Round at the sides, pointed at the corners: an eye. */
  border-radius: 50% / 60%;
}

.password-reveal::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  background: currentColor;
}

/* Shown: strike it through, the usual sign for «нажмите, чтобы скрыть». */
.password-reveal.is-shown::after {
  width: 22px;
  height: 1.6px;
  margin: -0.8px 0 0 -11px;
  border-radius: 0;
  transform: rotate(-45deg);
}

.field-tip {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 8;
  margin: 0;
  padding: 6px 8px;
  box-sizing: border-box;
  font-size: 12px;
  line-height: 1.35;
  color: #8b1e14;
  background: #fff5f4;
  border: 1px solid #f0c2bd;
  border-radius: 6px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  pointer-events: none;
}

.field-tip::before {
  content: "";
  position: absolute;
  left: 14px;
  top: -5px;
  width: 8px;
  height: 8px;
  background: #fff5f4;
  border-left: 1px solid #f0c2bd;
  border-top: 1px solid #f0c2bd;
  transform: rotate(45deg);
}

.field-tip[hidden] {
  display: none;
}

.field-tip-success {
  color: #1e7a3a;
  background: #eaf7ee;
  border-color: #b7e0c2;
}

.field-tip-success::before {
  background: #eaf7ee;
  border-left-color: #b7e0c2;
  border-top-color: #b7e0c2;
}

.auth-form-footer {
  position: relative;
}

/* Form-level auth status: static banner above the form, not a field bubble. */
.field-tip-form {
  position: static;
  top: auto;
  bottom: auto;
  left: auto;
  right: auto;
  z-index: auto;
  margin: 0 0 14px;
  padding: 8px 10px;
  box-shadow: none;
  border-radius: 8px;
  font-size: 13px;
  pointer-events: auto;
}

.field-tip-form::before {
  content: none;
  display: none;
}

#login-card h2 {
  margin: 0 0 18px;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: #1e293b;
}

/* More specific than the UA [hidden] rule; without this, register/forgot
   modes keep showing the password field (display:block wins). */
#login-form label[hidden],
#login-form .field-wrap[hidden],
#login-form input[hidden] {
  display: none;
}

.login-captcha {
  margin-bottom: 12px;
  min-height: 100px;
}

/* Legacy block errors kept for non-auth pages; auth forms use .field-tip. */
.login-error {
  margin-bottom: 12px;
  padding: 8px 10px;
  border-radius: 6px;
  background: #fdecec;
  color: #c0392b;
  font-size: 13px;
}

.login-success {
  margin-bottom: 12px;
  padding: 8px 10px;
  border-radius: 6px;
  background: #eaf7ee;
  color: #1e7a3a;
  font-size: 13px;
}

.login-oauth {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 8px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid #e4ebf2;
}

.login-oauth-btn {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 0;
  text-align: center;
  text-decoration: none;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
  padding: 10px 6px;
  border-radius: 8px;
  border: 1px solid #ccd6df;
  background: #fff;
  color: #222;
}

.login-oauth-btn:hover {
  background: #f5f8fb;
  border-color: #4a90d9;
}

.login-oauth-icon {
  display: block;
  width: 22px;
  height: 22px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
}

.login-oauth-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.login-oauth-google .login-oauth-icon {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23EA4335' d='M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z'/%3E%3Cpath fill='%234285F4' d='M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z'/%3E%3Cpath fill='%23FBBC05' d='M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z'/%3E%3Cpath fill='%2334A853' d='M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z'/%3E%3C/svg%3E");
}

.login-oauth-yandex .login-oauth-icon {
  /* Official Yandex mark (yastatic home-static «Я» disc); source: static/images/oauth-yandex.svg */
  background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.04%2012c0-5.523%204.476-10%2010-10%205.522%200%2010%204.477%2010%2010s-4.478%2010-10%2010c-5.524%200-10-4.477-10-10z%22%20fill%3D%22%23FC3F1D%22%2F%3E%3Cpath%20d%3D%22M13.32%207.666h-.924c-1.694%200-2.585.858-2.585%202.123%200%201.43.616%202.1%201.881%202.959l1.045.704-3.003%204.487H7.49l2.695-4.014c-1.55-1.111-2.42-2.19-2.42-4.015%200-2.288%201.595-3.85%204.62-3.85h3.003v11.868H13.32V7.666z%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E");
}

.login-oauth-github .login-oauth-icon {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2324292F' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E");
}

.login-oauth-vk .login-oauth-icon {
  /* Official VK mark (simple-icons `vk`): one path whose lettering winds
     against the squircle, so the default nonzero rule knocks it out as a
     hole rather than filling it solid. */
  background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%230077FF' d='m9.489.004.729-.003h3.564l.73.003.914.01.433.007.418.011.403.014.388.016.374.021.36.025.345.03.333.033c1.74.196 2.933.616 3.833 1.516.9.9 1.32 2.092 1.516 3.833l.034.333.029.346.025.36.02.373.025.588.012.41.013.644.009.915.004.98-.001 3.313-.003.73-.01.914-.007.433-.011.418-.014.403-.016.388-.021.374-.025.36-.03.345-.033.333c-.196 1.74-.616 2.933-1.516 3.833-.9.9-2.092 1.32-3.833 1.516l-.333.034-.346.029-.36.025-.373.02-.588.025-.41.012-.644.013-.915.009-.98.004-3.313-.001-.73-.003-.914-.01-.433-.007-.418-.011-.403-.014-.388-.016-.374-.021-.36-.025-.345-.03-.333-.033c-1.74-.196-2.933-.616-3.833-1.516-.9-.9-1.32-2.092-1.516-3.833l-.034-.333-.029-.346-.025-.36-.02-.373-.025-.588-.012-.41-.013-.644-.009-.915-.004-.98.001-3.313.003-.73.01-.914.007-.433.011-.418.014-.403.016-.388.021-.374.025-.36.03-.345.033-.333c.196-1.74.616-2.933 1.516-3.833.9-.9 2.092-1.32 3.833-1.516l.333-.034.346-.029.36-.025.373-.02.588-.025.41-.012.644-.013.915-.009ZM6.79 7.3H4.05c.13 6.24 3.25 9.99 8.72 9.99h.31v-3.57c2.01.2 3.53 1.67 4.14 3.57h2.84c-.78-2.84-2.83-4.41-4.11-5.01 1.28-.74 3.08-2.54 3.51-4.98h-2.58c-.56 1.98-2.22 3.78-3.8 3.95V7.3H10.5v6.92c-1.6-.4-3.62-2.34-3.71-6.92Z'/></svg>");
}

.login-oauth-vk {
  border-color: #b3d4ff;
  color: #0058c7;
}

.login-oauth-google {
  border-color: #dadce0;
}

.login-oauth-yandex {
  border-color: #f5c4bb;
  color: #c23018;
}

.login-oauth-github {
  border-color: #c9ced4;
  color: #24292f;
}

.login-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px;
  margin-top: 12px;
}

.login-link {
  border: 0;
  background: none;
  padding: 0;
  color: #2b5f92;
  font-size: 13px;
  cursor: pointer;
  text-decoration: underline;
}

.login-link:hover {
  color: #1a3f66;
}

/* Remember-me on the left, buttons on the right: the row was half empty, so
   the choice costs no extra height. */
.auth-actions-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

#login-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  /* Keeps the buttons right-aligned when the label is hidden (register and
     reset have no session to remember). */
  margin-left: auto;
}

.auth-remember {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  font-size: 13px;
  color: #475569;
}

.auth-remember[hidden] {
  display: none;
}

/* The native input stays in the layout -- moved out of sight, not removed --
   so it keeps focus, keyboard toggling and the label association. */
.auth-remember input {
  position: absolute;
  opacity: 0;
  width: 18px;
  height: 18px;
  margin: 0;
  cursor: pointer;
}

.auth-checkbox {
  position: relative;
  flex: none;
  width: 18px;
  height: 18px;
  box-sizing: border-box;
  border: 1px solid #ccd6df;
  border-radius: 5px;
  background: #fff;
  transition: background-color 120ms ease, border-color 120ms ease;
}

/* The tick is two borders of an empty box, rotated. No glyph, so it cannot
   come out as a missing-font rectangle. */
.auth-checkbox::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 1px;
  width: 5px;
  height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0.6);
  opacity: 0;
  transition: opacity 120ms ease, transform 120ms ease;
}

.auth-remember:hover .auth-checkbox {
  border-color: #4a90d9;
}

.auth-remember input:checked + .auth-checkbox {
  background: #4a90d9;
  border-color: #4a90d9;
}

.auth-remember input:checked + .auth-checkbox::after {
  opacity: 1;
  transform: rotate(45deg) scale(1);
}

/* Keyboard users get the ring; mouse users do not, so a click leaves no halo. */
.auth-remember input:focus-visible + .auth-checkbox {
  border-color: #4a90d9;
  box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.22);
}

@media (prefers-reduced-motion: reduce) {
  .auth-checkbox,
  .auth-checkbox::after {
    transition: none;
  }
}

#login-actions button {
  font-size: 14px;
  font-weight: 600;
  padding: 8px 16px;
  border-radius: 8px;
  cursor: pointer;
  border: 1px solid #4a90d9;
  background: #4a90d9;
  color: #fff;
  transition: background-color 120ms ease, border-color 120ms ease;
}

#login-actions button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.3);
}

#login-actions button:hover {
  background: #3b7dc0;
  border-color: #3b7dc0;
}

#login-cancel {
  background: #fff;
  color: #2b5f92;
}

#login-cancel:hover {
  background: #eef4fb;
  border-color: #4a90d9;
}

@media (max-width: 640px) {
  .home-gate {
    padding: 20px 16px;
  }

  .home-gate-logo {
    width: min(280px, 84vw);
    max-height: 88px;
    margin-bottom: 24px;
  }

  .home-gate-title {
    font-size: 26px;
    margin-bottom: 6px;
  }

  .home-gate-text {
    font-size: 16px;
    line-height: 1.5;
    margin-bottom: 16px;
    max-width: none;
  }

  .home-gate-login {
    width: 100%;
    max-width: 280px;
  }

  .site-logo--float {
    display: none;
  }

  .site-logo--menu {
    display: block;
  }

  #toolbar .site-logo--menu {
    display: block;
  }

  #toolbar {
    left: 8px;
    right: 8px;
    top: 8px;
    flex-wrap: wrap;
    gap: 6px 8px;
    padding: 8px;
  }

  /* Show the hamburger and let it sit on its own row. */
  #toolbar-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    font-size: 18px;
    padding: 6px 12px;
  }

  /* When collapsed, shrink the toolbar to just the toggle button. */
  #toolbar.collapsed {
    right: auto;
  }

  #toolbar.collapsed > *:not(#toolbar-toggle) {
    display: none;
  }

  /* Input takes a full row and shrinks freely. */
  #word {
    flex: 1 1 100%;
    min-width: 0;
    font-size: 16px; /* prevents auto-zoom on focus in iOS Safari */
  }

  /* Label+field groups share a row and stay intact. */
  .field-group {
    flex: 1 1 auto;
    min-width: 0;
    gap: 6px;
  }

  /* Framed clustering group spans the full row and wraps cleanly. */
  .cluster-box {
    flex: 1 1 100%;
    flex-wrap: wrap;
    margin: 0;
  }

  #user-country,
  #threshold {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 16px;
  }

  /* Buttons stretch into comfortable touch targets. */
  #translate-btn,
  #export-btn {
    flex: 1 1 auto;
    font-size: 15px;
    padding: 8px 12px;
  }

  #about-btn {
    font-size: 13px;
    padding: 7px 12px;
  }

  /* Footer bar controls are replaced by the hamburger menu on mobile. The bar
     itself stays in the DOM so the included #about-modal keeps working. */
  .map-footer-bar #about-btn,
  .map-footer-bar #export-btn,
  .map-footer-bar #map-edit-toggle,
  .map-footer-bar #share-btn,
  .map-footer-bar #map-author {
    display: none;
  }

  /* Hamburger menu, top-left. */
  #map-menu-wrap {
    display: block;
    position: fixed;
    left: 12px;
    top: 12px;
    z-index: 20;
  }

  #map-menu-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: 1px solid #d4dde5;
    border-radius: 6px;
    background: #fff;
    color: #2b5f92;
    font-size: 20px;
    line-height: 1;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    cursor: pointer;
  }

  #map-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    min-width: 190px;
    background: #fff;
    border: 1px solid #d4dde5;
    border-radius: 6px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  #map-menu[hidden] {
    display: none;
  }

  .map-menu-item {
    display: block;
    width: 100%;
    padding: 11px 14px;
    border: 0;
    border-bottom: 1px solid #eef2f6;
    background: none;
    font-size: 15px;
    color: #2b5f92;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
  }

  .map-menu-item:last-child {
    border-bottom: 0;
  }

  .map-menu-item:hover {
    background: #eef4fb;
  }

  /* Legend is controlled from the menu: hidden until opened, no own toggle. */
  #map-legend {
    right: 44px;
    bottom: 12px;
    max-width: min(240px, calc(100vw - 72px));
    max-height: 45vh;
    overflow-y: auto;
    font-size: 12px;
    padding: 10px 12px;
    display: none;
  }

  #map-legend.mobile-open {
    display: block;
  }

  #map-legend-toggle {
    display: none;
  }

  .legend-title {
    font-size: 13px;
    margin-bottom: 6px;
  }

  .legend-title:not(:last-child) {
    padding-bottom: 6px;
  }

  .legend-row {
    gap: 6px;
    padding: 2px 3px;
    margin: 0 -3px;
  }

  .legend-swatch {
    width: 12px;
    height: 12px;
  }

  .ai-dock {
    width: calc(100vw - 16px);
    bottom: 8px;
  }

  #ai-dock-toggle {
    font-size: 15px;
    padding: 10px 14px;
  }

  /* Палец крупнее курсора: круглые кнопки ряда растут вместе с отправкой. */
  .ai-round {
    width: 30px;
    height: 30px;
  }

  #ai-prompt {
    font-size: 16px;
  }

  /* `flex: 1` снято вместе со строкой, которую эти кнопки занимали: теперь они
     стоят в полосе композитора, и растянутая на треть ширины «Сброс» вытеснила
     бы оттуда лоток. Кнопке отправки другое: палец крупнее курсора, 32 вместо
     28. */
  .ai-send {
    width: 32px;
    height: 32px;
  }

  /* Прокрутка тела теперь общая для всех ширин; здесь остаётся только
     потолок: на телефоне док не должен занимать больше двух пятых экрана. */
  .ai-dock-body {
    max-height: 40vh;
  }
}

/* Set-password page (templates/set_password.html). Reuses the login modal's
   input/error styling; only the page frame is new. */
.set-password-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  box-sizing: border-box;
  background: #eef2f6;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.set-password-card {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
  padding: 26px 28px;
  width: 400px;
  max-width: 100%;
  overflow: visible;
}

.set-password-card h1 {
  margin: 0 0 12px;
  font-size: 20px;
  color: #222;
}

.set-password-intro {
  margin: 0 0 16px;
  font-size: 14px;
  line-height: 1.5;
  color: #555;
}

#set-password-form .field-wrap > label {
  display: block;
  margin-bottom: 4px;
  font-size: 13px;
  color: #444;
}

.set-password-policy {
  margin: 0 0 12px;
  font-size: 12px;
  line-height: 1.4;
  color: #6b7885;
}

#set-password-submit {
  width: 100%;
  padding: 9px 14px;
  font-size: 14px;
  color: #fff;
  background: #4a90d9;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

#set-password-submit:hover {
  background: #3b7fc4;
}

#set-password-submit:disabled {
  opacity: 0.6;
  cursor: default;
}

/* Опубликованные карты автора: счётчик на профиле и сам список.
   Ширина карточки шире профильной -- у названий карт своя длина, и в 480px
   они переносятся по два-три слова, превращая список в лесенку. */
.profile-maps {
  margin: 0 0 20px;
  color: #475569;
}

.profile-maps a {
  color: #2b5f92;
  text-decoration: none;
}

.profile-maps a:hover {
  text-decoration: underline;
}

.maps-card {
  max-width: 640px;
  text-align: left;
}

.maps-card .profile-name {
  text-align: center;
}

.maps-count {
  margin: 0 0 20px;
  text-align: center;
  color: #64748b;
  font-size: 0.95rem;
}

.maps-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.maps-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid #e2e8f0;
}

.maps-item:last-child {
  border-bottom: none;
}

.maps-link {
  color: #1e293b;
  text-decoration: none;
}

.maps-link:hover {
  color: #2b5f92;
  text-decoration: underline;
}

/* Дата не переносится и не сжимается: столбец дат -- то, по чему список
   читают глазами сверху вниз. */
.maps-date {
  flex: none;
  color: #94a3b8;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

.maps-empty {
  margin: 0;
  color: #64748b;
}

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

.maps-pager-link {
  color: #2b5f92;
  text-decoration: none;
}

.maps-pager-link:hover {
  text-decoration: underline;
}

/* Край списка -- это не кнопка: серым и без указателя, чтобы по ней не
   щёлкали впустую. */
.maps-pager-link.is-off {
  color: #cbd5e1;
  cursor: default;
}

.maps-pager-at {
  color: #64748b;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
}
