/* CAD panel — clean, light, system fonts. */
:root {
  --bg: #eef1f5;
  --card: #ffffff;
  --ink: #1c2330;
  --muted: #5b6577;
  --line: #d7dde6;
  --accent: #2563eb;
  --accent-dark: #1d4fd7;
  --danger: #b3261e;
  --ok: #1a7f37;

  /* Surfaces and status tints. --field is the raised chrome of form controls
     and secondary buttons on a --card; --subtle/--subtle-2 are the two
     recessed block tints; the *-bg/-line/-ink triples tint the info, success
     and danger blocks. Everything else reads these too, so the dark theme
     below only has to restate this block. */
  --field: #ffffff;
  --field-disabled: #f2f4f8;
  --subtle: #f7f9fc;
  --subtle-2: #fafbfd;
  --chip: #e6ebf3;
  --info-bg: #eef4ff;
  --info-line: #c9dcff;
  --info-ink: #1a3a8f;
  --ok-bg: #eef7ee;
  --ok-line: #c6e6c6;
  --ok-ink: #1a6b2f;
  --danger-bg: #fdf0ee;
  --danger-line: #f3c6c0;
  --error-bg: #fdf6f5;
  --shadow: rgba(16, 24, 40, 0.05);
  --shadow-menu: rgba(16, 24, 40, 0.16);
  --shadow-logo: rgba(16, 24, 40, 0.22);
  --spinner-track: rgba(255, 255, 255, 0.45);
  --spinner-track-soft: rgba(16, 24, 40, 0.3);
  color-scheme: light;

  /* Fluid page shell: header, content and footer share one width that fills
     the window — only the gutters scale with the viewport. --shell-max is
     the single knob that keeps a 4K window from stretching cards into an
     unreadable line; everything inside re-flows into columns before that. */
  --gutter: clamp(14px, 2.4vw, 40px);
  --shell-max: 1800px;
  --gap: clamp(12px, 1.1vw, 20px);
}

/* Dark theme: the same variable contract with inverted surfaces. theme.js puts
   data-theme on <html> before first paint, so there is no light flash; the
   attribute is what every rule above keys off. color-scheme makes the native
   widgets (select popups, scrollbars, autofill) follow along. */
:root[data-theme="dark"] {
  --bg: #10141b;
  --card: #171c25;
  --ink: #e6eaf1;
  --muted: #98a2b3;
  --line: #2a323d;
  --accent: #5b8def;
  --accent-dark: #4a7de0;
  --danger: #ff6b5e;
  --ok: #4ad07a;

  --field: #1d232d;
  --field-disabled: #232a35;
  --subtle: #1c222c;
  --subtle-2: #191f28;
  --chip: #232a35;
  --info-bg: #16243d;
  --info-line: #27406b;
  --info-ink: #a8c4ff;
  --ok-bg: #14291b;
  --ok-line: #245231;
  --ok-ink: #7fd99a;
  --danger-bg: #2b1a18;
  --danger-line: #5a2f2a;
  --error-bg: #241a19;
  --shadow: rgba(0, 0, 0, 0.35);
  --shadow-menu: rgba(0, 0, 0, 0.55);
  --shadow-logo: rgba(0, 0, 0, 0.5);
  --spinner-track-soft: rgba(255, 255, 255, 0.28);
  color-scheme: dark;
}

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 system-ui, "Segoe UI", Roboto, sans-serif;
}
.top, main, .foot {
  width: 100%;
  max-width: var(--shell-max);
  margin: 0 auto;
  padding-inline: var(--gutter);
}
.top { padding-block: 22px 6px; }
main { padding-block: 12px 40px; }
.top h1 { margin: 0 0 2px; font-size: clamp(20px, 0.7vw + 14px, 26px); }
.sub { margin: 0; color: var(--muted); }

/* Brand tile beside the page titles. The artwork is a dark rounded square,
   so the logo keeps its own corners; drop-shadow follows the alpha shape. */
.top-title { display: flex; align-items: center; gap: 12px; min-width: 0; }
.brand-head { min-width: 0; }
.brand-logo {
  flex: none;
  width: 44px;
  height: auto;
  filter: drop-shadow(0 1px 3px var(--shadow-logo));
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 16px;
  margin-top: 14px;
  box-shadow: 0 1px 2px var(--shadow);
}
.form-card label { display: block; font-weight: 600; margin-bottom: 6px; }
textarea {
  width: 100%;
  resize: vertical;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font: inherit;
  background: var(--field);
  color: inherit;
}
textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
textarea:disabled { background: var(--field-disabled); color: var(--muted); }
.form-row { display: flex; align-items: center; gap: 10px 12px; margin-top: 10px; flex-wrap: wrap; }
.hint { color: var(--muted); font-size: 13px; }

button {
  font: inherit;
  font-weight: 600;
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 8px 16px;
  cursor: pointer;
}
button:disabled { opacity: 0.55; cursor: not-allowed; }
.btn-spinner {
  display: inline-block;
  width: 14px; height: 14px;
  border: 2px solid var(--spinner-track);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  vertical-align: -2px;
  margin-right: 6px;
}
.primary { background: var(--accent); color: #fff; }
.primary:hover:not(:disabled) { background: var(--accent-dark); }
.secondary { background: var(--field); border-color: var(--line); color: var(--ink); }
.secondary:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.ghost { background: transparent; color: var(--muted); }
.ghost:hover:not(:disabled) { color: var(--ink); }

.inline-error {
  margin-top: 10px;
  color: var(--danger);
  background: var(--danger-bg);
  border: 1px solid var(--danger-line);
  border-radius: 8px;
  padding: 8px 12px;
  white-space: pre-line;
}

.status {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
  padding: 10px 14px;
  background: var(--info-bg);
  border: 1px solid var(--info-line);
  border-radius: 10px;
  color: var(--info-ink);
}
.status[hidden] { display: none; }
.spinner {
  width: 16px; height: 16px;
  border: 2px solid var(--info-line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
  flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Transient notice, bottom-center, for an action the client refuses while a
   build/export runs. The header/result buttons are dimmed by :disabled, but
   the per-history buttons stay enabled — without this a click there was a
   silent no-op. One reused element: showToast restarts the hide timer. */
.toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 50;
  max-width: min(92vw, 560px);
  padding: 10px 16px;
  border: 1px solid var(--info-line);
  border-radius: 10px;
  background: var(--info-bg);
  color: var(--info-ink);
  box-shadow: 0 8px 24px var(--shadow-menu);
  font-size: 14px;
  text-align: center;
  animation: toast-in 0.18s ease-out;
}
.toast[hidden] { display: none; }
@keyframes toast-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

/* The original user prompt of a run, collapsed under "Промпт" — in the
   current result card (full-width above the preview grid) and per history
   item. The input box is cleared after a build, so this is where the text
   stays readable. */
.prompt-block {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--subtle);
  padding: 4px 12px 6px;
  font-size: 13px;
}
.result-card > .prompt-block { margin-bottom: 14px; }
.prompt-block summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--muted);
  user-select: none;
}
.prompt-block[open] summary { margin-bottom: 4px; }
.prompt-block summary:hover { color: var(--accent); }
.prompt-text {
  margin: 0;
  white-space: pre-line;
  overflow-wrap: anywhere;
  color: var(--ink);
}

/* The preview and the text split the card proportionally instead of a fixed
   460px ceiling, so the pair keeps a readable ratio at any window size;
   below 900px they stack. */
.result-grid { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.55fr); gap: clamp(14px, 1.5vw, 26px); }
@media (max-width: 900px) { .result-grid { grid-template-columns: 1fr; } }
.gif-wrap { margin: 0; }
#gif { width: 100%; border-radius: 8px; background: var(--field) url() center/contain no-repeat; }
.result-text { white-space: pre-line; overflow-wrap: anywhere; }
.result-text a { color: var(--accent); }
.actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.note { margin-top: 10px; color: var(--ok); font-size: 13px; }

/* "Импорт в файл" opens a small popup with one entry per downloadable format
   (STEP or STL), so a click picks the file instead of downloading both. The
   entry of an already-downloaded format is marked with a check. */
.export-picker { position: relative; display: inline-block; }
.menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  min-width: 100%;
  padding: 4px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 6px 18px var(--shadow-menu);
}
.menu[hidden] { display: none; }
.menu-item {
  background: transparent;
  border: none;
  border-radius: 6px;
  padding: 7px 12px;
  text-align: left;
  white-space: nowrap;
}
.menu-item:hover:not(:disabled) { background: var(--info-bg); color: var(--accent); }

.error-card {
  border-color: var(--danger-line);
  background: var(--error-bg);
  white-space: normal;
}
.error-card p { margin: 6px 0; }
.error-card pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px;
  font-size: 12.5px;
  max-height: 320px;
  overflow: auto;
}

#history-wrap h2 { font-size: 15px; color: var(--muted); margin: 22px 0 4px; }
.hist-item {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  margin-top: 8px;
  background: var(--subtle-2);
}
/* Each history row is a <details>: the collapsed <summary> holds the preview
   GIF, the request id and the description; expanded content below adds
   validation/time/cost, the prompt and the action buttons. The native
   disclosure triangle is hidden — a chevron on the right edge hints that the
   row expands. */
.hist-item > summary {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  cursor: pointer;
  list-style: none; /* Firefox marker */
}
.hist-item > summary::-webkit-details-marker { display: none; }
.hist-item > summary::after {
  content: "";
  flex: none;
  align-self: center;
  margin-left: auto;
  border-left: 6px solid var(--muted);
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  opacity: 0.55;
  transition: transform 0.15s ease;
}
.hist-item[open] > summary::after { transform: rotate(90deg); }
.hist-gif { width: clamp(104px, 7.5vw, 150px); flex: none; border-radius: 6px; }
.hist-caption { flex: 1; min-width: 0; }
.hist-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}
.hist-id { color: var(--muted); font-size: 13px; }
.hist-id b { color: var(--ink); font-weight: 700; }
.hist-meta { color: var(--muted); font-size: 13px; flex: none; }
.hist-desc {
  display: block;
  margin-top: 3px;
  font-size: 14px;
  white-space: pre-line;
  overflow-wrap: anywhere;
}
.hist-detail {
  margin-top: 8px;
  border-top: 1px dashed var(--line);
  padding-top: 6px;
}
.hist-line {
  margin: 3px 0;
  font-size: 13px;
  color: var(--muted);
  white-space: pre-line;
  overflow-wrap: anywhere;
}
.hist-item .prompt-block { margin-top: 8px; }
.hist-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.hist-actions button { font: inherit; font-size: 13px; padding: 4px 10px; }
.hist-actions button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.hist-actions button.danger { color: var(--danger); }
.hist-actions button.danger:hover:not(:disabled) {
  color: var(--danger);
  border-color: var(--danger-line);
  background: var(--danger-bg);
}
/* The stock .btn-spinner is white-on-primary; secondary/history buttons need
   a dark spinner on their light background. */
.secondary .btn-spinner, .hist-actions .btn-spinner {
  border-color: var(--spinner-track-soft);
  border-top-color: var(--accent);
  width: 12px; height: 12px; margin-right: 6px; vertical-align: -1px;
}

.foot { padding-block: 0 26px; color: var(--muted); font-size: 12.5px; }

/* Header nav (profile / logout) and auth pages. */
.top-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.top-nav { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; }
.top-nav[hidden] { display: none; }
.user-chip {
  color: var(--muted);
  background: var(--chip);
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 13px;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.btn-nav {
  font-weight: 600;
  text-decoration: none;
  color: var(--ink);
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 16px;
}
.btn-nav:hover { border-color: var(--accent); color: var(--accent); }

/* Theme switch, present in the header of every page (and above the card on
   the login page, which has no header). The button names the subject and shows
   the CURRENT mode as an icon; a click advances light -> dark -> system. The
   icon is inline SVG stroked with currentColor, so it picks up the hover
   accent, and the mode name it cannot convey lives in the title/aria-label. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--field);
  border-color: var(--line);
  color: var(--ink);
  white-space: nowrap;
}
.theme-toggle:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.theme-icon {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.auth-tools { display: flex; justify-content: flex-end; margin-top: 12px; }

input[type="text"], input[type="password"] {
  width: 100%;
  padding: 10px 12px;
  margin: 4px 0 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font: inherit;
  display: block;
  background: var(--field);
  color: inherit;
}
input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
input:disabled { background: var(--field-disabled); color: var(--muted); }

.auth-main { max-width: 420px; margin: 0 auto; padding: 12px 20px; }
.auth-card { margin-top: 48px; padding: 24px; }
.auth-logo {
  display: block;
  width: 84px;
  height: auto;
  margin: 0 auto 14px;
  filter: drop-shadow(0 2px 6px var(--shadow-logo));
}
.auth-card h1 { margin: 0 0 2px; font-size: 22px; }
.auth-card .sub { margin-bottom: 18px; }
.auth-card label { display: block; font-weight: 600; margin-bottom: 2px; }
.auth-card button[type="submit"] { width: 100%; margin-top: 6px; }

.profile-h2 { font-size: 15px; margin: 14px 0 4px; }
.login-line { margin: 0 0 6px; color: var(--muted); }
.meta-label { color: var(--muted); margin-right: 8px; }

/* Roles, user table (admin page). */
.role-chip {
  display: inline-block;
  border-radius: 999px;
  padding: 2px 10px;
  margin-left: 8px;
  font-size: 12.5px;
  font-weight: 600;
  vertical-align: 1px;
}
.role-chip.ADMIN { background: var(--info-bg); border: 1px solid var(--info-line); color: var(--info-ink); }
.role-chip.USER { background: var(--ok-bg); border: 1px solid var(--ok-line); color: var(--ok-ink); }
select {
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--field);
  color: inherit;
  width: 100%;
  margin: 4px 0 12px;
}
button.danger { color: var(--danger); }
button.danger:hover:not(:disabled) { background: var(--danger-bg); border-color: var(--danger-line); }
.users-table { width: 100%; max-width: 100%; border-collapse: collapse; margin-top: 6px; }
.users-table th, .users-table td { overflow-wrap: break-word; }
.users-table th {
  text-align: left;
  color: var(--muted);
  font-size: 13px;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
}
.users-table td { padding: 8px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.users-table tr:last-child td { border-bottom: none; }
.users-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.users-actions button.active {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--info-bg);
}
.meta-cell { color: var(--muted); font-size: 13px; }
.pw-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pw-row input { flex: 1 1 190px; width: auto; min-width: 0; max-width: 100%; margin: 0; }
.pw-row select { width: auto; margin: 0; }
.pw-row select.model-select { flex: 1 1 200px; width: auto; min-width: 0; max-width: 100%; }
.pw-row button { padding: 6px 12px; }

/* Model catalog (llm_models.json) + per-user assignment display. */
.models-list { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.model-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.model-name { font-weight: 600; }
.model-id {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 12px;
  color: var(--muted);
  background: var(--field-disabled);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1px 7px;
}
.chip-default {
  color: var(--ok-ink);
  background: var(--ok-bg);
  border: 1px solid var(--ok-line);
  border-radius: 999px;
  padding: 1px 9px;
  font-size: 12px;
  font-weight: 600;
}

/* ------------------------------------------------------------ page layouts
   Nothing below pins a content width: blocks fill the window (up to
   --shell-max) and split into columns as soon as there is room for them, so a
   1600px window gets a laid-out page instead of a stranded 980px column.

   .stack is the one idiom for a vertical run of blocks — the gap lives on the
   grid and children drop their own top margins, so the same markup works in a
   one-column and a multi-column parent. */

/* One flexible column: an implicit `auto` track would be sized by the widest
   child's min-content (the users table), which overflows the page. */
.stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap); align-content: start; }
.stack > * { margin-top: 0; }

/* History rows fill the available width: one column on a phone, two or more
   on a desktop, without a per-breakpoint rule. */
#history-wrap { min-width: 0; }
#history-wrap h2 { margin: 0 0 4px; }
#history {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 460px), 1fr));
}
#history > * { margin-top: 0; }

/* Users: the two form cards share a row, the accounts table spans the page
   under them (it has eight columns and needs every pixel). */
@media (min-width: 1100px) {
  .users-main { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .users-main > .card-wide { grid-column: 1 / -1; }
}
/* The table scrolls sideways instead of squashing its cells on a phone. */
.table-wrap { overflow-x: auto; }

/* Profile: read-only account facts on the left, the password form right. */
@media (min-width: 1000px) {
  .profile-cols {
    grid-template-columns: minmax(240px, 0.75fr) minmax(0, 1.3fr);
    gap: clamp(20px, 3vw, 56px);
  }
}

/* Label + control pairs: side by side while they fit, stacked when they do
   not — one markup for both panels, no per-form width queries. */
.field-grid {
  display: grid;
  gap: 10px 16px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}
.field { min-width: 0; }
.field input, .field select { margin: 0; }
.field .hint { display: block; margin-top: 2px; }

/* Stacked actions: full-width buttons with the hint above them (it describes
   the field, not the buttons) — on a phone the row is too narrow for the
   primary button and the hint side by side. */
@media (max-width: 560px) {
  .form-row { flex-direction: column; align-items: stretch; gap: 10px; }
  .form-row .hint { order: -1; margin-bottom: 2px; text-align: center; }
}
