/* =====================================================================
   Hawaii Robo Labs — light workbench, glass-black chrome,
   sunset-amber / lagoon-teal brand accents.
   ink:   #1D2126   bg:    #F2F0EC   line:  #E2DED7
   teal:  #2E8F88   amber: #E8930C  err:   #CF4A3D
   ==================================================================== */
:root {
  --ink: #1D2126;
  --ink2: #5C646D;
  --bg: #F2F0EC;
  --surface: #FFFFFF;
  --surface2: #F8F7F4;
  --line: #E2DED7;
  --amber: #E8930C;
  --amber-soft: #FDF0D9;
  --teal: #2E8F88;
  --teal-soft: #E2F2F0;
  --curtain: #5B6BD6;
  --ok: #2F9E6F;
  --err: #CF4A3D;
  --glass: #16181B;
  --radius: 10px;
  /* legacy aliases kept so any missed reference still resolves */
  --paper: var(--bg);
  --panel: var(--surface);
  --grid: var(--line);
  --blue: var(--teal);
  --font-display: 'Archivo', sans-serif;
  --font-body: 'IBM Plex Sans', sans-serif;
  --font-mono: 'IBM Plex Mono', monospace;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
[hidden] { display: none !important; }
body {
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--bg);
}
button { font-family: var(--font-body); cursor: pointer; }

/* ---------------- Auth ---------------- */
.auth { min-height: 100vh; display: grid; place-items: center; padding: 24px; }
.auth-card {
  display: grid; grid-template-columns: 1fr 1.2fr; width: min(860px, 100%);
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 18px 50px rgba(22, 24, 27, .12); overflow: hidden;
}
.auth-brand {
  background: var(--glass); color: #fff; padding: 44px 36px;
  display: flex; flex-direction: column; justify-content: center; gap: 14px;
  border-bottom: 3px solid var(--amber);
}
.auth-brand h1 {
  font-family: var(--font-display); font-weight: 700; font-size: 2.1rem;
  line-height: 1.05; margin: 0; letter-spacing: .5px;
}
.auth-brand .tag { color: #9AA2AA; margin: 0; }
.bot { width: 72px; stroke: var(--amber); stroke-width: 3; fill: none; }
.bot .eye { fill: var(--amber); stroke: none; }
.bot .ant { fill: #fff; stroke: none; animation: blink 2.4s infinite; }
@keyframes blink { 0%, 88% { opacity: 1; } 92%, 96% { opacity: .2; } 100% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .bot .ant { animation: none; } }

.auth-forms { padding: 36px; }
.tabs { display: flex; gap: 8px; margin-bottom: 20px; }
.tab {
  border: 1.5px solid var(--line); background: transparent; border-radius: 8px;
  padding: 8px 16px; font-family: var(--font-display); font-weight: 600; color: var(--ink);
}
.tab.active { background: var(--teal); color: #fff; border-color: var(--teal); }
.form { display: flex; flex-direction: column; gap: 14px; }
.form label { display: flex; flex-direction: column; gap: 6px; font-size: .9rem; font-weight: 500; }
.form label.checkbox-row { flex-direction: row; align-items: center; gap: 8px; }
.form label.checkbox-row input { width: auto; }
.form input, .form select {
  border: 1.5px solid var(--line); border-radius: 8px; padding: 10px 12px;
  font: inherit; background: var(--surface2);
}
.form input:focus, .form select:focus { outline: 2px solid var(--teal); outline-offset: 1px; }
.primary {
  background: var(--teal); color: #fff; border: none; border-radius: 8px;
  padding: 12px; font-family: var(--font-display); font-weight: 600; font-size: 1rem;
}
.primary:hover { filter: brightness(1.08); }
.btn-secondary {
  background: var(--surface2); color: var(--ink); border: 1.5px solid var(--line); border-radius: 8px;
  padding: 12px; font-family: var(--font-display); font-weight: 600; font-size: 1rem;
}
.btn-secondary:hover { background: var(--surface); border-color: var(--ink2); }
.btn-with-icon { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
.btn-with-icon .btn-icon { width: 18px; height: 18px; }
.msg { min-height: 1.2em; font-size: .9rem; color: var(--err); margin: 12px 0 0; }
.msg.ok { color: var(--ok); }
.robo-prompt p { margin: 4px 0 12px; color: var(--ink2); }
.robo-prompt-input {
  width: 100%; border: 1.5px solid var(--line); border-radius: 8px;
  padding: 10px 12px; font: inherit; background: var(--surface2);
}
.robo-prompt-input:focus { outline: 2px solid var(--teal); outline-offset: 1px; }
.robo-prompt .dialog-actions { margin-top: 18px; }
.robo-prompt .primary { flex: 1; }
.username-status { min-height: 1.1em; font-size: .82rem; color: var(--ink2); }
.username-status.available { color: var(--ok); }
.username-status.taken { color: var(--err); }

/* Password fields: the reveal button sits inside the input's right gutter, so
   the field keeps one border and the icon never collides with the text. */
.pw-field { position: relative; display: flex; }
.pw-field input { width: 100%; padding-right: 44px; }
.pw-toggle {
  position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  display: grid; place-items: center; width: 34px; height: 34px;
  border: none; border-radius: 7px; background: transparent;
  color: var(--ink2); padding: 0;
}
.pw-toggle:hover { background: var(--line); color: var(--ink); }
.pw-toggle:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.pw-toggle[aria-pressed="true"] { color: var(--teal); }
.pw-icon { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* Hidden password shows a plain eye ("reveal"); revealed shows the struck-out
   eye ("hide"), so the icon always names the action the click performs. */
.pw-icon .pw-slash { opacity: 0; transition: opacity .12s ease; }
.pw-toggle[aria-pressed="true"] .pw-icon .pw-slash { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .pw-icon .pw-slash { transition: none; } }
.field-hint { min-height: 1.1em; font-size: .82rem; font-weight: 400; color: var(--ink2); }
.field-hint.ok { color: var(--ok); }
.field-hint.bad { color: var(--err); }

/* ---------------- IDE ---------------- */
/* Full-height shell shared by both dashboards (#ide and #ros2Ide). The
   viewport height here is what lets .workspace-row flex:1 and hand Blockly
   a real height - without it the canvas collapses to nothing. */
.ide-shell { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }

/* ---- Topbar ----
   One anatomy on every page, four regions in DOM order:
     .topbar-brand    logo + product badge
     .topbar-context  what is being edited (title, board / robot) - IDEs only
     .actions         the toolbar
     .topbar-utility  tasks, notifications, account menu (always far right)
   Desktop lays them out on one row; the "Responsive" section at the end of
   this file re-flows the same markup into two rows on tablets and three on
   phones (with .actions becoming a swipeable strip). .topbar-compact marks
   the pages whose toolbar is one or two buttons (CV, admin, teacher, KB...)
   and stays a single row at every width. */
.topbar {
  display: flex; align-items: center; gap: 12px; padding: 10px 16px;
  background: var(--glass); color: #E8E6E1; flex-wrap: wrap;
  border-bottom: 3px solid var(--amber);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .04) inset, 0 6px 18px -12px rgba(0, 0, 0, .6);
}
.topbar-brand { display: inline-flex; align-items: center; gap: 10px; flex-shrink: 0; min-width: 0; }
.topbar-context { display: inline-flex; align-items: center; gap: 12px; min-width: 0; }
.topbar-utility { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0; }
.brand { font-family: var(--font-display); font-weight: 700; letter-spacing: .5px; color: var(--amber); white-space: nowrap; display: inline-flex; align-items: center; gap: 6px; }
.brand-logo { height: 30px; width: 30px; object-fit: contain; flex-shrink: 0; }
.title-input {
  background: #24272B; border: 1px solid #3A3F45; border-radius: 7px;
  color: #E8E6E1; font: 600 1rem var(--font-body); padding: 6px 10px; min-width: 180px;
}
.title-input:focus { outline: none; border-color: var(--amber); }
/* Title + optional "Guide" button, docked as one control. The button only
   exists for showcase projects that have a Knowledge Base article, so the
   bar stays clean for everything else. */
.title-wrap { display: inline-flex; align-items: stretch; }
.title-wrap:has(.guide-btn:not([hidden])) .title-input { border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; }
.guide-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 10px; cursor: pointer;
  background: #24272B; color: var(--amber);
  border: 1px solid #3A3F45; border-left: 1px solid #3A3F45; border-radius: 0 7px 7px 0;
  animation: guide-pulse 1.6s ease-in-out 2;
}
.guide-btn:hover, .guide-btn:focus-visible { background: rgba(232, 147, 12, .14); border-color: var(--amber); outline: none; }
.guide-btn .btn-icon { width: 17px; height: 17px; }
@keyframes guide-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232, 147, 12, 0); }
  50% { box-shadow: 0 0 0 4px rgba(232, 147, 12, .35); }
}
@media (prefers-reduced-motion: reduce) { .guide-btn { animation: none; } }
#boardSelect {
  background: #24272B; color: #E8E6E1; border: 1px solid #3A3F45;
  border-radius: 8px; padding: 6px 10px; font: 500 .9rem var(--font-body);
}
.actions { margin-left: 0; flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; }
.topbar-compact .actions { flex: 0 1 auto; }
.actions button, .topbar-utility > button, .topbar-utility .who, .tasks-wrap button {
  background: transparent; color: #E8E6E1; border: 1px solid #3A3F45;
  border-radius: 8px; padding: 7px 12px; font-weight: 500;
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
}
.actions button:hover, .topbar-utility > button:hover, .tasks-wrap button:hover { border-color: #E8E6E1; }
.actions button.icon-only { padding: 7px 9px; }
.actions button.icon-only:disabled { opacity: .35; cursor: not-allowed; }
.actions button.icon-only:disabled:hover { border-color: #3A3F45; }
.btn-icon { width: 16px; height: 16px; flex-shrink: 0; }
.tb-primary { display: inline-flex; align-items: center; gap: 8px; }
/* Thin divider between toolbar clusters (history | file | deploy). */
.tb-sep { width: 1px; height: 22px; background: #3A3F45; margin: 0 2px; flex-shrink: 0; align-self: center; }
/* Plain text name (pages without an account menu). */
span.who { color: #9AA2AA; font-size: .85rem; padding: 0 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px; }

.menu-wrap { position: relative; display: inline-flex; align-items: center; gap: 2px; }
.menu-chevron-btn { padding: 7px 8px !important; }
.menu-wrap .dropdown { top: 100%; left: 0; right: auto; }
.profile-menu { position: relative; }
.profile-menu .who {
  color: #9AA2AA; font-size: .85rem; padding: 7px 10px; max-width: 240px;
}
.profile-menu .who .who-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.profile-menu .who:hover { border-color: #E8E6E1; color: #E8E6E1; }
.profile-menu .who .btn-icon { color: #9AA2AA; }
.profile-menu .who .chevron { width: 12px; height: 12px; transition: transform .15s ease; }
.profile-menu .who[aria-expanded="true"] .chevron { transform: rotate(180deg); }

.dropdown {
  position: absolute; top: 100%; right: 0; margin-top: 6px;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 18px 40px rgba(22, 24, 27, .18);
  min-width: 170px; overflow: hidden; z-index: 95;
}
.dropdown .dropdown-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: transparent; border: none; border-radius: 0;
  padding: 10px 14px; font: 500 .85rem var(--font-body); color: var(--ink);
  text-align: left; cursor: pointer; white-space: nowrap;
}
.dropdown .dropdown-item:hover { background: var(--surface2); }
.dropdown .dropdown-item.danger { color: var(--err); }
.dropdown .dropdown-item .btn-icon { color: inherit; }
.dropdown .dropdown-item + .dropdown-item { border-top: 1px solid var(--line); }

/* Type-to-search autocomplete (schools, and the "assign a plan" student/
   teacher picker) - a themed replacement for <input list="datalist">,
   whose popup can't be restyled and only matches on exact, full text. */
.autocomplete-field { z-index: 1; }
.autocomplete-field input { padding-right: 30px; }
.ac-clear {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px; border: none; background: transparent; color: var(--ink2);
  cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 50%;
  font-size: 17px; line-height: 1; padding: 0;
}
.ac-clear:hover { background: var(--surface2); color: var(--ink); }
.ac-menu { left: 0; right: 0; top: 100%; margin-top: 6px; min-width: 0; }
.ac-list { max-height: 260px; overflow-y: auto; }
.ac-menu .dropdown-item {
  /* block, not the .dropdown flex row: a result is one run of text, and
     flex would treat the <mark> around the matched part as its own item
     and push it apart by the row `gap`. */
  display: block; width: 100%; font: inherit; font-size: .85rem; white-space: normal; text-align: left;
}
.ac-menu .dropdown-item mark { background: transparent; color: var(--teal); font-weight: 700; padding: 0; }
.ac-menu .dropdown-item.is-active,
.ac-menu .dropdown-item:hover { background: var(--surface2); }
.ac-menu .dropdown-item.is-empty { color: var(--ink2); cursor: default; }
.ac-menu .dropdown-item.is-empty:hover { background: transparent; }
.autocomplete-field input.is-invalid { border-color: var(--err); box-shadow: 0 0 0 3px rgba(207, 74, 61, .12); }

.dialog-actions { display: flex; gap: 10px; margin-top: 4px; }
.dialog-actions .primary { flex: 1; }
.dialog-actions button:not(.primary) {
  border: 1.5px solid var(--line); background: var(--surface2); border-radius: 8px;
  padding: 12px 16px; font-weight: 500; color: var(--ink);
}
.confirm-msg { color: var(--ink); margin: 4px 0 22px; line-height: 1.5; }
.dialog-actions .primary.danger-btn { background: var(--err); }

.renew-actions { flex-direction: column; }
.renew-actions a { text-decoration: none; }
.renew-actions .btn-with-icon { justify-content: flex-start; }
.renew-actions .btn-secondary { border: 1.5px solid var(--line); }
.renew-back { display: flex; justify-content: center; gap: 20px; margin-top: 4px; }
/* Sign out sits beside "continue" without competing with it for attention. */
.renew-signout { color: var(--ink2); }
/* Date over badge: the Status column sits past the card's edge on narrow
   screens, so an expired plan says so where the reader is already looking. */
.renew-expiry { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.btn-whatsapp { background: #25D366; color: #fff; border-color: #25D366; }

.dashboard-choice-list { display: flex; flex-direction: column; gap: 12px; margin: 4px 0 18px; }
.dashboard-choice {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  border: 1.5px solid var(--line); border-radius: 10px; padding: 14px 16px;
}
.dashboard-choice.is-default { border-color: var(--ok); }
.dashboard-choice-name { font-weight: 600; }
.dashboard-choice-default-tag { color: var(--ok); font-size: .78rem; font-weight: 600; }
.dashboard-choice-actions { display: flex; gap: 8px; flex-shrink: 0; }
.btn-whatsapp:hover { filter: brightness(1.06); }

/* Signature: the burn switch — fills like a progress gauge while flashing */
.burn {
  position: relative; overflow: hidden; border: 2px solid var(--amber) !important;
  color: #fff; font-family: var(--font-display); font-weight: 700;
}
.burn .burn-fill {
  position: absolute; inset: 0; width: 0%; background: var(--amber);
  transition: width .25s ease;
}
.burn .burn-label { position: relative; display: inline-flex; align-items: center; gap: 6px; }
.burn.busy { pointer-events: none; }
.burn.done { border-color: var(--ok) !important; }
.burn.done .burn-fill { background: var(--ok); width: 100%; }

.teacher-edit-banner {
  display: flex; align-items: center; gap: 10px; padding: 8px 16px;
  background: var(--amber); color: #241A05; font-size: .85rem; font-weight: 500;
}
.teacher-edit-banner button {
  margin-left: auto; border: 1.5px solid #241A05; background: transparent;
  border-radius: 6px; padding: 4px 12px; font-size: .78rem; font-weight: 600; color: #241A05;
}
.sample-difficulty-picker {
  display: flex; align-items: center; gap: 6px; font-size: .78rem; font-weight: 600;
}
.sample-difficulty-picker select {
  border: 1.5px solid #241A05; border-radius: 6px; padding: 3px 6px;
  font: 500 .78rem var(--font-body); background: rgba(255,255,255,.55); color: #241A05;
}
.sample-article-search { width: 190px; }
.sample-article-search input {
  border: 1.5px solid #241A05; border-radius: 6px; padding: 3px 6px 3px 30px;
  font: 500 .78rem var(--font-body); background: rgba(255,255,255,.55); color: #241A05;
  box-shadow: none; width: 100%;
}
.sample-article-search .app-search-icon { left: 8px; width: 14px; height: 14px; color: #241A05; }

.workspace-row { flex: 1; display: flex; min-height: 0; }
#blocklyDiv { flex: 1; }

/* Toolbox category icons (left-side block palette) — kept in sync with
   the actual block colors defined in blocks.js, not the page theme. */
.blocklyTreeRow { cursor: pointer; }
.blocklyZoom, .blocklyTrash { cursor: pointer; }
.blocklyTreeIcon.cat-icon {
  display: inline-block !important; vertical-align: middle; visibility: visible !important;
  width: 18px !important; height: 18px !important;
  background-repeat: no-repeat; background-position: center; background-size: 18px 18px;
  margin-right: 6px;
}
.cat-icon-program { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23334155' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 3v18'/%3E%3Cpath d='M5 4h12l-3 4 3 4H5'/%3E%3C/svg%3E"); }
.cat-icon-pins { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231D5BD8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 2v4'/%3E%3Cpath d='M15 2v4'/%3E%3Cpath d='M7 8h10v4a5 5 0 0 1-10 0V8z'/%3E%3Cpath d='M12 17v5'/%3E%3C/svg%3E"); }
.cat-icon-timing { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237A5AF8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7v5l3 3'/%3E%3C/svg%3E"); }
.cat-icon-sensors { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230E9F6E' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 20v-3'/%3E%3Cpath d='M8.5 19a5 5 0 0 1 7 0'/%3E%3Cpath d='M5 16.5a9.5 9.5 0 0 1 14 0'/%3E%3Ccircle cx='12' cy='17' r='1' fill='%230E9F6E' stroke='none'/%3E%3C/svg%3E"); }
.cat-icon-motion { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23D97706' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1'/%3E%3C/svg%3E"); }
.cat-icon-sound { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230891B2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M11 5 6 9H2v6h4l5 4z'/%3E%3Cpath d='M15.5 8.5a5 5 0 0 1 0 7'/%3E%3Cpath d='M19 5a10 10 0 0 1 0 14'/%3E%3C/svg%3E"); }
.cat-icon-serial { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23DB2777' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='16' rx='2'/%3E%3Cpath d='M7 9l3 3-3 3'/%3E%3Cpath d='M13 15h4'/%3E%3C/svg%3E"); }
.cat-icon-logic { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b80a5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2l8 10-8 10-8-10z'/%3E%3C/svg%3E"); }
.cat-icon-loops { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235ba55b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17 2l4 4-4 4'/%3E%3Cpath d='M3 11V9a4 4 0 0 1 4-4h14'/%3E%3Cpath d='M7 22l-4-4 4-4'/%3E%3Cpath d='M21 13v2a4 4 0 0 1-4 4H3'/%3E%3C/svg%3E"); }
.cat-icon-math { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b67a5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 8v8M8 12h8'/%3E%3C/svg%3E"); }
.cat-icon-text { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a55b80' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6h16'/%3E%3Cpath d='M12 6v14'/%3E%3C/svg%3E"); }
.cat-icon-variables { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a55b5b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l6 12'/%3E%3Cpath d='M10 6l-6 12'/%3E%3Cpath d='M14 9h6'/%3E%3Cpath d='M14 15h6'/%3E%3C/svg%3E"); }
.cat-icon-functions { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237a5ba5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5a2 2 0 0 0 2 2h2'/%3E%3Cpath d='M15 3h2a2 2 0 0 1 2 2v5a2 2 0 0 0 2 2 2 2 0 0 0-2 2v5a2 2 0 0 1-2 2h-2'/%3E%3C/svg%3E"); }
.code-panel {
  width: 380px; display: flex; flex-direction: column; background: var(--surface);
  border-left: 1px solid var(--line);
}
.code-head, .console-head {
  display: flex; justify-content: space-between; align-items: center;
  font-family: var(--font-display); font-weight: 600; font-size: .85rem;
  letter-spacing: .5px; text-transform: uppercase;
  padding: 8px 12px; background: var(--surface2); border-bottom: 1px solid var(--line);
}
.console-head { border-top: 1px solid var(--line); flex-wrap: wrap; gap: 8px; }
/* Always visible (unlike #monitorControls, which only matters on the Serial
   Monitor tab) - Burn reads the same selected port, so its status belongs
   here regardless of which sub-tab is showing. */
.port-chip {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1.5px solid var(--line); background: var(--surface); border-radius: 999px;
  padding: 4px 12px 4px 10px; font-size: .78rem; color: var(--ink2);
}
.port-chip:hover { border-color: var(--teal); color: var(--ink); }
.port-chip .btn-icon { width: 14px; height: 14px; }
.port-chip .status-dot { width: 8px; height: 8px; box-shadow: none; }
.port-chip.is-selected { color: var(--ink); }
.code-head button {
  border: 1.5px solid var(--line); background: var(--surface); border-radius: 6px;
  padding: 3px 10px; font-size: .78rem; color: var(--ink);
}
#codeView { flex: 1.4; margin: 0; overflow: auto; padding: 12px; font: .78rem/1.5 var(--font-mono); }
#consoleView, #monitorView {
  flex: 1; margin: 0; overflow: auto; padding: 12px;
  font: .75rem/1.5 var(--font-mono); background: #16181B; color: #9FE8C1;
}
#consoleView .err { color: #FCA5A5; }

/* ---- ROS 2: Diagnostics and Terminal tabs (docs/plans/web-terminal.md) ----
   Both take the console's slot in the panel: same flex share, same dark
   ground for the output, so switching tabs moves nothing else. */
.diag-view, .term-view { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.diag-controls {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
  padding: 8px 12px; background: var(--surface2); border-bottom: 1px solid var(--line);
}
.diag-controls select {
  flex: 1 1 140px; min-width: 0; border: 1.5px solid var(--line); border-radius: 6px;
  padding: 4px 6px; font: 500 .78rem var(--font-body); background: var(--surface); color: var(--ink);
}
.diag-view .hint { margin: 6px 12px 0; font-size: .74rem; }
.diag-view .hint:empty { display: none; }
#diagOutput {
  flex: 1; margin: 8px 0 0; overflow: auto; padding: 12px; white-space: pre-wrap;
  font: .75rem/1.5 var(--font-mono); background: #16181B; color: #9FE8C1;
}
#diagOutput .err { color: #FCA5A5; }
#diagOutput .meta { color: #8B949E; }
.term-banner {
  display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 12px; background: var(--amber-soft); border-bottom: 1px solid var(--amber);
  font-size: .78rem; color: var(--ink);
}
.term-banner strong { font-weight: 700; }
.term-actions { display: flex; gap: 6px; }
.term-mount { flex: 1; min-height: 0; background: #16181B; padding: 6px 0 0 6px; }
.term-mount .xterm { height: 100%; }
.term-status { margin: 0; padding: 6px 12px; font-size: .74rem; background: var(--surface2); border-top: 1px solid var(--line); }

.console-tabs { display: flex; gap: 4px; }
.subtab {
  border: none; background: transparent; border-radius: 6px; padding: 4px 10px;
  font-family: var(--font-display); font-weight: 600; font-size: .78rem;
  letter-spacing: .5px; text-transform: uppercase; color: var(--ink2);
}
.subtab.active { background: var(--teal); color: #fff; }
.monitor-controls { display: flex; align-items: center; gap: 6px; text-transform: none; letter-spacing: normal; }
.monitor-controls select {
  border: 1.5px solid var(--line); border-radius: 6px; padding: 3px 6px;
  font: 500 .78rem var(--font-body); background: var(--surface);
}
.monitor-controls button {
  border: 1.5px solid var(--ok); color: var(--ok); background: transparent;
  border-radius: 6px; padding: 3px 10px; font-size: .78rem; font-weight: 600;
}
.monitor-controls button.connected { border-color: var(--err); color: var(--err); }
.monitor-send {
  display: flex; gap: 6px; padding: 8px 12px; background: var(--surface2);
  border-top: 1px solid var(--line);
}
.monitor-send input {
  flex: 1; min-width: 0; border: 1.5px solid var(--line); border-radius: 6px;
  padding: 6px 8px; font: .8rem var(--font-mono); background: var(--surface);
}
.monitor-send select {
  border: 1.5px solid var(--line); border-radius: 6px; padding: 6px; font-size: .75rem;
}
.monitor-send button {
  border: 1.5px solid var(--line); background: var(--surface); border-radius: 6px;
  padding: 6px 12px; font-size: .8rem; font-weight: 500; color: var(--ink);
}
.monitor-send button:disabled, .monitor-send input:disabled { opacity: .5; cursor: not-allowed; }

/* ---------------- dialogs ---------------- */
dialog {
  border: 1px solid var(--line); border-radius: 14px; padding: 20px 24px;
  width: min(480px, 92vw); font-family: var(--font-body);
  box-shadow: 0 18px 50px rgba(22, 24, 27, .3);
}
dialog::backdrop { background: rgba(20, 22, 25, .45); }
dialog h2 { font-family: var(--font-display); margin-top: 0; }

/* Shared close affordance, injected into every dialog by dialogs.js.
   The dialog element is already positioned by the UA (absolute, or fixed
   when opened with showModal), so it is the containing block here - no
   position needs setting on the dialog itself. */
.dialog-close {
  position: absolute; top: 12px; right: 12px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  background: transparent; border: none; border-radius: 8px;
  color: var(--ink2); cursor: pointer; line-height: 0;
}
.dialog-close svg { width: 18px; height: 18px; }
.dialog-close:hover { background: var(--surface2); color: var(--ink); }
/* Keep a long title from running underneath the button. */
.has-dialog-close > h2 { padding-right: 34px; }
dialog.dialog-fullscreen {
  width: 100vw; height: 100vh; max-width: 100vw; max-height: 100vh;
  inset: 0; margin: 0; border-radius: 0; border: none;
  display: flex; flex-direction: column; padding: 24px 40px;
}
dialog.dialog-fullscreen #editDialogFields {
  flex: 1; display: flex; flex-direction: column; overflow: auto; min-height: 0;
}
dialog.dialog-fullscreen #editDialogFields .field-block { flex: 1; min-height: 0; }
dialog.dialog-fullscreen #editDialogFields .kb-editor { flex: 1; display: flex; flex-direction: column; min-height: 0; }
dialog.dialog-fullscreen #editDialogFields .kb-editor .ql-container { flex: 1; overflow: auto; }
dialog.dialog-fullscreen .dialog-actions { flex-shrink: 0; margin-top: 16px; }
.proj-list { list-style: none; margin: 0 0 16px; padding: 0; max-height: 300px; overflow: auto; }
.proj-list li {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  padding: 9px 6px; border-bottom: 1px solid var(--line);
}
.proj-list .proj-desc { font-size: .78rem; color: var(--ink2); display: block; margin-top: 2px; }
.proj-list .proj-actions { display: flex; gap: 6px; flex-shrink: 0; }
.proj-list button {
  border: 1.5px solid var(--line); background: var(--surface2); border-radius: 6px; padding: 6px;
  display: inline-flex; align-items: center; justify-content: center; color: var(--ink);
}
.proj-list button.danger { border-color: var(--err); color: var(--err); background: transparent; }

.student-sub { font-size: .75rem; color: var(--ink2); }
.roll-input {
  width: 76px; border: 1.5px solid var(--line); border-radius: 6px;
  padding: 5px 8px; font-size: .8rem; background: var(--surface);
}

/* ---------------- Admin dashboard ---------------- */
.admin-tabs {
  display: flex; gap: 4px; padding: 10px 16px; background: var(--glass);
  border-bottom: 3px solid var(--amber); overflow-x: auto;
}
.admin-tab {
  border: none; background: transparent; border-radius: 8px; padding: 8px 16px;
  font-family: var(--font-display); font-weight: 600; font-size: .85rem; color: #9AA2AA;
  white-space: nowrap;
}
.admin-tab.active { background: var(--teal); color: #fff; }
.admin-content { padding: 20px; max-width: 1100px; margin: 0 auto; }
.admin-panel { display: flex; flex-direction: column; gap: 20px; }
.admin-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  padding: 20px 22px; box-shadow: 0 1px 3px rgba(29, 33, 38, .06);
}
.admin-card h2 { font-family: var(--font-display); margin: 0 0 14px; font-size: 1.05rem; }
.form-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; }
.form-row label { display: flex; flex-direction: column; gap: 6px; font-size: .85rem; font-weight: 500; min-width: 140px; flex: 1 1 160px; }
.form-row label.wide { flex: 2 1 260px; }
.form-row input, .form-row select {
  border: 1.5px solid var(--line); border-radius: 8px; padding: 9px 10px; font: inherit; background: var(--surface2); width: 100%;
}
.form-row button { align-self: flex-end; }

.admin-table { width: 100%; border-collapse: collapse; margin-top: 4px; }
.admin-table th, .admin-table td {
  text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); font-size: .88rem;
}
.admin-table th {
  font-family: var(--font-display); font-size: .75rem; letter-spacing: .5px;
  text-transform: uppercase; color: var(--ink2);
}
.admin-table code { font-family: var(--font-mono); background: var(--surface2); padding: 2px 6px; border-radius: 4px; }

.key-output {
  width: 100%; margin-top: 12px; font-family: var(--font-mono); font-size: .85rem;
  border: 1.5px solid var(--line); border-radius: 8px; padding: 10px; resize: vertical; background: var(--surface2);
}
.key-filters { margin-bottom: 12px; }

.pagination { display: flex; align-items: center; gap: 12px; margin-top: 14px; font-size: .85rem; }
.pagination button {
  border: 1.5px solid var(--line); background: var(--surface2); border-radius: 6px; padding: 6px 14px; font: inherit; color: var(--ink);
}
.pagination button:disabled { opacity: .4; cursor: not-allowed; }
.tag-used { color: var(--ink2); font-size: .78rem; }
.tag-unused { color: var(--ok); font-weight: 600; font-size: .78rem; }

.access-toggle {
  border: 1.5px solid var(--ok); color: var(--ok); background: transparent;
  border-radius: 999px; padding: 4px 12px; font-size: .78rem; font-weight: 600;
}
.access-toggle.is-disabled { border-color: var(--err); color: var(--err); }
/* An account that is active but whose dashboards an admin has overridden -
   distinct from plain "Active" so the exception is visible in the row. */
.access-toggle.is-custom { border-color: var(--amber); color: #B5750A; }

/* ---------------- access control modal (Users tab) ---------------- */
.access-account-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  border: 1.5px solid var(--line); border-radius: 10px; padding: 12px 14px; margin-bottom: 6px;
}
.access-row-name { font-weight: 600; }
.access-row-note { color: var(--ink2); font-size: .8rem; margin-top: 2px; }
.access-dashboards { display: flex; flex-direction: column; gap: 10px; }
.access-dashboard { border: 1.5px solid var(--line); border-radius: 10px; padding: 12px 14px; }
.access-dashboard-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.access-effective {
  font-size: .7rem; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}
.access-effective.yes { background: var(--teal-soft); color: var(--teal); }
.access-effective.no { background: #FBE3DF; color: var(--err); }
.access-choices { display: flex; gap: 8px; margin: 10px 0 8px; }
.access-choice {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  border: 1.5px solid var(--line); border-radius: 8px; padding: 7px 10px;
  font-size: .82rem; font-weight: 600; color: var(--ink2); cursor: pointer;
}
.access-choice input { margin: 0; }
.access-choice.is-picked { border-color: var(--teal); color: var(--teal); background: var(--teal-soft); }

.hint { color: var(--ink2); font-size: .85rem; margin: -6px 0 14px; }
.table-toolbar { display: flex; justify-content: flex-end; gap: 8px; margin-bottom: 10px; margin-top: 10px; }
.table-toolbar.spread { justify-content: space-between; }
.table-toolbar select {
  border: 1.5px solid var(--line); border-radius: 8px; padding: 7px 10px;
  font: inherit; background: var(--surface2); color: var(--ink);
}

/* ---------------- Invoice generator (Admin > Invoices) ---------------- */
.invoice-generator { display: grid; grid-template-columns: 1fr 320px; gap: 20px; align-items: start; }
.invoice-generator-main { display: flex; flex-direction: column; gap: 20px; min-width: 0; }

.invoice-step-label {
  display: flex; align-items: center; gap: 8px; font-family: var(--font-display);
  font-weight: 700; font-size: .78rem; letter-spacing: .05em; text-transform: uppercase;
  color: var(--teal); margin-bottom: 14px;
}
.invoice-step-label-spaced { margin-top: 22px; }
.invoice-step-label .step-num {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 20px; height: 20px; border-radius: 50%; background: var(--teal-soft); color: var(--teal);
  font-size: .72rem;
}
.hint-inline { color: var(--ink2); font-size: .8rem; font-weight: 400; text-transform: none; letter-spacing: normal; }

#invRecipientTabs.tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 4px; }
#invRecipientTabs .tab { text-align: center; }

.invoice-empty-state {
  display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center;
  border: 1.5px dashed var(--line); border-radius: var(--radius); padding: 30px 20px; margin-top: 16px;
  color: var(--ink2);
}
.invoice-empty-state .btn-icon { width: 30px; height: 30px; color: var(--line); }
.invoice-empty-state p { margin: 0; font-size: .88rem; max-width: 30ch; }

.invoice-recipient-card {
  display: flex; align-items: center; gap: 14px; padding: 14px 16px; margin: 16px 0;
  background: linear-gradient(135deg, var(--teal-soft), var(--surface2));
  border: 1px solid var(--line); border-radius: var(--radius);
}
.invoice-recipient-avatar {
  width: 44px; height: 44px; border-radius: 50%; background: var(--teal); color: #fff;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  font-family: var(--font-display); font-weight: 700; font-size: 1.1rem;
}
.invoice-recipient-info { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.invoice-recipient-info strong {
  font-family: var(--font-display); font-size: 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.invoice-recipient-type {
  width: fit-content; font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--teal); background: var(--surface); border: 1px solid var(--teal); border-radius: 999px; padding: 1px 8px;
}
.invoice-recipient-contact { display: flex; gap: 12px; flex-wrap: wrap; font-size: .82rem; color: var(--ink2); }
.invoice-recipient-contact span:empty { display: none; }

.invoice-subs-table input[type="checkbox"] { width: 17px; height: 17px; accent-color: var(--teal); cursor: pointer; }
.invoice-subs-table tbody tr.is-selected { background: var(--teal-soft); }

.invoice-options-row { display: flex; flex-wrap: wrap; align-items: center; gap: 20px; margin: 16px 0; }

.toggle-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; font-size: .88rem; font-weight: 500; }
.toggle-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.toggle-switch .track {
  width: 38px; height: 21px; border-radius: 999px; background: var(--line); position: relative;
  transition: background-color .15s ease; flex-shrink: 0;
}
.toggle-switch .track::after {
  content: ''; position: absolute; top: 2px; left: 2px; width: 17px; height: 17px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .15s ease;
}
.toggle-switch input:checked + .track { background: var(--teal); }
.toggle-switch input:checked + .track::after { transform: translateX(17px); }
.toggle-switch input:focus-visible + .track { outline: 2px solid var(--teal); outline-offset: 2px; }

.segmented { display: inline-flex; gap: 2px; padding: 3px; background: var(--surface2); border: 1.5px solid var(--line); border-radius: 999px; }
.segmented-option { position: relative; }
.segmented-option input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.segmented-option span {
  display: block; padding: 7px 16px; border-radius: 999px; font-size: .82rem; font-weight: 600;
  color: var(--ink2); cursor: pointer; transition: background-color .15s ease, color .15s ease; white-space: nowrap;
}
.segmented-option input:checked + span { background: var(--teal); color: #fff; }
.segmented-option input:focus-visible + span { outline: 2px solid var(--teal); outline-offset: 2px; }

.invoice-summary {
  position: sticky; top: 20px; background: var(--glass); color: #fff; border-radius: var(--radius);
  padding: 22px 20px; display: flex; flex-direction: column; gap: 16px;
  border-top: 3px solid var(--amber); box-shadow: 0 14px 34px rgba(22, 24, 27, .2);
}
.invoice-summary-head { display: flex; flex-direction: column; gap: 4px; }
.invoice-summary-eyebrow {
  font-family: var(--font-display); font-size: .7rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--amber);
}
.invoice-summary-recipient { font-weight: 600; color: #fff; font-size: .95rem; overflow-wrap: anywhere; }
.invoice-summary-rows { margin: 0; display: flex; flex-direction: column; gap: 8px; }
.invoice-summary-row { display: flex; justify-content: space-between; gap: 10px; margin: 0; font-size: .86rem; color: #C7CBD1; }
.invoice-summary-row dt, .invoice-summary-row dd { margin: 0; font-weight: 500; }
.invoice-summary-row dd { color: #fff; }
.invoice-summary-total {
  display: flex; justify-content: space-between; align-items: baseline;
  padding-top: 14px; border-top: 1px solid rgba(255, 255, 255, .14);
}
.invoice-summary-total span { font-size: .82rem; color: #C7CBD1; font-weight: 600; }
.invoice-summary-total strong { font-family: var(--font-display); font-size: 1.5rem; color: var(--amber); }
.invoice-summary-actions { display: flex; flex-direction: column; gap: 8px; }
.invoice-summary-actions .btn-secondary { background: rgba(255, 255, 255, .08); color: #fff; border-color: rgba(255, 255, 255, .2); }
.invoice-summary-actions .btn-secondary:hover:not(:disabled) { background: rgba(255, 255, 255, .14); }
.invoice-summary-actions button:disabled { opacity: .4; cursor: not-allowed; }
.invoice-summary .msg { color: #FFB4A8; margin: 0; }
.invoice-summary .msg.ok { color: #7CE0B3; }

@media (max-width: 860px) {
  .invoice-generator { grid-template-columns: 1fr; }
  .invoice-summary { position: static; }
}

/* Invoice history: sales/tax summary tiles + cancelled-row styling + preview */
.invoice-stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 4px 0 6px; }
.invoice-stat-tile {
  background: var(--surface2); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 16px; display: flex; flex-direction: column; gap: 6px;
}
.invoice-stat-tile-accent { background: var(--teal-soft); border-color: var(--teal); }
.invoice-stat-label { font-size: .74rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--ink2); }
.invoice-stat-tile strong { font-family: var(--font-display); font-size: 1.35rem; overflow-wrap: anywhere; }
.invoice-stat-tile-accent strong { color: var(--teal); }
.invoice-stats-row-cancelled { margin-top: 4px; }
.invoice-stat-tile-muted { background: #FBE3DF; border-color: var(--err); }
.invoice-stat-tile-muted strong { color: var(--err); }

.admin-table tbody tr.is-cancelled { opacity: .55; }

.invoice-preview-dialog { width: min(820px, 94vw); }
.invoice-preview-frame-wrap {
  height: 70vh; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; margin: 4px 0;
}
.invoice-preview-frame-wrap iframe { width: 100%; height: 100%; border: none; background: #525659; }

.difficulty-tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-display); font-size: .7rem; font-weight: 700;
  letter-spacing: .4px; text-transform: uppercase;
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}
.difficulty-tag::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.difficulty-tag.easy { background: var(--teal-soft); color: var(--teal); }
.difficulty-tag.intermediate { background: var(--amber-soft); color: #B5750A; }
.difficulty-tag.hard { background: #FBE3DF; color: var(--err); }
.difficulty-tag.expert { background: #E7E5FA; color: var(--curtain); }
.difficulty-select {
  border: 1.5px solid var(--line); border-radius: 6px; padding: 4px 6px;
  font-size: .78rem; font-family: var(--font-body); background: var(--surface2); color: var(--ink);
}
.tag-input {
  border: 1.5px solid var(--line); border-radius: 6px; padding: 4px 6px; width: 90px;
  font-size: .78rem; font-family: var(--font-body); background: var(--surface2); color: var(--ink);
}
.article-search-input {
  border: 1.5px solid var(--line); border-radius: 6px; padding: 4px 6px; width: 150px;
  font-size: .78rem; font-family: var(--font-body); background: var(--surface2); color: var(--ink);
}
.article-cell { display: flex; align-items: center; gap: 6px; position: relative; }
.table-btn.icon-only:disabled { opacity: .35; cursor: not-allowed; }
.table-btn.icon-only:disabled:hover { border-color: var(--line); color: inherit; }
.table-btn.icon-only.confirm-btn:not(:disabled) { border-color: var(--teal); color: var(--teal); }
.project-tag {
  display: inline-flex; align-items: center;
  font-family: var(--font-display); font-size: .7rem; font-weight: 700;
  letter-spacing: .4px; text-transform: uppercase;
  padding: 2.5px 8px; border-radius: 999px; white-space: nowrap;
  background: transparent; border: 1.5px solid var(--teal); color: var(--teal);
}

/* Board a showcase project is written for - same chip shape as .project-tag,
   in the neutral ink colour so the KITE/track tags keep the accent. */
.board-tag {
  display: inline-flex; align-items: center;
  font-family: var(--font-display); font-size: .7rem; font-weight: 600;
  letter-spacing: .3px;
  padding: 2.5px 8px; border-radius: 999px; white-space: nowrap;
  background: transparent; border: 1.5px solid var(--ink2); color: var(--ink2);
}
/* A hardware requirement, not a label: this project refuses to run on any
   other kind of robot, so it is filled rather than outlined like .board-tag. */
.platform-tag {
  display: inline-flex; align-items: center;
  font-family: var(--font-display); font-size: .7rem; font-weight: 600;
  letter-spacing: .3px;
  padding: 2.5px 8px; border-radius: 999px; white-space: nowrap;
  background: var(--amber-soft); border: 1.5px solid var(--amber); color: #B5750A;
}

.proj-list li.samples-group-head-row { border-bottom: none; padding: 9px 0 4px; display: block; }
.samples-group-head {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-display); font-size: .78rem; font-weight: 700;
  letter-spacing: .6px; text-transform: uppercase; color: var(--ink2);
}
.proj-list li.samples-group-head-row:first-child { padding-top: 0; }
.samples-group-head::after { content: ''; flex: 1; height: 1px; background: var(--line); }
.samples-filter { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
.samples-filter button {
  border: 1.5px solid var(--line); background: var(--surface2); color: var(--ink);
  border-radius: 999px; padding: 5px 13px; font-size: .78rem; font-weight: 600;
}
.samples-filter button.active { background: var(--teal); border-color: var(--teal); color: #fff; }
.name-link {
  background: none; border: none; padding: 0; font: inherit; color: var(--teal);
  font-weight: 600; text-decoration: underline; cursor: pointer;
}
.table-btn {
  border: 1.5px solid var(--line); background: var(--surface2); border-radius: 6px;
  padding: 5px 10px; font-size: .78rem; white-space: nowrap; color: var(--ink);
}
.table-btn.danger { border-color: var(--err); color: var(--err); background: transparent; }
.table-btn.icon-only {
  padding: 6px; display: inline-flex; align-items: center; justify-content: center;
  vertical-align: middle;
}
.table-btn.icon-only .btn-icon { width: 15px; height: 15px; }

.quick-adjust-row { display: flex; flex-wrap: wrap; gap: 6px; margin: -8px 0 14px; }
.quick-adjust-row .table-btn { padding: 4px 9px; }

/* ---------------- progress / milestones ---------------- */
.progress-cell { display: flex; align-items: center; gap: 8px; min-width: 130px; }
.progress-track { flex: 1; height: 8px; background: var(--line); border-radius: 999px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--ok); border-radius: 999px; }
.progress-label { font-size: .78rem; color: var(--ink2); white-space: nowrap; }

.milestones-dialog { width: min(520px, 92vw); }
.milestones-dialog.wide-dialog { width: min(640px, 92vw); max-height: 86vh; overflow-y: auto; }
.dialog-subhead {
  font-family: var(--font-display); font-size: .85rem; margin: 18px 0 8px; color: var(--ink);
}
.dialog-subhead .hint { font-family: var(--font-body); font-weight: 400; margin: 0; display: inline; }
.plan-items-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.plan-kit-picker {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px;
  padding: 10px 12px; margin-bottom: 10px;
  background: var(--surface2); border: 1px dashed var(--line); border-radius: 8px;
}
.plan-kit-picker > label:first-child { display: flex; flex-direction: column; gap: 6px; font-size: .85rem; font-weight: 500; flex: 1 1 220px; }
.plan-kit-picker select { border: 1.5px solid var(--line); border-radius: 8px; padding: 7px 10px; font: inherit; background: var(--surface); color: var(--ink); }
.plan-kit-picker .checkbox-row { margin: 0 0 8px; }
.plan-kit-picker .table-btn { margin-bottom: 4px; }
.plan-kit-picker .plan-kit-qty { display: flex; flex-direction: column; gap: 6px; font-size: .85rem; font-weight: 500; flex: 0 0 64px; }
.plan-kit-picker .plan-kit-qty input {
  width: 64px; border: 1.5px solid var(--line); border-radius: 8px; padding: 7px 8px; font: inherit; background: var(--surface); color: var(--ink);
}
/* One row per kit the plan includes: name, how many, whether optional parts count. */
.plan-kits-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 4px; }
.plan-kit-row {
  display: grid; grid-template-columns: 1fr 70px auto auto; gap: 8px; align-items: center;
  padding: 6px 10px; background: var(--teal-soft); border: 1px solid var(--teal); border-radius: 8px; font-size: .85rem;
}
.plan-kit-row strong { font-weight: 600; }
.plan-kit-row .plan-kit-parts { color: var(--ink2); font-weight: 400; }
.plan-kit-row input[type="number"] {
  width: 64px; border: 1.5px solid var(--line); border-radius: 6px; padding: 5px 6px; font: inherit; background: var(--surface);
}
.plan-kit-row .checkbox-row { margin: 0; font-size: .8rem; white-space: nowrap; }
.plan-kit-row .icon-btn { color: var(--err); }
.plan-kits-empty { color: var(--ink2); font-size: .85rem; margin: 0 0 4px; }
.plan-kit-tag {
  display: inline-block; padding: 1px 8px; margin: 1px 2px 1px 0; border-radius: 999px; font-size: .78rem; font-weight: 600;
  background: var(--teal-soft); color: var(--teal); border: 1px solid var(--teal);
}
.plan-item-row {
  display: grid; grid-template-columns: 2fr 70px 1.4fr auto; gap: 6px; align-items: center;
}
.plan-item-row input {
  border: 1.5px solid var(--line); border-radius: 6px; padding: 7px 8px; font: inherit; background: var(--surface2);
}
.plan-item-row .icon-btn { color: var(--err); }
.plan-items-empty { color: var(--ink2); font-size: .85rem; margin: 0 0 10px; }

.subscription-tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-display); font-size: .7rem; font-weight: 700;
  letter-spacing: .4px; text-transform: uppercase;
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}
.subscription-tag::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.subscription-tag.active { background: var(--teal-soft); color: var(--teal); }
.subscription-tag.expired { background: var(--amber-soft); color: #B5750A; }
.subscription-tag.cancelled { background: #FBE3DF; color: var(--err); }
.plan-type-tag {
  display: inline-block; font-size: .72rem; font-weight: 600; color: var(--ink2);
  background: var(--surface2); border: 1px solid var(--line); border-radius: 6px; padding: 2px 8px;
}
.milestones-list { list-style: none; margin: 0 0 16px; padding: 0; max-height: 380px; overflow: auto; }
.milestones-list li { padding: 9px 4px; border-bottom: 1px solid var(--line); }
.milestones-list label { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.milestones-list input[type="checkbox"] { margin-top: 3px; width: 16px; height: 16px; flex-shrink: 0; }
.milestone-title { font-weight: 600; }
.milestone-title.done { text-decoration: line-through; color: var(--ink2); }
.milestone-desc { font-size: .8rem; color: var(--ink2); margin-top: 2px; }

.student-detail-summary {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 10px;
  margin: 4px 0 12px; padding: 12px; background: var(--surface2); border-radius: 10px;
}
.student-detail-stat { display: flex; flex-direction: column; gap: 2px; }
.student-detail-stat .stat-value { font: 700 1.1rem var(--font-display); color: var(--ink); }
.student-detail-stat .stat-label {
  font-size: .7rem; color: var(--ink2); text-transform: uppercase; letter-spacing: .4px;
}

/* ---------------- Knowledge base (docs-style layout) ---------------- */
.kb-shell { display: flex; height: calc(100vh - 53px); height: calc(100dvh - var(--topbar-h, 53px)); overflow: hidden; }
.actions .kb-contents-btn { display: none; }
.kb-backdrop { position: fixed; inset: var(--topbar-h, 53px) 0 0; background: rgba(22, 24, 27, .45); z-index: 80; }
.kb-sidebar {
  width: 260px; flex-shrink: 0; background: var(--surface); border-right: 1px solid var(--line);
  padding: 20px 16px; overflow-y: auto;
}
.kb-sidebar h2 {
  font-family: var(--font-display); font-size: .75rem; letter-spacing: .5px; text-transform: uppercase;
  color: var(--ink2); margin: 0 0 12px;
}
.kb-nav-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.kb-nav-list .kb-nav-list { margin: 2px 0 2px 14px; padding-left: 10px; border-left: 2px solid var(--line); }
.kb-nav-btn {
  width: 100%; text-align: left; background: none; border: none; border-radius: 8px;
  padding: 9px 12px; font: 500 .9rem var(--font-body); color: var(--ink);
  display: flex; align-items: center; gap: 6px;
}
.kb-nav-btn:hover { background: var(--surface2); }
.kb-nav-btn.active { background: var(--teal); color: #fff; font-weight: 600; }
.kb-nav-btn span { flex: 1; }
.kb-nav-btn .difficulty-tag { flex-shrink: 0; font-size: .62rem; padding: 2px 7px; }
.kb-nav-btn.active .difficulty-tag { background: rgba(255,255,255,.25); color: #fff; }
.kb-nav-chevron {
  width: 14px; height: 14px; flex-shrink: 0; transition: transform .15s ease; color: var(--ink2);
}
.kb-nav-btn.active .kb-nav-chevron { color: #fff; }
.kb-nav-node.expanded > .kb-nav-btn .kb-nav-chevron { transform: rotate(90deg); }
.kb-nav-node:not(.expanded) > .kb-nav-list { display: none; }

.kb-search { position: relative; margin-bottom: 16px; }
.kb-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--ink2); width: 16px; height: 16px; }
.kb-search input {
  width: 100%; box-sizing: border-box; border: 1.5px solid var(--line); border-radius: 8px;
  padding: 9px 10px 9px 32px; font: .85rem var(--font-body); background: var(--surface2);
}
.kb-search input:focus { outline: 2px solid var(--teal); outline-offset: 1px; }
.kb-search-results { display: flex; flex-direction: column; gap: 8px; }
.kb-search-result {
  display: block; width: 100%; text-align: left; background: var(--surface); border: 1.5px solid var(--line);
  border-radius: 8px; padding: 10px 12px; cursor: pointer; font: inherit;
}
.kb-search-result:hover { border-color: var(--teal); }
.kb-search-result-path { font-size: .7rem; color: var(--ink2); margin-bottom: 2px; }
.kb-search-result-title { font-weight: 600; font-size: .9rem; margin-bottom: 3px; color: var(--ink); }
.kb-search-result-title .difficulty-tag { font-size: .62rem; padding: 2px 7px; vertical-align: middle; margin-left: 4px; }
.kb-search-result-snippet { font-size: .8rem; color: var(--ink2); line-height: 1.5; }
.kb-search-empty { color: var(--ink2); font-size: .85rem; margin: 0; }
.kb-search-results mark, .kb-content mark { background: var(--amber-soft); color: inherit; border-radius: 3px; padding: 0 2px; }

.kb-main { flex: 1; overflow-y: auto; padding: 32px 40px; }
.kb-article { max-width: 820px; }
.kb-breadcrumb { font-size: .8rem; color: var(--ink2); margin: 0 0 8px; }
.kb-breadcrumb strong { color: var(--ink); }
.kb-difficulty-row:empty { display: none; }
.kb-difficulty-row { margin: 0 0 16px; }
.kb-content h1 { font-family: var(--font-display); font-size: 1.8rem; margin: 0 0 .6em; }
.kb-content h2 { font-family: var(--font-display); font-size: 1.3rem; margin-top: 1.8em; }
.kb-content h3 { font-family: var(--font-display); font-size: 1.05rem; margin-top: 1.6em; }
.kb-content p { line-height: 1.7; }
.kb-content a { color: var(--teal); }
.kb-content code { font-family: var(--font-mono); background: var(--surface2); padding: 2px 6px; border-radius: 4px; font-size: .85em; }
.kb-content pre { background: var(--glass); color: #E6EDF7; padding: 16px; border-radius: 10px; overflow-x: auto; }
.kb-content pre code { background: none; padding: 0; }
.kb-content blockquote, .kb-content .callout {
  border-left: 4px solid var(--amber); background: var(--amber-soft); padding: 12px 16px;
  border-radius: 0 8px 8px 0; margin: 20px 0;
}
.kb-content img { max-width: 100%; border-radius: 8px; }
.kb-content ul, .kb-content ol { line-height: 1.7; }
.kb-content table, .kb-table {
  width: 100%; border-collapse: collapse; margin: 14px 0 22px; font-size: .9rem;
  display: block; overflow-x: auto;      /* narrow screens scroll the table, not the page */
}
.kb-content th, .kb-content td { padding: 9px 12px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
.kb-content thead th { font-weight: 700; color: var(--ink); border-bottom: 2px solid var(--line); white-space: nowrap; }
.kb-content tbody tr:last-child td { border-bottom: none; }
.kb-content .kb-open-sample-btn {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 8px;
  background: var(--teal); color: #fff; text-decoration: none;
  font-family: var(--font-display); font-weight: 600; font-size: .95rem;
  padding: 12px 20px; border-radius: 8px;
}
.kb-content .kb-open-sample-btn:hover { filter: brightness(1.08); }

/* Line-by-line code walkthroughs (server/seed/kb-curriculum-visuals.js).
   These live here rather than as inline styles on the cells for one reason:
   an inline style beats any media query, and the phone rules further down
   restack these three columns into a stack. */
.kb-content table.kb-codewalk th.cw-n { text-align: right; }
.kb-content table.kb-codewalk td.cw-n {
  color: var(--ink2); font-family: var(--font-mono); font-size: .8rem;
  text-align: right; width: 2.6em; white-space: nowrap;
}
.kb-content table.kb-codewalk td.cw-c { width: 46%; }
.kb-content table.kb-codewalk td.cw-c code { white-space: pre; background: none; padding: 0; }
.kb-content table.kb-codewalk td.cw-s { color: var(--ink2); }
.kb-empty { color: var(--ink2); text-align: center; padding: 60px 20px; }

.kb-components-row { display: flex; flex-wrap: wrap; gap: 14px; margin: 12px 0 20px; }
.kb-component-card {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  width: 96px; padding: 10px 8px; background: var(--surface2); border: 1px solid var(--line);
  border-radius: 10px; text-align: center;
}
.kb-component-card span { font-size: .75rem; color: var(--ink2); line-height: 1.3; }
.kb-wiring-diagram {
  display: block; margin: 12px 0 20px; border: 1px solid var(--line); border-radius: 10px;
}
.kb-wiring-table { font-size: .86rem; }
.kb-wiring-table code { font-family: var(--font-mono); font-size: .82rem; white-space: nowrap; }
.kb-wiring-table td:nth-child(4) { white-space: nowrap; }
.kb-wire-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; vertical-align: -1px; }
.kb-power-notes li { margin-bottom: 8px; }
.kb-output-figure { margin: 12px 0 20px; max-width: 260px; }
.kb-output-illustration {
  display: block; width: 100%; border: 1px solid var(--line); border-radius: 10px 10px 0 0; border-bottom: none;
}
.kb-output-figure figcaption {
  font-size: .8rem; color: var(--ink2); text-align: center; line-height: 1.4;
  padding: 8px 10px; border: 1px solid var(--line); border-top: none; border-radius: 0 0 10px 10px;
}

/* Kit BOM tab: one card per kit - eyebrow / name / stat tiles up top, a
   category-grouped parts table, and the kit's notes in an amber callout.
   Same pattern as the KB wiring articles so the two read as one system. */
.bom-kit-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-end; gap: 14px 24px; margin-bottom: 4px; }
.bom-eyebrow { font-family: var(--font-mono); font-size: .74rem; letter-spacing: .06em; text-transform: uppercase; color: var(--teal); margin-bottom: 4px; }
.bom-kit-head h2 { margin: 0; font-size: 1.3rem; }
.bom-sub { margin: 6px 0 0; color: var(--ink2); max-width: 62ch; font-size: .9rem; line-height: 1.5; }
.bom-stats { display: flex; gap: 10px; flex-wrap: wrap; }
.bom-stat { background: var(--surface2); border: 1px solid var(--line); border-radius: 10px; padding: 8px 14px; min-width: 104px; }
.bom-stat b { display: block; font-family: var(--font-display); font-size: 1.35rem; line-height: 1.1; font-variant-numeric: tabular-nums; }
.bom-stat span { font-size: .68rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink2); }
.bom-toolbar-left { display: flex; gap: 8px; flex-wrap: wrap; }
.bom-table td.bom-cat { background: var(--surface2); font-family: var(--font-display); font-weight: 600; font-size: .76rem; letter-spacing: .05em; text-transform: uppercase; padding: 7px 12px; }
.bom-table th.bom-qty, .bom-table td.bom-qty { text-align: right; white-space: nowrap; }
.bom-table td.bom-qty { font-family: var(--font-mono); font-size: 1rem; font-weight: 500; font-variant-numeric: tabular-nums; }
.bom-table td.bom-part { min-width: 180px; }
.bom-table td.bom-part b { font-weight: 600; }
.bom-table td.bom-spec, .bom-table td.bom-note { color: var(--ink2); font-size: .84rem; }
.bom-table td.bom-spec { min-width: 160px; }
.bom-table td.bom-note { min-width: 180px; }
.bom-table td.bom-cov { white-space: nowrap; }
.bom-bar { display: inline-block; width: 64px; height: 7px; border-radius: 4px; background: var(--teal-soft); vertical-align: middle; margin-right: 8px; overflow: hidden; }
.bom-bar i { display: block; height: 100%; background: var(--teal); border-radius: 4px; }
.bom-bar.all i { background: var(--amber); }
.bom-cov span:last-child { font-family: var(--font-mono); font-size: .8rem; font-variant-numeric: tabular-nums; }
.bom-opt { display: inline-block; font-size: .66rem; text-transform: uppercase; letter-spacing: .06em; font-weight: 600; color: #B5750A; background: var(--amber-soft); border-radius: 4px; padding: 1px 6px; margin-left: 6px; vertical-align: 1px; }
.bom-actions { white-space: nowrap; text-align: right; }
.bom-actions .table-btn { padding: 4px; }
.bom-rules { margin-top: 14px; background: var(--surface2); border: 1px solid var(--line); border-left: 4px solid var(--amber); border-radius: 0 10px 10px 0; padding: 10px 16px; font-size: .86rem; color: var(--ink2); }
.bom-rules strong { color: var(--ink); }
.bom-rules ul { margin: 6px 0 0; padding-left: 18px; }
.bom-rules li { margin: 3px 0; }
.bom-empty { color: var(--ink2); margin: 0; padding: 6px 0; }

.kb-ui-mockup { display: block; margin: 14px 0 22px; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
.kb-category-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }

/* Block reference: one toolbox category, then its blocks drawn at real size.
   The artwork is Blockly's own SVG (see server/seed/kb-block-svgs.js), so it is
   whatever width the block is - hence the horizontal scroll on the wide ones
   rather than squashing them out of proportion. */
.kb-block-cat { margin: 26px 0; padding: 2px 0 2px 16px; }
.kb-block-cat-head { display: flex; align-items: center; gap: 9px; margin: 0 0 4px; font-size: 1.05rem; }
.kb-content .kb-block-cat-head { margin-top: 0; }
.kb-block-cat-intro { margin: 0 0 6px; color: var(--ink2); font-size: .9rem; }
.kb-block-entry {
  display: flex; flex-wrap: wrap; gap: 6px 20px; align-items: flex-start;
  padding: 14px 0; border-bottom: 1px dashed var(--line);
}
.kb-block-entry:last-child { border-bottom: none; }
.kb-block-art { flex: 0 1 auto; max-width: 100%; overflow-x: auto; padding-bottom: 2px; }
.kb-block-art svg.kb-block { display: block; }
.kb-block-copy { flex: 1 1 300px; min-width: 260px; font-size: .9rem; line-height: 1.6; color: var(--ink2); }
.kb-block-copy strong { display: block; color: var(--ink); margin-bottom: 2px; }

.kb-textarea {
  width: 100%; min-height: 260px; border: 1.5px solid var(--line); border-radius: 8px;
  padding: 12px; font: .85rem/1.5 var(--font-mono); background: var(--surface2); resize: vertical;
}
.field-block { display: flex; flex-direction: column; gap: 6px; font-size: .85rem; font-weight: 500; margin: 14px 0; }

/* ---- Knowledge Base image uploads ---- */
.kb-drop {
  border: 2px dashed var(--line); border-radius: 12px; padding: 20px 16px; text-align: center;
  transition: border-color .15s ease, background .15s ease;
}
.kb-drop.is-over { border-color: var(--teal); background: color-mix(in srgb, var(--teal) 8%, transparent); }
.kb-drop p { margin: 6px 0 0; font-weight: 400; }
.kb-drop-icon { width: 30px; height: 30px; color: var(--ink2); }
.link-btn {
  background: none; border: none; padding: 0; color: var(--teal);
  font: inherit; text-decoration: underline; cursor: pointer;
}

.kb-upload { margin-top: 10px; }
.kb-upload-head { display: flex; justify-content: space-between; font-size: .8rem; color: var(--ink2); margin-bottom: 5px; }
.kb-upload-track { height: 8px; border-radius: 999px; background: var(--surface2); overflow: hidden; }
.kb-upload-bar { height: 100%; width: 0; border-radius: 999px; background: var(--teal); transition: width .18s ease; }
.kb-upload-bar.is-done { background: var(--teal); }
.kb-upload-bar.is-error { background: #DC2626; }

.kb-image-list { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.kb-image-item {
  display: flex; align-items: center; gap: 12px; padding: 8px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface2);
}
.kb-image-item img {
  width: 56px; height: 42px; object-fit: cover; border-radius: 6px;
  background: var(--surface); flex-shrink: 0;
}
.kb-image-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.kb-image-meta code {
  font-size: .78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  background: none; padding: 0;
}
.kb-image-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }

/* ---- Knowledge Base outliner ----
   The Sections table doubles as a tree editor: rows carry their nesting as
   indentation, a grip starts a drag, and a drop line shows both where the row
   will land and - by how far it is indented - what it will land under. */
.kb-row .kb-grip-cell { width: 30px; padding-right: 0; }
.kb-grip {
  color: var(--line); cursor: grab; padding: 2px; border-radius: 5px;
  transition: color .12s ease, background .12s ease;
}
.kb-row:hover .kb-grip { color: var(--ink2); background: var(--surface2); }
.kb-grip:active { cursor: grabbing; }

/* The indent spacer is an element rather than padding so the guide line can
   sit on it and show which parent a nested row belongs to. */
.kb-title-indent {
  display: inline-block; height: 1.15em; vertical-align: middle;
  border-left: 2px solid var(--line); margin-right: 0;
}
.kb-title-indent[style*="width:0px"] { border-left: none; }
.kb-title-cell { white-space: nowrap; }

.kb-move-cell { white-space: nowrap; }
.kb-move-cell .table-btn { padding: 4px; }
.table-btn[disabled] { opacity: .25; cursor: default; pointer-events: none; }

.kb-row { transition: background .12s ease, opacity .12s ease; }
.kb-row.is-dragging { opacity: .35; }
.kb-row.is-dragging .kb-grip { cursor: grabbing; }

/* One-row-tall marker with no border of its own, so it reads as a line drawn
   between two rows rather than as another row. */
tr.kb-drop-line td { padding: 0 !important; border: none !important; height: 0; }
tr.kb-drop-line div {
  height: 3px; border-radius: 3px; background: var(--teal);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--teal) 22%, transparent);
  position: relative;
}
tr.kb-drop-line div::before {
  content: ''; position: absolute; left: -5px; top: -3.5px;
  width: 10px; height: 10px; border-radius: 50%; background: var(--teal);
}

.table-icon { display: inline-flex; align-items: center; color: var(--ink2); }
.table-icon.is-html { color: var(--amber); }
.table-icon.is-rich { color: var(--teal); }

.kb-editor-modes { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; flex-wrap: wrap; }
.kb-mode {
  padding: 5px 12px; border: 1.5px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink2); font-size: .82rem; cursor: pointer;
}
.kb-mode.is-active { background: var(--teal); border-color: var(--teal); color: #fff; }
.kb-editor-modes .hint { font-size: .8rem; }
.kb-html-source { min-height: 320px; font-size: .8rem; white-space: pre; overflow-wrap: normal; overflow-x: auto; }

.kb-editor {
  background: var(--surface); border: 1.5px solid var(--line); border-radius: 8px; overflow: hidden;
}
.kb-editor .ql-toolbar.ql-snow {
  border: none; border-bottom: 1.5px solid var(--line); background: var(--surface2);
  font-family: var(--font-body); border-radius: 8px 8px 0 0;
}
.kb-editor .ql-container.ql-snow { border: none; font-family: var(--font-body); font-size: .95rem; }
.kb-editor .ql-editor { min-height: 220px; line-height: 1.6; }
.kb-editor .ql-editor.ql-blank::before { color: var(--ink2); font-style: normal; }

@media (max-width: 900px) {
  .auth-card { grid-template-columns: 1fr; }
  .code-panel { width: 300px; }
  /* Knowledge Base: the section tree becomes a drawer (kb.js toggles
     .is-open from the Contents button in the topbar) so the article gets
     the whole screen and the tree, however long, scrolls on its own. */
  .actions .kb-contents-btn { display: inline-flex; }
  .topbar-compact .actions .kb-contents-btn .btn-label { display: inline; } /* keep the word even on small phones */
  .kb-sidebar {
    position: fixed; top: var(--topbar-h, 53px); bottom: 0; left: 0; z-index: 85;
    width: min(86vw, 340px); border-right: 1px solid var(--line);
    box-shadow: 0 0 40px rgba(22, 24, 27, .25);
    transform: translateX(-104%); transition: transform .22s ease;
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  }
  .kb-sidebar.is-open { transform: none; }
  .kb-main { padding: 20px 16px 48px; }
  .kb-content h1 { font-size: 1.45rem; }
  .kb-content h2 { font-size: 1.15rem; }
  .kb-content pre { padding: 12px; font-size: .8rem; }
  .kb-breadcrumb { line-height: 1.6; }
}

/* ---------------------------------------------------------------------
   Knowledge Base article content on a phone.

   The shell above already turns the section tree into a drawer. What is
   left is the article itself, and three things in it do not survive a
   360px screen on their own:

   1. The diagrams. Each one is an inline <svg> with a ~780-unit viewBox
      and 9-12px labels. `width:100%` fits the screen but scales the whole
      coordinate system to 0.45x, so the labels come out around 5px and no
      font rule can rescue them - the text is inside the shrinking box.
      kb.js wraps each one in .kb-figscroll; here it keeps a legible
      minimum width and scrolls sideways within that wrapper instead of
      shrinking, with the scrollbar left visible as the affordance.

   2. The line-by-line code tables. Three columns, one of them
      `white-space: pre`, will not fit however hard they are squeezed, and
      a table that scrolls sideways hides the explanation column - which is
      the half a student actually needs. Below 720px each row becomes a
      stack instead: line number, then the code, then what it means.

   3. Ordinary tables and block artwork, which only need tightening.
   --------------------------------------------------------------------- */

/* Wrapper is inert on a desktop; it exists so the phone rules have
   something to scroll that is not the page. */
.kb-content .kb-figscroll { margin: 0; }

@media (max-width: 720px) {
  .kb-content .kb-figscroll {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    padding-bottom: 2px;
  }
  /* --fig-w is the diagram's own viewBox width, capped by kb.js, so a
     figure that already fits gets no scrollbar. */
  .kb-content .kb-figscroll > svg { min-width: var(--fig-w, 560px); }
  .kb-content .kb-figscroll::-webkit-scrollbar { height: 7px; }
  .kb-content .kb-figscroll::-webkit-scrollbar-thumb {
    background: var(--line); border-radius: 999px;
  }

  /* ---- the line-by-line walkthroughs, stacked ---- */
  .kb-content table.kb-codewalk,
  .kb-content table.kb-codewalk tbody,
  .kb-content table.kb-codewalk tr { display: block; width: 100%; }
  .kb-content table.kb-codewalk thead { display: none; }
  .kb-content table.kb-codewalk { overflow-x: visible; }
  .kb-content table.kb-codewalk tr {
    border-bottom: 1px solid var(--line);
    padding: 10px 0;
  }
  .kb-content table.kb-codewalk tr:last-child { border-bottom: none; }
  .kb-content table.kb-codewalk td {
    display: block; width: auto; border: none; padding: 0; text-align: left;
  }
  .kb-content table.kb-codewalk td.cw-n {
    font-size: .68rem; letter-spacing: .05em; text-transform: uppercase;
    color: var(--ink2); margin-bottom: 3px;
  }
  .kb-content table.kb-codewalk td.cw-n::before { content: "line "; }
  .kb-content table.kb-codewalk td.cw-c {
    overflow-x: auto; scrollbar-width: thin;
    background: var(--surface2); border: 1px solid var(--line);
    border-radius: 6px; padding: 7px 9px; margin-bottom: 6px;
  }
  .kb-content table.kb-codewalk td.cw-s { font-size: .86rem; line-height: 1.55; }
  /* A blank line in a listing carries no explanation - on a phone the
     stacked, empty row is just noise. */
  .kb-content table.kb-codewalk tr.cw-blank { display: none; }

  /* ---- ordinary tables ---- */
  .kb-content table { font-size: .84rem; }
  .kb-content th, .kb-content td { padding: 7px 9px; }

  /* ---- block reference entries ---- */
  .kb-content .kb-block-copy { min-width: 0; flex-basis: 100%; }
  .kb-block-cat { padding-left: 11px; margin: 20px 0; }

  /* ---- the rest ---- */
  .kb-content .kb-open-sample-btn { width: 100%; justify-content: center; }
  .kb-components-row { gap: 10px; }
  .kb-content h3 { font-size: 1rem; }
}

@media (prefers-reduced-motion: reduce) { .kb-sidebar { transition: none; } }
button:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--amber); outline-offset: 2px;
}

/* =====================================================================
   ROS 2 Python dashboard (ros2.html)
   Shares the workbench chrome with the Arduino IDE; the lagoon-teal
   accent is what tells the two tracks apart at a glance.
   ==================================================================== */

/* The gate covers the page until /api/auth/me confirms ROS access, so an
   unauthorised workspace never flashes on screen. */
.ros-gate { min-height: 100vh; display: grid; place-items: center; padding: 24px; background: var(--glass); }
.ros-gate-card {
  display: flex; flex-direction: column; align-items: center; gap: 18px;
  text-align: center; color: #E8E6E1; max-width: 460px;
}
.ros-gate-card p { margin: 0; line-height: 1.6; color: #9AA2AA; }

.ros-topbar { border-bottom-color: var(--teal); }
.ros-badge {
  background: var(--teal); color: #fff; font-family: var(--font-body);
  font-size: .68rem; font-weight: 700; letter-spacing: .6px; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; vertical-align: middle; margin-left: 2px;
}

/* ---------------- robot picker ---------------- */
.robot-picker { display: inline-flex; align-items: center; gap: 8px; }
.robot-picker-label {
  font-size: .7rem; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: #9AA2AA;
}
#robotSelect {
  background: #24272B; color: #E8E6E1; border: 1px solid #3A3F45;
  border-radius: 8px; padding: 6px 10px; font: 500 .9rem var(--font-body); min-width: 150px;
}

.status-dot {
  width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0;
  background: var(--ink2); box-shadow: 0 0 0 3px rgba(255, 255, 255, .06);
}
.status-dot.is-online { background: var(--ok); }
.status-dot.is-error  { background: var(--err); }
.status-dot.is-offline { background: #6B7280; }
/* A running node is the one state worth catching out of the corner of an eye. */
.status-dot.is-running { background: var(--amber); animation: robot-pulse 1.2s ease-in-out infinite; }
@keyframes robot-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232, 147, 12, .55); }
  50%      { box-shadow: 0 0 0 6px rgba(232, 147, 12, 0); }
}

/* ---------------- emergency stop ---------------- */
.estop {
  background: var(--err) !important; color: #fff !important;
  border: 2px solid #8E2A20 !important; font-family: var(--font-display);
  font-weight: 800; letter-spacing: .4px; padding: 8px 16px !important;
  box-shadow: 0 3px 0 #8E2A20;
}
.estop:hover { background: #B93C30 !important; border-color: #8E2A20 !important; }
.estop:active { transform: translateY(2px); box-shadow: 0 1px 0 #8E2A20; }
.estop.firing { animation: estop-flash .2s steps(2) 3; }
@keyframes estop-flash { to { background: #fff !important; color: var(--err) !important; } }

/* ---------------- code panel extras ---------------- */
.code-lines { font: 500 .72rem var(--font-mono); color: var(--ink2); text-transform: none; letter-spacing: 0; }

/* ---------------- My Robots ---------------- */
.robots-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 22px; }
.robot-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface2);
}
.robot-id { display: flex; align-items: center; gap: 12px; min-width: 0; }
.robot-id strong { display: block; font-size: .95rem; }
.robot-sub { display: block; font: 400 .76rem var(--font-mono); color: var(--ink2); margin-top: 2px; }
.robot-actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* The robot's own software, above the workspace: a quieter strip than the
   amber banners, because it is a normal part of every lesson rather than a
   warning about one. */
.bringup-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 16px; background: var(--surface2);
  border-bottom: 1px solid var(--line); font-size: .82rem;
}
.bringup-state { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.bringup-hint { color: var(--ink2); font-size: .78rem; }
.bringup-bar button[disabled] { opacity: .45; cursor: default; }

.robot-lidar-row {
  display: flex; align-items: center; gap: 8px;
  margin: 4px 0 0; font-size: .82rem; color: var(--ink2);
}
.robot-lidar-row input { width: auto; margin: 0; }

.robot-add { border-top: 1px solid var(--line); padding-top: 16px; }
.robot-add h3 {
  font-family: var(--font-display); font-size: .95rem; margin: 0 0 12px;
}
.robot-add .form-row { align-items: flex-end; }
/* The robot-type blurb follows the inputs instead of a heading, so it needs
   normal clearance rather than the negative top margin .hint uses to tuck
   itself under a title. */
.robot-add .form-row + .hint { margin: 10px 0 0; }

/* ---------------- toasts ---------------- */
.toast-host {
  position: fixed; right: 18px; bottom: 18px; z-index: 90;
  display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
  pointer-events: none;
}
.toast {
  background: var(--glass); color: #E8E6E1; border-left: 4px solid var(--teal);
  border-radius: 8px; padding: 11px 16px; font-size: .85rem; max-width: 380px;
  box-shadow: 0 12px 30px rgba(22, 24, 27, .28);
  animation: toast-in .18s ease-out;
}
.toast-ok  { border-left-color: var(--ok); }
.toast-err { border-left-color: var(--err); }
.toast.leaving { opacity: 0; transform: translateY(6px); transition: opacity .3s ease, transform .3s ease; }
@keyframes toast-in { from { opacity: 0; transform: translateY(10px); } }

@media (max-width: 900px) {
  .robot-row { flex-direction: column; align-items: flex-start; }
  .toast-host { left: 18px; right: 18px; align-items: stretch; }
  .toast { max-width: none; }
}

/* The ROS 2 pill sits beside the Active/Disabled one in the admin users
   table; teal (not green) so the two switches stay tellable apart. */

/* Difficulty picker beside the "In samples" toggle in the ROS 2 project list. */
.proj-difficulty {
  border: 1.5px solid var(--line); border-radius: 6px; padding: 4px 8px;
  font: 500 .78rem var(--font-body); background: var(--surface2); color: var(--ink);
}
.access-toggle.sample-toggle.is-active { border-color: var(--teal); color: var(--teal); }
.access-toggle.sample-toggle:disabled { opacity: .55; }

/* =====================================================================
   Tasks, notifications and the task brief panel
   ===================================================================== */

/* ---- the bell, in the dark topbar ---- */
.bell-wrap { position: relative; display: flex; }
.bell-btn {
  position: relative; background: transparent; color: #E8E6E1;
  border: 1px solid #3A3F45; border-radius: 8px; padding: 7px 10px;
  display: flex; align-items: center; cursor: pointer;
}
.bell-btn:hover { border-color: var(--amber); color: var(--amber); }
.bell-badge {
  position: absolute; top: -6px; right: -6px; min-width: 18px; height: 18px;
  padding: 0 5px; border-radius: 999px; background: var(--amber); color: #1D2126;
  font: 700 .68rem var(--font-body); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 2px var(--glass);
}
.bell-panel {
  position: absolute; top: calc(100% + 10px); right: 0; width: 350px; z-index: 95;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: 0 18px 40px rgba(20, 22, 25, .22); overflow: hidden;
}
.bell-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 11px 14px; border-bottom: 1px solid var(--line); background: var(--surface2);
}
.bell-head strong { font-family: var(--font-display); font-size: .92rem; }
/* Named through the panel for the same reason, which is what the !important
   that used to be here was really working around. */
.bell-panel .bell-readall {
  background: none; border: none; border-radius: 0; color: var(--ink2);
  cursor: pointer; font: 500 .78rem var(--font-body); padding: 0;
}
.bell-panel .bell-readall:hover { color: var(--ink); text-decoration: underline; }
.bell-list { list-style: none; margin: 0; padding: 0; max-height: 380px; overflow-y: auto; }
/* Every rule below names the panel as well as the part, and that is on
   purpose: the bell mounts inside .actions in the dark topbar, so a bare
   `.bell-item` (0,1,0) loses to `.actions button` (0,1,1) and the rows come
   out with the topbar's near-white text on this white panel - which is what
   made the notification title unreadable - plus its dark border and padding. */
.bell-panel .bell-item {
  display: flex; gap: 10px; align-items: flex-start; width: 100%; text-align: left;
  padding: 11px 14px; background: none; border: none; border-bottom: 1px solid var(--line);
  border-radius: 0; cursor: pointer; font: 400 .82rem var(--font-body); color: var(--ink);
}
.bell-panel .bell-item:hover { background: var(--surface2); border-color: var(--line); }
.bell-item.is-unread { background: color-mix(in srgb, var(--amber) 7%, transparent); }
.bell-item-icon {
  flex-shrink: 0; width: 28px; height: 28px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--teal-soft); color: var(--teal);
}
.bell-item-icon svg { width: 15px; height: 15px; }
.bell-kind-task_graded { background: var(--amber-soft); color: #A96A05; }
.bell-kind-task_expired { background: #FBE7E4; color: var(--err); }
.bell-item-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.bell-panel .bell-item-text strong { font-weight: 600; color: var(--ink); }
.bell-item-text span { color: var(--ink2); }
.bell-item-text time { color: var(--ink2); font-size: .74rem; }
.bell-dot {
  flex-shrink: 0; width: 8px; height: 8px; border-radius: 50%;
  background: var(--amber); margin-top: 6px;
}
.bell-empty { padding: 22px 14px; text-align: center; color: var(--ink2); font-size: .82rem; }
.bell-all {
  display: block; padding: 10px 14px; text-align: center; font-size: .78rem;
  color: var(--teal); text-decoration: none; background: var(--surface2);
  border-top: 1px solid var(--line);
}
.bell-all:hover { text-decoration: underline; }

/* The message modal dialogs.js builds for pages with no dialog of their own. */
.robo-alert { max-width: 420px; }
.robo-alert h2 { margin: 0 0 8px; font-size: 1.05rem; }
.robo-alert p { margin: 0 0 4px; color: var(--ink2); line-height: 1.55; }

/* ---- /notifications ---- */
.notes-page { max-width: 860px; margin: 0 auto; padding: 26px 20px 60px; }
.notes-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 16px; flex-wrap: wrap; margin-bottom: 18px;
}
.notes-head h1 { font-family: var(--font-display); margin: 0 0 4px; font-size: 1.5rem; }
.notes-head .hint { margin: 0; }
.notes-controls { display: flex; align-items: center; gap: 10px; }
.notes-filter { display: flex; gap: 2px; background: var(--surface2); border-radius: 999px; padding: 3px; }
.notes-filter button {
  border: none; background: none; border-radius: 999px; padding: 6px 14px;
  font: 500 .82rem var(--font-body); color: var(--ink2); cursor: pointer;
  display: flex; align-items: center; gap: 6px;
}
.notes-filter button.active { background: var(--surface); color: var(--ink); box-shadow: 0 1px 3px rgba(0,0,0,.08); }
.notes-pill {
  background: var(--amber); color: #1D2126; border-radius: 999px;
  padding: 0 6px; font: 700 .68rem var(--font-body);
}
.notes-month {
  font: 600 .78rem var(--font-body); text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink2); margin: 22px 0 8px;
}
.notes-group {
  list-style: none; margin: 0; padding: 0;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
}
.notes-item {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 13px 16px; background: none; border: none; cursor: pointer;
  font: 400 .86rem var(--font-body); color: var(--ink);
}
.notes-group li + li .notes-item { border-top: 1px solid var(--line); }
.notes-item:hover { background: var(--surface2); }
.notes-item.is-unread { background: color-mix(in srgb, var(--amber) 6%, transparent); }
.notes-kind {
  flex-shrink: 0; width: 92px; font: 600 .7rem var(--font-body);
  text-transform: uppercase; letter-spacing: .05em; color: var(--teal);
}
.notes-kind-task_graded { color: #A96A05; }
.notes-kind-task_expired { color: var(--err); }
.notes-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.notes-text span { color: var(--ink2); font-size: .8rem; }
.notes-item time { flex-shrink: 0; color: var(--ink2); font-size: .76rem; }
.notes-empty {
  padding: 40px; text-align: center; color: var(--ink2);
  background: var(--surface); border: 1px dashed var(--line); border-radius: 12px;
}

/* ---- the task brief: a small window floating over the workspace ----
   Blockly ships its own stylesheet, in which the toolbox is z-index 70 and the
   block drag surface 80. The panel has to clear both or the category strip
   paints straight over it. Toasts (90) and the header popovers (95) still come
   out on top, which is what you want when one of them fires. */
.task-panel {
  position: fixed; left: 16px; bottom: 16px; z-index: 85;
  width: min(420px, 42vw); display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 16px 38px rgba(20, 22, 25, .2); overflow: hidden;
}
/* Once dragged it is placed by left/top, so the default corner must let go. */
.task-panel.is-floating { bottom: auto; }
.task-panel.is-dragging {
  box-shadow: 0 24px 54px rgba(20, 22, 25, .34); user-select: none;
}
/* The title bar doubles as the drag handle. touch-action keeps a finger drag
   from scrolling the page out from under it. */
.task-panel-bar {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  background: var(--glass); color: #E8E6E1;
  cursor: move; touch-action: none; user-select: none;
}
.task-panel-bar button { cursor: pointer; }
.task-panel-grip { display: flex; flex-shrink: 0; color: #71787F; }
.task-panel-grip svg { width: 13px; height: 13px; }
.task-panel-bar:hover .task-panel-grip { color: var(--amber); }
.task-panel-toggle {
  background: transparent; border: 1px solid #3A3F45; border-radius: 7px;
  color: #E8E6E1; padding: 4px; cursor: pointer; display: flex;
}
.task-panel-toggle:hover { border-color: var(--amber); color: var(--amber); }
.task-panel-toggle svg { transition: transform .18s ease; }
.task-panel.is-collapsed .task-panel-toggle svg { transform: rotate(-90deg); }
.task-panel-label {
  font: 600 .66rem var(--font-body); text-transform: uppercase;
  letter-spacing: .08em; color: var(--amber);
}
.task-panel-title {
  flex: 1; min-width: 0; font: 600 .84rem var(--font-body);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.task-timer {
  font: 600 .88rem var(--font-mono); background: #24272B; color: #E8E6E1;
  border: 1px solid #3A3F45; border-radius: 7px; padding: 4px 9px;
}
.task-timer.is-warn { color: var(--amber); border-color: var(--amber); }
.task-timer.is-danger {
  color: #fff; background: var(--err); border-color: var(--err);
  animation: task-pulse 1s ease-in-out infinite;
}
@keyframes task-pulse { 50% { opacity: .62; } }
.task-savestate { font: 400 .7rem var(--font-body); color: #9BA1A8; white-space: nowrap; }
.task-savestate.is-ok { color: #6FCF97; }
.task-savestate.is-err { color: #F2865E; }
.task-submit {
  background: var(--amber); color: #1D2126; border: none; border-radius: 7px;
  padding: 6px 12px; font: 600 .8rem var(--font-body); cursor: pointer;
}
.task-submit:hover { filter: brightness(1.06); }
.task-panel-body { padding: 12px 14px; overflow-y: auto; max-height: 46vh; }
.task-panel.is-collapsed .task-panel-body { display: none; }
.task-meta {
  display: flex; flex-wrap: wrap; gap: 6px 18px; margin-bottom: 10px;
  padding-bottom: 10px; border-bottom: 1px solid var(--line);
}
.task-meta div { display: flex; flex-direction: column; }
.task-meta dt {
  font: 600 .64rem var(--font-body); text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink2);
}
.task-meta dd { margin: 0; font: 500 .82rem var(--font-body); color: var(--ink); }
.task-instructions { font-size: .86rem; line-height: 1.55; }
.task-instructions > :first-child { margin-top: 0; }
.task-note {
  margin: 10px 0 0; padding: 9px 11px; border-radius: 8px;
  background: var(--amber-soft); color: #7A4E04; font-size: .8rem;
}
.task-feedback {
  margin-top: 12px; padding: 10px 12px; border-radius: 8px;
  background: var(--teal-soft); color: #1F5F5A; font-size: .86rem;
}
.task-feedback p { margin: 4px 0 0; }

/* Blockly cannot go read-only after injection, so a closed task is covered. */
#blocklyDiv { position: relative; }
.workspace-lock {
  position: absolute; inset: 0; z-index: 30;
  background: rgba(242, 240, 236, .82); backdrop-filter: blur(2px);
  display: flex; align-items: center; justify-content: center;
}
.workspace-lock-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  padding: 26px 30px; text-align: center; max-width: 340px;
  box-shadow: 0 14px 34px rgba(20, 22, 25, .16);
}
.workspace-lock-card svg { width: 34px; height: 34px; color: var(--ink2); }
.workspace-lock-card strong { display: block; margin: 10px 0 6px; font-family: var(--font-display); }
.workspace-lock-card p { margin: 0; color: var(--ink2); font-size: .86rem; }

/* A closed CV task: the veil sits over the data and training panes only,
   so the trained model can still be tested from the pane beside them. */
.cv-workspace.is-task-locked #cvClassesPane,
.cv-workspace.is-task-locked #cvTrainPane { position: relative; }
.cv-workspace.is-task-locked .workspace-lock { border-radius: inherit; }
/* ...but the "back to projects" row stays reachable above the veil. */
.cv-workspace.is-task-locked #cvClassesPane .cv-workspace-head { position: relative; z-index: 31; }

/* ---- the student's task list ---- */
.task-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.task-list-item {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; text-align: left; padding: 12px 14px; cursor: pointer;
  background: var(--surface2); border: 1px solid var(--line); border-radius: 10px;
  font: 400 .88rem var(--font-body); color: var(--ink);
}
.task-list-item:hover { border-color: var(--teal); background: var(--surface); }
.task-list-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.task-list-sub {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  color: var(--ink2); font-size: .77rem;
}
.task-chip {
  border-radius: 999px; padding: 1px 8px; font: 600 .68rem var(--font-body);
  background: var(--surface); border: 1px solid var(--line); color: var(--ink2);
}
.task-chip.is-test { background: var(--amber-soft); border-color: var(--amber); color: #8A5A04; }
.task-chip.is-closed { background: #FBE7E4; border-color: var(--err); color: var(--err); }
.task-list-state, .task-state {
  flex-shrink: 0; border-radius: 999px; padding: 3px 10px;
  font: 600 .72rem var(--font-body); background: var(--surface2);
  border: 1px solid var(--line); color: var(--ink2); white-space: nowrap;
}
.task-state.is-live, .task-state-in_progress { border-color: var(--teal); color: var(--teal); }
.task-state.is-done, .task-state-submitted { border-color: var(--ok); color: var(--ok); }
.task-state.is-marked, .task-state-graded { background: var(--amber-soft); border-color: var(--amber); color: #8A5A04; }
.task-state.is-expired, .task-state-expired { border-color: var(--err); color: var(--err); }

/* ---- teacher: tasks, groups, reports ---- */
.form-row-checks { align-items: center; gap: 18px; }
.row-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.table-btn.primary-btn { border-color: var(--teal); color: var(--teal); }
.table-scroll { overflow-x: auto; }
.table-scroll .admin-table { min-width: 780px; }
.dialog-wide { max-width: min(1100px, 94vw); width: 1100px; }
.roll-input.is-narrow { width: 60px; }
.mark-input { width: 72px; }
.feedback-input { width: 100%; min-width: 150px; }
.mark-input, .feedback-input {
  border: 1.5px solid var(--line); border-radius: 6px; padding: 5px 8px;
  font: 400 .82rem var(--font-body); background: var(--surface); color: var(--ink);
}
.mark-input:focus, .feedback-input:focus { outline: none; border-color: var(--teal); }
.task-progress { display: flex; flex-direction: column; }
.task-progress .student-sub { margin: 0; }

.check-list {
  list-style: none; margin: 8px 0 0; padding: 8px; max-height: 320px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface2);
}
.check-list li + li { margin-top: 2px; }
.check-list .checkbox-row {
  display: flex; align-items: center; gap: 9px; padding: 6px 8px;
  border-radius: 7px; cursor: pointer; font-size: .86rem;
}
.check-list .checkbox-row:hover { background: var(--surface); }
.group-member-search { display: flex; flex-direction: column; gap: 6px; margin: 12px 0; font-size: .9rem; font-weight: 500; }
.app-search { position: relative; display: block; }
.app-search-icon { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); width: 17px; height: 17px; color: var(--ink2); pointer-events: none; }
.app-search input {
  width: 100%; border: 1.5px solid var(--line); border-radius: 9px;
  padding: 10px 12px 10px 36px; font: inherit; color: var(--ink); background: var(--surface2);
  box-shadow: 0 2px 8px rgba(22, 24, 27, .04); transition: border-color .15s ease, box-shadow .15s ease;
}
.app-search input::placeholder { color: var(--ink2); }
.app-search input:hover { border-color: #B9B5AD; }
.app-search input:focus { outline: none; border-color: var(--teal); box-shadow: 0 0 0 3px rgba(46, 143, 136, .14); }
.form input.is-invalid { border-color: var(--err); box-shadow: 0 0 0 3px rgba(207, 74, 61, .12); }
.app-search-label { min-width: 260px; }
.assign-search { margin: 8px 0; }
.assign-search input { font-size: .82rem; padding-top: 8px; padding-bottom: 8px; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.assign-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.assign-cols h3 {
  font: 600 .78rem var(--font-body); text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink2); margin: 0; display: flex; justify-content: space-between; align-items: center;
}

/* Enable/disable switch on a task row. */
.switch { display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  width: 38px; height: 21px; border-radius: 999px; background: var(--line);
  display: flex; align-items: center; padding: 2px; transition: background .16s ease;
}
.switch-thumb {
  width: 17px; height: 17px; border-radius: 50%; background: var(--surface);
  box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .16s ease;
}
.switch input:checked + .switch-track { background: var(--teal); }
.switch input:checked + .switch-track .switch-thumb { transform: translateX(17px); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--teal); outline-offset: 2px; }

@media (max-width: 900px) {
  .task-panel { left: 10px; right: 10px; width: auto; }
  .assign-cols { grid-template-columns: 1fr; }
  .bell-panel { width: min(340px, calc(100vw - 28px)); }
}

/* Right-aligned group inside a table toolbar (the add-time control). */
.toolbar-gap { flex: 1; }
.toolbar-field {
  display: flex; align-items: center; gap: 6px;
  font: 500 .8rem var(--font-body); color: var(--ink2);
}
.toolbar-field input {
  width: 62px; border: 1.5px solid var(--line); border-radius: 6px; padding: 5px 8px;
  font: 400 .82rem var(--font-body); background: var(--surface); color: var(--ink);
}
.toolbar-field input:focus { outline: none; border-color: var(--teal); }

/* ---- "My tasks" in the topbar, with its outstanding count ---- */
.tasks-wrap { position: relative; display: flex; }
.tasks-badge {
  position: absolute; top: -6px; right: -6px; min-width: 18px; height: 18px;
  padding: 0 5px; border-radius: 999px; background: var(--amber); color: #1D2126;
  font: 700 .68rem var(--font-body); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 2px var(--glass); pointer-events: none;
}

/* ---- the task list itself ---- */
.task-list-dialog { max-width: min(680px, 94vw); width: 680px; }
.task-list-dialog .notes-filter { margin: 10px 0 14px; align-self: flex-start; }
.task-list-dialog .task-list { max-height: 58vh; overflow-y: auto; }
.task-list-item { align-items: stretch; }
.task-list-main { flex: 1; gap: 5px; }
.task-list-end {
  display: flex; flex-direction: column; align-items: flex-end;
  justify-content: space-between; gap: 8px; flex-shrink: 0;
}
.task-list-go { font: 600 .76rem var(--font-body); color: var(--teal); white-space: nowrap; }
.task-list-item:hover .task-list-go { text-decoration: underline; }
.task-list-note {
  font-size: .78rem; color: var(--ink2); font-style: italic;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 42ch;
}
.task-when { font-weight: 600; color: var(--ink); }
.task-when.is-urgent { color: var(--err); }

/* =====================================================================
   Form polish: switch cards, field help, action footers
   ===================================================================== */

/* Safety net. `.form label.checkbox-row` only un-stacks a checkbox inside a
   `.form`; a `.form-row` sets flex-direction:column on its labels and made
   the box sit above its own text with a 100%-wide hit area. */
.form-row label.checkbox-row { flex-direction: row; align-items: center; gap: 8px; }
.form-row label.checkbox-row input { width: auto; flex: none; }

/* A one-line note under a field. Regular weight so it reads as help text
   rather than a second label. */
.field-help {
  font: 400 .75rem/1.4 var(--font-body); color: var(--ink2); margin-top: -1px;
}

/* An option that changes how the whole task behaves deserves more than a
   checkbox: the switch says which way it is set, the text says what that
   costs. */
.setting-cards { display: flex; flex-direction: column; gap: 10px; margin: 18px 0; }
.setting-card {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 14px 16px; border: 1.5px solid var(--line); border-radius: 12px;
  background: var(--surface2); transition: border-color .15s ease, background .15s ease;
}
.setting-card:has(input:checked) {
  border-color: var(--teal);
  background: color-mix(in srgb, var(--teal) 5%, transparent);
}
.setting-card > .switch { flex-shrink: 0; margin-top: 1px; }
.setting-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.setting-title {
  font: 600 .9rem var(--font-body); color: var(--ink); cursor: pointer; width: fit-content;
}
.setting-text p {
  margin: 0; font: 400 .8rem/1.5 var(--font-body); color: var(--ink2); max-width: 74ch;
}
/* The control a setting reveals sits at the end of its own row rather than
   underneath, so the card uses its width instead of growing taller. */
.setting-extra { margin-left: auto; align-self: center; flex-shrink: 0; }
.setting-extra label {
  display: flex; align-items: center; gap: 10px;
  font: 500 .82rem var(--font-body); color: var(--ink);
}

/* A number that means nothing without its unit, so the unit sits in the box. */
.input-suffix {
  display: inline-flex; align-items: center; align-self: flex-start;
  border: 1.5px solid var(--line); border-radius: 8px; background: var(--surface);
  overflow: hidden;
}
.input-suffix:focus-within { border-color: var(--teal); }
.input-suffix input {
  width: 76px; border: none; background: transparent; padding: 8px 10px;
  font: inherit; color: var(--ink);
}
.input-suffix input:focus { outline: none; }
.input-suffix > span {
  padding: 8px 11px; background: var(--surface2); border-left: 1.5px solid var(--line);
  font: 500 .78rem var(--font-body); color: var(--ink2); white-space: nowrap;
}

/* Actions sit below a rule, so the form has a visible end. */
.form-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line);
}
.form-actions .msg { margin: 0; }

/* The task brief is a paragraph or two, not a knowledge-base article. */
.kb-textarea.is-compact { min-height: 150px; font: .85rem/1.6 var(--font-body); }

/* =====================================================================
   Knowledge Base image editor - cropping, and sizing in the article
   ===================================================================== */

/* A published image must never break the reader's column, whatever pixel
   width the editor gave it. */
.kb-content img, .task-instructions img {
  max-width: 100%; height: auto; border-radius: 8px;
}

/* ---- crop dialog ---- */
.crop-dialog { max-width: min(1040px, 95vw); width: 1040px; }
.crop-layout { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; }

/* The stage centres the picture and can be wider than it, so everything drawn
   on top is offset by where the image actually lands - see layout(). The
   stage itself must NOT clip, or the handles get cut off at the edges. */
.crop-stage {
  position: relative; flex: 1 1 420px; min-width: 280px;
  align-self: flex-start; overflow: visible; padding: 10px;
  background: var(--glass); border-radius: 10px; line-height: 0;
  display: flex; justify-content: center;
}
/* The dimming outside the crop. It is a single enormous shadow, which has to
   be clipped to the picture - hence its own layer, so the clipping never
   reaches the handles. */
.crop-clip { position: absolute; overflow: hidden; pointer-events: none; border-radius: 4px; }
.crop-shade { position: absolute; box-shadow: 0 0 0 9999px rgba(20, 22, 25, .58); }
.crop-stage img {
  display: block; max-width: 100%; max-height: 54vh;
  width: auto; height: auto; user-select: none; -webkit-user-drag: none;
}
.crop-box {
  position: absolute; box-sizing: border-box;
  border: 1.5px solid #fff; cursor: move;
}
.crop-grid { position: absolute; inset: 0; pointer-events: none; }
.crop-grid::before, .crop-grid::after {
  content: ''; position: absolute; inset: 0; opacity: .5;
}
/* Rule of thirds, drawn with gradients rather than four more elements. */
.crop-grid::before {
  background:
    linear-gradient(to right, transparent calc(33.33% - .5px), rgba(255,255,255,.7) calc(33.33% - .5px),
      rgba(255,255,255,.7) calc(33.33% + .5px), transparent calc(33.33% + .5px)),
    linear-gradient(to right, transparent calc(66.66% - .5px), rgba(255,255,255,.7) calc(66.66% - .5px),
      rgba(255,255,255,.7) calc(66.66% + .5px), transparent calc(66.66% + .5px));
}
.crop-grid::after {
  background:
    linear-gradient(to bottom, transparent calc(33.33% - .5px), rgba(255,255,255,.7) calc(33.33% - .5px),
      rgba(255,255,255,.7) calc(33.33% + .5px), transparent calc(33.33% + .5px)),
    linear-gradient(to bottom, transparent calc(66.66% - .5px), rgba(255,255,255,.7) calc(66.66% - .5px),
      rgba(255,255,255,.7) calc(66.66% + .5px), transparent calc(66.66% + .5px));
}
.crop-handle {
  position: absolute; width: 14px; height: 14px; box-sizing: border-box;
  background: #fff; border: 1.5px solid var(--glass); border-radius: 3px;
}
.crop-handle[data-h="nw"] { top: -7px; left: -7px; cursor: nwse-resize; }
.crop-handle[data-h="n"]  { top: -7px; left: 50%; margin-left: -7px; cursor: ns-resize; }
.crop-handle[data-h="ne"] { top: -7px; right: -7px; cursor: nesw-resize; }
.crop-handle[data-h="e"]  { top: 50%; right: -7px; margin-top: -7px; cursor: ew-resize; }
.crop-handle[data-h="se"] { bottom: -7px; right: -7px; cursor: nwse-resize; }
.crop-handle[data-h="s"]  { bottom: -7px; left: 50%; margin-left: -7px; cursor: ns-resize; }
.crop-handle[data-h="sw"] { bottom: -7px; left: -7px; cursor: nesw-resize; }
.crop-handle[data-h="w"]  { top: 50%; left: -7px; margin-top: -7px; cursor: ew-resize; }

.crop-side { flex: 0 0 260px; min-width: 240px; }
.crop-side .field-block { margin: 0 0 16px; }
.crop-aspects { display: flex; flex-wrap: wrap; gap: 6px; }
.crop-aspects button {
  border: 1.5px solid var(--line); background: var(--surface); border-radius: 999px;
  padding: 5px 12px; font: 500 .78rem var(--font-body); color: var(--ink2); cursor: pointer;
}
.crop-aspects button:hover { border-color: var(--teal); color: var(--teal); }
.crop-aspects button.active { background: var(--teal); border-color: var(--teal); color: #fff; }
#kbCropQuality { width: 100%; accent-color: var(--teal); }

.crop-readout {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; margin: 0;
  padding: 12px; border-radius: 10px; background: var(--surface2);
}
.crop-readout div { display: flex; flex-direction: column; }
.crop-readout dt {
  font: 600 .64rem var(--font-body); text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink2);
}
.crop-readout dd { margin: 0; font: 500 .84rem var(--font-mono); color: var(--ink); }

/* ---- the toolbar that floats over a selected image ---- */
.kb-editor img.is-selected {
  outline: 2px solid var(--teal); outline-offset: 2px; border-radius: 4px;
}
.img-tools {
  position: fixed; z-index: 80; display: flex; align-items: center; gap: 6px;
  padding: 6px 8px; border-radius: 10px;
  background: var(--glass); color: #E8E6E1;
  box-shadow: 0 10px 26px rgba(20, 22, 25, .3);
}
.img-tools-size {
  font: 500 .72rem var(--font-mono); color: #9BA1A8; padding-right: 4px;
  border-right: 1px solid #3A3F45; margin-right: 2px; white-space: nowrap;
}
.img-tools-group { display: flex; gap: 2px; }
.img-tools button {
  background: transparent; color: #E8E6E1; border: 1px solid #3A3F45;
  border-radius: 7px; padding: 4px 9px; font: 500 .76rem var(--font-body); cursor: pointer;
}
.img-tools button:hover { border-color: var(--amber); color: var(--amber); }
.img-tools button.is-danger:hover { border-color: var(--err); color: #F2865E; }
.img-tools .input-suffix { border-color: #3A3F45; background: #24272B; }
.img-tools .input-suffix input { color: #E8E6E1; }
.img-tools .input-suffix > span {
  background: #1D2024; border-left-color: #3A3F45; color: #9BA1A8;
}
.input-suffix.is-tight input { width: 62px; padding: 4px 8px; font-size: .78rem; }
.input-suffix.is-tight > span { padding: 4px 8px; font-size: .7rem; }

/* The corner grip, sitting on the image's bottom-right. */
.img-handle {
  position: fixed; z-index: 80; width: 14px; height: 14px; box-sizing: border-box;
  background: var(--teal); border: 2px solid #fff; border-radius: 3px;
  cursor: nwse-resize; box-shadow: 0 2px 6px rgba(20, 22, 25, .35);
}

@media (max-width: 900px) {
  .crop-layout { flex-direction: column; }
  .crop-side { flex: 1 1 auto; width: 100%; }
  .crop-readout { grid-template-columns: 1fr 1fr; }
  .img-tools { flex-wrap: wrap; max-width: calc(100vw - 16px); }
}

/* =====================================================================
   Interaction polish
   Every clickable surface gets the same three things: a smooth transition,
   a visible hover state, and - where it reads as a primary action - a small
   lift. Added once, here, rather than per-component, so the feel stays
   identical everywhere instead of accumulating one-off tweaks. Kept purely
   additive (new selectors/properties only) so nothing already tuned above -
   the estop's press animation, the burn gauge - has to be touched.
   ===================================================================== */
button, input, select, textarea {
  transition: background-color .15s ease, border-color .15s ease,
    color .15s ease, box-shadow .15s ease, transform .1s ease;
}
input[type="checkbox"], input[type="radio"] { accent-color: var(--teal); }

.primary:hover, .btn-secondary:hover, .table-btn:hover:not(:disabled),
.kb-content .kb-open-sample-btn:hover { transform: translateY(-1px); }
.primary:active, .btn-secondary:active, .table-btn:active:not(:disabled) { transform: translateY(0); }

.table-btn:hover:not(:disabled) { border-color: var(--ink2); background: var(--surface); }
.table-btn.danger:hover:not(:disabled) { background: #FBE3DF; }
.table-btn.primary-btn:hover:not(:disabled) { background: var(--teal-soft); }
.table-btn.icon-only:hover:not(:disabled) { border-color: var(--teal); color: var(--teal); }

.proj-list li { transition: background-color .12s ease; }
.proj-list li:hover { background: var(--surface2); }
.proj-list button:hover { border-color: var(--teal); color: var(--teal); background: var(--surface); }
.proj-list button.danger:hover { background: #FBE3DF; color: var(--err); }

.admin-table tbody tr { transition: background-color .12s ease; }
.admin-table tbody tr:hover { background: var(--surface2); }

.actions button:hover:not(.burn):not(.estop), .topbar-utility button:hover { background: rgba(255, 255, 255, .06); }
.admin-tab:hover:not(.active) { background: rgba(255, 255, 255, .08); color: #E8E6E1; }
.subtab:hover:not(.active) { background: var(--surface2); color: var(--ink); }
.kb-mode:hover:not(.is-active) { border-color: var(--teal); color: var(--teal); }
.samples-filter button:hover:not(.active) { border-color: var(--teal); color: var(--teal); }
.notes-filter button:hover:not(.active) { color: var(--ink); }
.tab:hover:not(.active) { border-color: var(--teal); color: var(--teal); }
.pagination button:hover:not(:disabled) { border-color: var(--ink2); }
.dialog-actions button:not(.primary):hover { background: var(--surface); border-color: var(--ink2); }

/* A slimmer, on-brand scrollbar wherever an ordinary DOM element scrolls.
   Blockly draws its own workspace/flyout scrollbars in SVG, so this never
   touches the block canvas - only things like the KB sidebar, dropdowns,
   dialogs and the notification list. */
html { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background-color: var(--line); border-radius: 999px;
  border: 2px solid var(--bg); background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background-color: #C9C4B9; }

/* =====================================================================
   Responsive: tablet and phone
   Everything above this point already handles its own component at
   max-width:900px; this section fills what that pass left behind (the
   dashboard chrome shared by every page, and the Blockly workspace, which
   is the one screen a fixed-width side panel makes genuinely unusable on a
   phone) and adds a tighter pass for phone-width screens specifically.
   ===================================================================== */
@media (max-width: 1024px) {
  .admin-content { padding: 16px; }
}

@media (max-width: 760px) {
  /* Topbar layout itself lives in the "Responsive shell" section below;
     this block only tightens the pieces around it. */
  .table-toolbar button { padding: 6px 9px; font-size: .82rem; }

  .admin-tabs { padding: 8px 10px; }
  .admin-tab { padding: 7px 12px; font-size: .8rem; }
  .admin-content { padding: 12px; }
  .admin-card { padding: 14px 16px; }
  .form-row { gap: 10px; }
  .form-row label { min-width: 0; flex-basis: 100%; }

  /* Blockly needs real width to be usable at all - on a narrow viewport the
     code/serial panel drops below the canvas instead of squeezing it into a
     sliver a few dozen pixels wide. (With shell.js loaded the two never
     share the screen at all - see .ide-shell[data-view] below.) */
  .workspace-row { flex-direction: column; }
  .code-panel { width: 100%; height: 42vh; border-left: none; border-top: 1px solid var(--line); }
  .teacher-edit-banner { flex-wrap: wrap; padding: 8px 12px; }
  .teacher-edit-banner button { margin-left: 0; }

  .setting-card { flex-wrap: wrap; }
  .setting-extra { margin-left: 0; align-self: stretch; width: 100%; }
  .plan-item-row { grid-template-columns: 1fr; gap: 4px; }
  .plan-kit-row { grid-template-columns: 1fr auto; }
  .plan-kit-row strong { grid-column: 1 / -1; }

  /* Every dialog already caps its own width at some min(fixed, Nvw) - this
     only tightens the padding, so a phone-width dialog stops wasting a
     chunk of its already-narrow content area on margin. */
  dialog { padding: 16px 18px; }

  .notes-page { padding: 18px 14px 40px; }
  .notes-head { align-items: flex-start; }
  .notes-item { flex-wrap: wrap; }
  .notes-kind { width: auto; }
  .notes-item time { order: 3; width: 100%; margin-top: 2px; }

  .task-meta { gap: 6px 12px; }
  .kb-component-card { width: 84px; }
}

/* =====================================================================
 * Project Showcase - standalone page (public/showcase.html)
 * =================================================================== */
.showcase-main { padding: 32px 40px; max-width: 1200px; margin: 0 auto; }
.showcase-heading { font-family: var(--font-display); font-size: 1.6rem; margin: 0 0 6px; }
.showcase-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 18px 0 12px; }
.showcase-search { flex: 1 1 280px; min-width: 220px; }
.showcase-toolbar select {
  border: 1.5px solid var(--line); border-radius: 9px; padding: 10px 12px;
  font: inherit; background: var(--surface2); color: var(--ink);
}
.showcase-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 14px; margin-top: 16px;
}
.showcase-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  padding: 16px; display: flex; flex-direction: column; gap: 8px;
}
.showcase-card.showcase-locked { opacity: .6; }
.showcase-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.showcase-card-title { font: 700 1rem var(--font-display); color: var(--ink); flex: 1 1 auto; }
.showcase-card-desc { font-size: .82rem; color: var(--ink2); line-height: 1.45; flex: 1; margin: 0; }
.showcase-card-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 4px; }
.track-tag {
  display: inline-flex; align-items: center;
  font-family: var(--font-display); font-size: .7rem; font-weight: 700;
  letter-spacing: .4px; text-transform: uppercase;
  padding: 2.5px 8px; border-radius: 999px; white-space: nowrap;
  background: var(--surface2); color: var(--ink2); border: 1.5px solid var(--line);
}
.track-tag.ros2 { border-color: var(--curtain); color: var(--curtain); }

/* ---------------- admin Project Showcase toolbar ---------------- */
/* `space-between` on a row of very differently-sized controls (a wide
   search box, two compact selects, a wide button) puts equal gaps between
   unequal shapes, which reads as randomly scattered rather than aligned.
   Letting the search box absorb the free space instead keeps the filters
   and the add-button clustered together with one consistent gap. */
.showcase-toolbar.spread { justify-content: flex-start; flex-wrap: wrap; row-gap: 10px; }
.showcase-toolbar .app-search-label { flex: 1 1 240px; min-width: 200px; }
.showcase-toolbar select {
  flex: 0 0 auto;
  border: 1.5px solid var(--line); border-radius: 9px; padding: 0 14px;
  font: inherit; background: var(--surface2); color: var(--ink);
  box-shadow: 0 2px 8px rgba(22, 24, 27, .04);
}
.showcase-toolbar .btn-with-icon { flex: 0 0 auto; padding: 0 20px; }

/* ---------------- admin Project Showcase (cards, not a table) ---------------- */
.admin-showcase-grid { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 18px; }
.showcase-card-admin { gap: 0; }

/* The title row never wraps: a long title is truncated with an ellipsis
   (full text still available via the native title tooltip) so the track
   tag always lands in the same top-right spot instead of sometimes
   sharing the title's line and sometimes falling to a line of its own. */
.showcase-card-admin .showcase-card-head:not(.showcase-card-meta) { flex-wrap: nowrap; }
.showcase-card-admin .showcase-card-title {
  font-size: 1.05rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.showcase-card-admin .track-tag { flex-shrink: 0; }

/* The board name gets its own fixed line under the title instead of
   sharing the badges row, where its position used to shift depending on
   how wide the difficulty pill next to it happened to be. */
.showcase-card-board { display: block; font-size: .8rem; color: var(--ink2); margin-top: 3px; }

.showcase-card-admin .showcase-card-meta { margin-top: 8px; row-gap: 8px; }
.showcase-card-updated { font-size: .74rem; color: var(--ink2); margin-left: auto; flex-shrink: 0; }

/* Every control block below the title/badges is visually its own section,
   separated by a hairline so the eye can parse "what this card IS" from
   "what you can edit on it" at a glance. */
.showcase-card-controls,
.showcase-card-article,
.showcase-card-admin .showcase-card-actions {
  border-top: 1px solid var(--line);
  margin-top: 12px; padding-top: 12px;
}

.showcase-card-controls { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.showcase-card-controls label {
  display: flex; flex-direction: column; gap: 4px; font-size: .72rem; font-weight: 600;
  color: var(--ink2); min-width: 0;
}
.showcase-card-controls select, .showcase-card-controls input {
  width: 100%; box-sizing: border-box;
}

.showcase-card-article { display: flex; flex-direction: column; gap: 7px; }
.showcase-card-label { font-size: .72rem; font-weight: 600; color: var(--ink2); }
.showcase-card-article .article-cell { flex-wrap: nowrap; }
.showcase-card-article .app-search { flex: 1 1 auto; min-width: 0; }
.showcase-card-article .article-search-input { width: 100%; box-sizing: border-box; }
.showcase-card-article .table-btn.icon-only { flex-shrink: 0; }

/* Cards in the same grid row stretch to equal height (showcase-card is a
   column flexbox), so pinning the actions to the bottom keeps every row's
   edit/delete buttons aligned even when one card's badges wrap to an extra line. */
.showcase-card-admin .showcase-card-actions { margin-top: auto; }

.sample-type-options { display: flex; flex-direction: column; gap: 10px; margin: 14px 0; }
.sample-type-option {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  border: 1.5px solid var(--line); background: var(--surface2); border-radius: 10px;
  padding: 14px 16px; font: 600 .95rem var(--font-body); color: var(--ink);
}
.sample-type-option:hover:not(:disabled) { border-color: var(--teal); color: var(--teal); background: var(--surface); }
.sample-type-option .btn-icon { width: 22px; height: 22px; flex-shrink: 0; }
.sample-type-option:disabled { opacity: .5; cursor: not-allowed; }
.sample-type-option em { font-style: normal; color: var(--ink2); font-weight: 500; }

@media (max-width: 700px) {
  .showcase-main { padding: 20px; }
}

@media (max-width: 480px) {
  .auth { padding: 14px; }
  .auth-forms { padding: 22px; }
  .auth-brand { padding: 28px 22px; }
  .auth-brand h1 { font-size: 1.7rem; }
  .code-panel { height: 46vh; }
  .crop-readout { grid-template-columns: 1fr; }
  .form-actions { flex-direction: column; align-items: stretch; }
  .form-actions button { width: 100%; }
}

/* =====================================================================
   Responsive shell
   The topbar markup is identical on every page (see the comment on
   .topbar); these rules re-flow it per breakpoint and add the phone-only
   chrome: the swipeable toolbar strip with edge fades, full-width sheets
   for the account / notification menus, and the Blocks | Code | Console
   switcher that shell.js appends to the Blockly workbench.
   ===================================================================== */

/* ---- edge fades on horizontal strips (shell.js sets .at-start/.at-end) --
   The pseudo-elements are sticky flex items inside the scroll container,
   so they stay pinned to its edges while the buttons slide underneath. */
.admin-tabs { position: relative; scrollbar-width: none; }
.admin-tabs::-webkit-scrollbar { display: none; }
.admin-tabs::before, .admin-tabs::after {
  content: ''; position: sticky; z-index: 1; flex: 0 0 36px; pointer-events: none;
  align-self: stretch; transition: opacity .15s ease;
}
/* The strip's 4px gap follows the pseudo too, so the negative margin swallows both. */
.admin-tabs::before { left: -16px; margin-right: calc(-36px - 4px); background: linear-gradient(to right, var(--glass) 12px, rgba(22, 24, 27, 0)); }
.admin-tabs::after  { right: -16px; margin-left: calc(-36px - 4px); background: linear-gradient(to left, var(--glass) 12px, rgba(22, 24, 27, 0)); }
.admin-tabs.at-start::before, .admin-tabs.at-end::after { opacity: 0; }

/* ---- laptop / tablet: header row + toolbar row -----------------------------
   The IDE toolbar plus title, board and account cluster only fit one row
   on a wide monitor; below that the toolbar takes a row of its own under
   a brand / context / account header, the way desktop IDEs lay it out. */
@media (max-width: 1600px) {
  .topbar:not(.topbar-compact) {
    display: grid; align-items: center; gap: 8px 12px; padding: 8px 14px;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "brand context utility" "actions actions actions";
  }
  .topbar:not(.topbar-compact) .topbar-brand   { grid-area: brand; }
  .topbar:not(.topbar-compact) .topbar-context { grid-area: context; }
  .topbar:not(.topbar-compact) .topbar-utility { grid-area: utility; margin-left: 0; justify-self: end; }
  .topbar:not(.topbar-compact) .actions        { grid-area: actions; flex-wrap: wrap; }
  .topbar:not(.topbar-compact) { padding-top: 8px; padding-bottom: 8px; }
  /* Everything in the context row must be able to give up width, or the
     utility cluster gets pushed over the top of it. */
  .topbar-context { flex-wrap: nowrap; }
  .topbar-context .title-wrap { flex: 1 1 160px; min-width: 0; max-width: 640px; }
  .topbar-context .title-input { flex: 1 1 160px; min-width: 0; width: 100%; }
  .topbar-context > select, .topbar-context .robot-picker { flex: 0 1 auto; min-width: 0; }
  .topbar-context #boardSelect, .topbar-context #robotSelect { flex: 0 1 auto; min-width: 120px; max-width: 240px; }
}
@media (max-width: 960px) {
  /* Logo + badge identify the product on their own; the words go so the
     title and board keep a usable width beside the account cluster. */
  .topbar:not(.topbar-compact) .brand-text { display: none; }
  .topbar:not(.topbar-compact) .ros-badge { margin-left: 0; }
}

/* ---- phone: three compact rows, swipeable toolbar ------------------------ */
@media (max-width: 760px) {
  .topbar { padding: 8px 10px; gap: 8px 10px; }
  .brand { font-size: .92rem; }
  .brand-logo { height: 26px; width: 26px; }
  .actions { gap: 6px; }
  .actions button, .topbar-utility .who, .tasks-wrap button { padding: 7px 10px; font-size: .82rem; }
  .actions .btn-icon { width: 15px; height: 15px; }
  /* Names and secondary labels go; icons (with their title tooltips and
     aria-labels) stay, so the utility cluster fits beside the brand. */
  .topbar .who-name, .tasks-wrap .btn-label { display: none; }
  .topbar-utility .who { padding: 7px 8px; }
  .topbar-utility .who .chevron { display: none; }
  .topbar-utility { gap: 6px; }

  .topbar:not(.topbar-compact) {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "brand utility" "context context" "actions actions";
  }
  .topbar:not(.topbar-compact) .topbar-brand { min-width: 0; overflow: hidden; }
  /* Title + board share the second row, the title taking whatever is left. */
  .topbar-context { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; }
  .topbar-context .title-wrap { min-width: 0; }
  .topbar-context .title-input { width: 100%; min-width: 0; flex: 1 1 auto; }
  #boardSelect { max-width: 46vw; min-width: 0; }
  .robot-picker { min-width: 0; max-width: 50vw; gap: 6px; }
  .robot-picker-label { display: none; }
  #robotSelect { min-width: 0; width: 100%; }
  /* iOS zooms into any focused field under 16px; keep the topbar fields at 16. */
  #boardSelect, #robotSelect, .title-input { font-size: 16px; }

  /* The toolbar: one strip that swipes sideways instead of wrapping into a
     wall of buttons. Bled to the bar's edges so it scrolls edge to edge. */
  .topbar:not(.topbar-compact) .actions {
    flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; position: relative;
    margin: 0 -10px -8px; padding: 2px 10px 8px;
    scroll-snap-type: x proximity; scroll-padding-inline: 10px; scrollbar-width: none;
    -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
  }
  .topbar:not(.topbar-compact) .actions::-webkit-scrollbar { display: none; }
  .topbar:not(.topbar-compact) .actions > * { flex-shrink: 0; scroll-snap-align: start; }
  .topbar:not(.topbar-compact) .actions::before,
  .topbar:not(.topbar-compact) .actions::after {
    content: ''; position: sticky; z-index: 1; flex: 0 0 44px; pointer-events: none;
    align-self: stretch; transition: opacity .15s ease;
  }
  /* Sticky offsets are measured from the strip's content box, so the
     pseudo-elements sit 10px (the strip's padding) past it to reach the
     bar's real edge; the gradient is a pure fade with no solid part, so it
     dims the clipped button instead of looking like a bar drawn over it. */
  .topbar:not(.topbar-compact) .actions::before { left: -10px; margin-right: calc(-44px - 6px); background: linear-gradient(to right, var(--glass) 10px, rgba(22, 24, 27, 0)); }
  .topbar:not(.topbar-compact) .actions::after  { right: -10px; margin-left: calc(-44px - 6px); background: linear-gradient(to left, var(--glass) 10px, rgba(22, 24, 27, 0)); }
  .topbar:not(.topbar-compact) .actions.at-start::before,
  .topbar:not(.topbar-compact) .actions.at-end::after { opacity: 0; }
  .tb-sep { margin: 0; height: 20px; }
  /* Icon-only toolbar on phones: captions are kept in the DOM for screen
     readers (and every button still has its title tooltip) but take no
     space, so the whole toolbar usually fits without swiping at all. */
  .topbar:not(.topbar-compact) .actions .btn-label:not(.estop .btn-label) {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .topbar:not(.topbar-compact) .actions button { padding: 8px 10px; min-width: 40px; justify-content: center; }
  .topbar:not(.topbar-compact) .actions .btn-icon { width: 18px; height: 18px; }
  .topbar:not(.topbar-compact) .actions .burn-label { gap: 0; }
  /* The emergency stop keeps its word - a safety control must read as one. */
  .estop { padding: 7px 12px !important; }
  .estop .btn-label { font-size: .74rem; }
  /* Primary actions stay put: the group sticks to the strip's right edge
     and the other buttons slide underneath it, so Upload / Run / STOP are
     reachable without a swipe. Its own left shadow is the "more this way"
     hint, so the strip's right fade is dropped when a pinned group exists. */
  .topbar:not(.topbar-compact) .actions .tb-primary {
    position: sticky; right: -10px; z-index: 2; gap: 6px;
    margin-left: auto; padding-left: 8px; background: var(--glass);
    box-shadow: -12px 0 12px -6px var(--glass);
  }
  .topbar:not(.topbar-compact) .actions:has(.tb-primary)::after { display: none; }

  /* Dropdowns inside the strip would be clipped by its overflow, and a
     350px panel hanging off a 40px bell overflows a phone anyway: every
     menu becomes a sheet pinned just under the bar, full width. */
  .topbar .dropdown, .topbar .menu-wrap .dropdown, .topbar .bell-panel {
    position: fixed; top: calc(var(--topbar-h, 60px) + 6px); left: 10px; right: 10px;
    width: auto; min-width: 0; margin-top: 0;
    max-height: calc(100dvh - var(--topbar-h, 60px) - 24px); overflow-y: auto;
  }
  .topbar .dropdown .dropdown-item { padding: 13px 16px; font-size: .92rem; }

  /* Compact pages: the brand shrinks, button labels fold into icons. */
  .topbar-compact .actions button { padding: 7px 10px; }
}
@media (max-width: 560px) {
  .topbar-compact .actions .btn-label, .topbar-compact .topbar-utility .btn-label { display: none; }
  .topbar-compact .actions button, .topbar-compact .topbar-utility button { padding: 7px 9px; }
}
@media (max-width: 480px) {
  .brand-text { display: none; }
  .ros-badge { margin-left: 0; font-size: .64rem; }
}

/* ---- phone workbench: Blocks | Code | Console ----------------------------
   shell.js appends <nav class="view-switch"> to .ide-shell and keeps
   data-view on the shell while the phone media query matches. Desktop never
   gets data-view, so none of these rules apply there. */
.view-switch { display: none; }
@media (max-width: 760px) {
  .ide-shell[data-view] .view-switch {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 6px; flex-shrink: 0;
    background: var(--glass); border-top: 1px solid #2A2E33;
    padding: 6px 10px calc(6px + env(safe-area-inset-bottom, 0px));
  }
  .view-switch button {
    position: relative; border: 1px solid #3A3F45; background: transparent; color: #9AA2AA;
    border-radius: 8px; padding: 9px 6px; min-height: 42px;
    font: 600 .74rem var(--font-display); letter-spacing: .5px; text-transform: uppercase;
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  }
  .view-switch button .btn-icon { width: 15px; height: 15px; }
  .view-switch button.active { background: var(--amber); border-color: var(--amber); color: #1D2126; }
  .ros-topbar ~ .view-switch button.active { background: var(--teal); border-color: var(--teal); color: #fff; }
  .view-switch button.has-new::after {
    content: ''; position: absolute; top: 6px; right: 8px; width: 7px; height: 7px;
    border-radius: 50%; background: var(--amber); box-shadow: 0 0 0 2px var(--glass);
  }
  .view-switch button.active.has-new::after { display: none; }

  .ide-shell[data-view] .workspace-row { flex-direction: column; }
  .ide-shell[data-view] .code-panel { height: auto; flex: 1 1 auto; min-height: 0; border-top: none; }
  .ide-shell[data-view="blocks"] .code-panel { display: none; }
  .ide-shell[data-view="code"] #blocklyDiv,
  .ide-shell[data-view="console"] #blocklyDiv { display: none; }
  .ide-shell[data-view="code"] .console-head,
  .ide-shell[data-view="code"] #consoleView,
  .ide-shell[data-view="code"] #diagView,
  .ide-shell[data-view="code"] #termView,
  .ide-shell[data-view="code"] #monitorView,
  .ide-shell[data-view="code"] #monitorSend { display: none; }
  .ide-shell[data-view="code"] #codeView { flex: 1; font-size: .8rem; }
  .ide-shell[data-view="console"] .code-head,
  .ide-shell[data-view="console"] #codeView { display: none; }
  .ide-shell[data-view="console"] .console-head { border-top: none; }
  .ide-shell[data-view="console"] #consoleView,
  .ide-shell[data-view="console"] #monitorView { font-size: .8rem; }
  /* Toasts and the floating task brief sit above the switcher, not on it. */
  .ide-shell[data-view] ~ .toast-host, .ide-shell[data-view] .toast-host,
  .ide-shell[data-view] ~ .task-panel:not(.is-floating) { bottom: 72px; }
}

/* =====================================================================
   Admin > Notifications
   =====================================================================
   A matrix of notification types against delivery channels, plus the
   template editor that opens over it.

   The switches are plain checkboxes with a styled sibling rather than a
   component: they live inside table cells that are rebuilt wholesale on
   every filter keystroke, and a checkbox survives innerHTML replacement
   with its label association intact.
   =================================================================== */

.nt-legend { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.nt-legend-item {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 5px 11px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface2); font-size: .8rem;
}
.nt-legend-item strong { color: var(--ink); }
.nt-legend-item span { color: var(--ink2); }
.nt-legend-item.is-off { opacity: .5; }

.nt-table td { vertical-align: middle; }
.nt-table .nt-col { width: 84px; text-align: center; }
.nt-table .nt-aud { color: var(--ink2); font-size: .85rem; max-width: 190px; }
.nt-desc { color: var(--ink2); font-size: .82rem; line-height: 1.5; margin: 2px 0 4px; max-width: 42ch; }
.nt-key { font-size: .72rem; opacity: .75; }
.nt-actions { white-space: nowrap; text-align: right; }
.nt-actions .table-btn { margin-left: 6px; }

/* Group header row. A plain <tr> rather than a <tbody> per group, so the
   single tbody can still be replaced in one assignment. */
.nt-table tr.nt-group td {
  background: var(--surface2); font-family: var(--font-display);
  font-size: .76rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink2); padding: 8px 12px;
}

.nt-na { color: var(--ink2); opacity: .6; font-size: .8rem; }

/* ---- the switch ---- */
.nt-switch { display: inline-block; position: relative; cursor: pointer; line-height: 0; }
.nt-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.nt-switch span {
  display: block; width: 38px; height: 22px; border-radius: 999px;
  background: var(--line); transition: background .15s ease;
}
.nt-switch span::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 16px; height: 16px; border-radius: 50%; background: #fff;
  box-shadow: 0 1px 3px rgba(20, 22, 25, .28); transition: transform .15s ease;
}
.nt-switch input:checked + span { background: var(--teal); }
.nt-switch input:checked + span::after { transform: translateX(16px); }
.nt-switch input:focus-visible + span { outline: 2px solid var(--teal); outline-offset: 2px; }
.nt-switch input:disabled { cursor: not-allowed; }
.nt-switch.is-off { opacity: .4; cursor: not-allowed; }
.nt-switch-sm span { width: 32px; height: 19px; }
.nt-switch-sm span::after { width: 13px; height: 13px; }
.nt-switch-sm input:checked + span::after { transform: translateX(13px); }

/* Marks a type whose wording an admin has changed. */
.nt-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--amber); vertical-align: middle; margin-left: 2px;
}

/* ---- the editor ---- */
/* [open] matters: a bare `display: flex` here would override the user
   agent's `display: none` for a closed <dialog>, leaving the editor on
   screen permanently. */
dialog.nt-dialog { width: min(1080px, 95vw); max-height: 92vh; }
dialog.nt-dialog[open] { display: flex; flex-direction: column; }
.nt-editor {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 20px; flex: 1; min-height: 0; overflow: hidden;
}
.nt-editor-fields { overflow: auto; padding-right: 4px; min-height: 0; }
.nt-editor-fields label { display: block; margin-bottom: 12px; font-size: .85rem; font-weight: 600; }
.nt-editor-fields input[type="text"], .nt-editor-fields textarea {
  width: 100%; margin-top: 5px; font-weight: 400;
}
/* .kb-textarea is sized for writing a Knowledge Base article - 260px of
   monospace. These slots are one or two sentences of prose, and at that
   height three of them push the variable palette off the screen. */
.nt-editor-fields textarea.kb-textarea {
  min-height: 74px; font: .88rem/1.6 var(--font-body); background: var(--surface);
}
/* The shipped wording sits in the placeholder, so it has to read as a
   real sentence rather than as a hint - but still not as typed input. */
.nt-editor-fields ::placeholder { color: var(--ink2); opacity: .7; font-style: italic; }

.nt-vars { margin-top: 6px; border-top: 1px solid var(--line); padding-top: 10px; }
.nt-vars summary { cursor: pointer; font-size: .85rem; font-weight: 600; }
.nt-var-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.nt-var {
  font-family: var(--font-mono); font-size: .75rem;
  padding: 4px 8px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--surface2); color: var(--ink); cursor: pointer;
}
.nt-var:hover { border-color: var(--teal); color: var(--teal); }

.nt-editor-preview {
  display: flex; flex-direction: column; min-height: 0;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface2);
}
.nt-preview-head {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 14px; border-bottom: 1px solid var(--line);
}
.nt-preview-head .hint { margin: 0; font-size: .78rem; }
.nt-preview-body { flex: 1; overflow: auto; min-height: 0; padding: 12px; }
.nt-preview-subject {
  display: flex; gap: 8px; align-items: baseline;
  font-size: .88rem; margin-bottom: 10px; color: var(--ink);
}
.nt-preview-subject span {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink2); font-weight: 700;
}
/* The email is a whole document with its own body background; an iframe
   keeps its styles out of the admin page. */
.nt-preview-frame {
  width: 100%; height: 100%; min-height: 420px;
  border: 1px solid var(--line); border-radius: 8px; background: #fff;
}
.nt-preview-bell {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; padding: 12px 14px;
}
.nt-preview-bell strong { display: block; margin-bottom: 4px; }
.nt-preview-bell p { margin: 0; color: var(--ink2); font-size: .88rem; line-height: 1.55; }

.nt-dlg-actions { flex-shrink: 0; align-items: center; }
.nt-dlg-spacer { flex: 1; }

@media (max-width: 900px) {
  .nt-editor { grid-template-columns: minmax(0, 1fr); overflow: auto; }
  .nt-editor-preview { min-height: 340px; }
  .nt-table .nt-aud { display: none; }
}

/* Renewal buttons carry a second line - "Tap to call", and what the other
   two actually do. The anchor is already a flex row (icon + label), so the
   label becomes a column inside it rather than the row becoming a wrap. */
.renew-actions .renew-btn-text {
  display: flex; flex-direction: column; align-items: flex-start;
  line-height: 1.25; text-align: left;
}
.renew-actions .renew-btn-sub {
  font-size: .74rem; font-weight: 400; opacity: .82; margin-top: 1px;
}

/* =====================================================================
   Admin > Organisation
   =====================================================================
   The business's own details. A plain form, grouped into fieldsets, plus
   a preview of the invoice letterhead those fields produce.
   =================================================================== */

.org-groups { display: flex; flex-direction: column; gap: 18px; margin-bottom: 18px; }

.org-group {
  border: 1px solid var(--line); border-radius: 10px;
  padding: 14px 16px 16px; margin: 0;
}
.org-group legend {
  font-family: var(--font-display); font-size: .74rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; color: var(--ink2);
  padding: 0 6px;
}

.org-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; }
.org-field { display: flex; flex-direction: column; font-size: .85rem; font-weight: 600; }
/* A textarea gets the full width: an address or payment terms read badly
   in a 260px column. */
.org-field.is-wide { grid-column: 1 / -1; }

.org-label { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.org-req { font-style: normal; font-size: .68rem; font-weight: 500; color: var(--ink2); opacity: .8; }

.org-field input, .org-field textarea { width: 100%; font-weight: 400; }
.org-field textarea.org-textarea {
  min-height: 72px; font: .88rem/1.6 var(--font-body); background: var(--surface);
}
/* An edited-but-unsaved field is worth marking - this form has one Save
   for everything, so "what have I changed" is otherwise invisible. */
.org-field input.is-dirty, .org-field textarea.is-dirty {
  border-color: var(--amber); background: var(--amber-soft);
}
.org-help { font-weight: 400; font-size: .75rem; color: var(--ink2); margin-top: 4px; line-height: 1.45; }

.org-reset {
  margin-left: auto; padding: 0; border: none; background: none;
  font: inherit; font-size: .7rem; font-weight: 500;
  color: var(--teal); cursor: pointer; text-decoration: underline;
}

.org-actions { align-items: center; }
.org-actions .primary { flex: 0 0 auto; }

/* ---- the letterhead preview ---- */
.org-letterhead-paper {
  background: #fff; border: 1px solid var(--line); border-radius: 8px;
  padding: 18px 20px; font-family: Helvetica, Arial, sans-serif;
  font-size: .8rem; line-height: 1.55; color: #1D2126; max-width: 560px;
}
.org-letterhead-paper strong { font-size: 1rem; }
.org-letterhead-paper hr { border: none; border-top: 1px solid #c8c8c8; margin: 12px 0; }
.org-letterhead-body { font-weight: 700; font-size: .85rem; }
.org-letterhead-footer {
  margin-top: 12px; font-size: .7rem; color: #6e6e6e; white-space: pre-wrap;
}

@media (max-width: 720px) {
  .org-fields { grid-template-columns: minmax(0, 1fr); }
}
