/* ==========================================================================
   Component library
   ========================================================================== */

/* `hidden` has to actually hide. The UA stylesheet sets `display: none` for
   it at the lowest possible specificity, so any class that declares a display
   — `.field` is flex, `.row` is flex, `.card` is block — silently wins and the
   "hidden" element stays on screen. Every conditionally-shown block in a form
   depends on this one line; `.today-strip[hidden]` in css/layout.css was a
   one-off patch for the same problem. */
[hidden] { display: none !important; }

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: var(--radius-sm);
  font-size: .82rem;
  font-weight: 600;
  white-space: nowrap;
  transition: all .12s var(--ease);
}
.btn:hover { background: var(--surface-2); border-color: var(--border-strong); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .5; pointer-events: none; }

.btn-primary {
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: #fff;
  box-shadow: var(--shadow-xs);
}
.btn-primary:hover { background: var(--brand-700); border-color: var(--brand-700); }

.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover { background: #b91c1c; border-color: #b91c1c; }

.btn-ghost { background: transparent; border-color: transparent; color: var(--text-2); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); border-color: transparent; }

.btn-sm { height: 28px; padding: 0 10px; font-size: .765rem; gap: 5px; }
.btn-lg { height: 40px; padding: 0 20px; font-size: .9rem; }
.btn-block { width: 100%; }
.btn-icon { width: 34px; padding: 0; }
.btn-icon.btn-sm { width: 28px; }

/* ------------------------------------------------------------- cards -- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
}

.card-pad { padding: 18px; }

.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}
.card-head h3 { font-size: .95rem; }
.card-head .sub { font-size: .765rem; color: var(--text-3); margin-top: 1px; }

.card-body { padding: 18px; }
.card-body.tight { padding: 12px; }
.card-foot {
  padding: 12px 18px;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 0 0 var(--radius) var(--radius);
}

.card-link { cursor: pointer; transition: all .14s var(--ease); }
.card-link:hover { border-color: var(--border-strong); box-shadow: var(--shadow); transform: translateY(-1px); }

/* -------------------------------------------------------- stat tiles -- */

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 15px 16px;
  box-shadow: var(--shadow-xs);
  position: relative;
  overflow: hidden;
}
.stat .stat-label {
  font-size: .7rem; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-3);
}
.stat .stat-value {
  font-size: 1.75rem; font-weight: 720; letter-spacing: -.03em;
  margin-top: 5px; line-height: 1.1; font-variant-numeric: tabular-nums;
}
.stat .stat-foot { font-size: .755rem; color: var(--text-2); margin-top: 4px; }
.stat .stat-ico {
  position: absolute; top: 13px; right: 14px;
  font-size: 1.1rem; opacity: .5;
}
.stat.accent::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--brand-500);
}

/* ------------------------------------------------------------ badges -- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: .69rem;
  font-weight: 700;
  letter-spacing: .015em;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  background: var(--surface-3);
  color: var(--text-2);
  white-space: nowrap;
}
.badge.ok     { background: var(--ok-bg);     color: var(--ok); }
.badge.warn   { background: var(--warn-bg);   color: var(--warn); }
.badge.danger { background: var(--danger-bg); color: var(--danger); }
.badge.info   { background: var(--info-bg);   color: var(--info); }
.badge.brand  { background: var(--brand-50);  color: var(--brand-600); }
[data-theme="dark"] .badge.brand { color: var(--brand-300); }
.badge.solid  { background: var(--brand-600); color: #fff; }

/* Release-stage marker that sits beside the wordmark. Deliberately a literal
   blue rather than --brand-*: the accent is user-configurable (indigo, teal,
   rose, amber), and how finished the app is shouldn't change colour with it.
   White on #1d4ed8 measures 6.7:1; the dark-theme #2563eb measures 5.2:1 on
   white text and 3.3:1 against --surface, so the pill itself stays visible. */
.beta-tag {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  padding: 2px 7px;
  border-radius: var(--radius-full);
  background: #1d4ed8;
  color: #fff;
  font-size: .58rem;
  font-weight: 800;
  letter-spacing: .09em;
  line-height: 1.6;
  text-transform: uppercase;
  white-space: nowrap;
}
[data-theme="dark"] .beta-tag { background: #2563eb; }

.dot-badge { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }

/* U15 — subject icon: a second, non-color channel next to a class's name */
.subject-icon {
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  flex-shrink: 0;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1;
}

/* ----------------------------------------------------------- avatars -- */

.avatar {
  width: 34px; height: 34px;
  border-radius: 50%;
  display: grid; place-items: center;
  color: #fff;
  font-weight: 700;
  font-size: .78rem;
  flex-shrink: 0;
  letter-spacing: -.02em;
}
.avatar.sm { width: 26px; height: 26px; font-size: .66rem; }
.avatar.lg { width: 46px; height: 46px; font-size: 1rem; }
.avatar.xl { width: 64px; height: 64px; font-size: 1.35rem; }

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

.field { display: flex; flex-direction: column; gap: 5px; }
.field > label {
  font-size: .765rem;
  font-weight: 650;
  color: var(--text-2);
}
.field .hint { font-size: .715rem; color: var(--text-3); }

.input, .select, .textarea {
  width: 100%;
  height: 36px;
  padding: 0 11px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: .845rem;
  transition: border-color .12s var(--ease), box-shadow .12s var(--ease);
}
.textarea { height: auto; min-height: 76px; padding: 9px 11px; resize: vertical; line-height: 1.5; font-family: inherit; }
.select { appearance: none; padding-right: 30px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M3 4.5L6 8l3-3.5' fill='none' stroke='%238791a5' stroke-width='1.5' stroke-linecap='round'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center; }

.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--brand-500);
  box-shadow: var(--ring);
}
.input::placeholder, .textarea::placeholder { color: var(--text-3); }

.input-sm { height: 30px; font-size: .79rem; padding: 0 9px; }

/* iOS Safari zooms the whole page whenever a focused control's text is under
   16px, and it does not zoom back out on blur — so every tap into a field
   left the app zoomed in. The viewport meta deliberately allows user scaling
   (removing that would be a worse accessibility failure), so the fix is to
   meet the threshold on touch devices. Desktop keeps the denser scale. */
@media (pointer: coarse) {
  .input, .select, .textarea { font-size: 16px; }
  .input-sm { font-size: 16px; height: 36px; }
}

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 13px; }
.form-grid .full { grid-column: 1 / -1; }
@media (max-width: 560px) { .form-grid { grid-template-columns: 1fr; } }

/* checkbox / switch */

.check {
  appearance: none;
  width: 17px; height: 17px;
  border: 1.5px solid var(--border-strong);
  border-radius: 5px;
  background: var(--surface);
  display: grid; place-items: center;
  flex-shrink: 0;
  transition: all .12s var(--ease);
  cursor: pointer;
}
.check:checked { background: var(--brand-600); border-color: var(--brand-600); animation: checkPop .22s var(--ease); }

/* Marking an assignment done is the single most-used control in the app and
   it was a 17px box — well under the 44px touch minimum, and not covered by
   the coarse-pointer block below, which only enlarged icon buttons and chips.
   The box stays visually the same size; the *target* grows around it. */
@media (pointer: coarse) {
  .check {
    width: 24px; height: 24px;
    position: relative;
  }
  .check::before {
    content: "";
    position: absolute;
    inset: -10px;                 /* 44px total hit area */
  }
  .check:checked::after { width: 6px; height: 11px; }
}
.check:checked::after {
  content: "";
  width: 4.5px; height: 8px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(43deg) translate(-.5px, -1px);
}
.check.round { border-radius: 50%; }

/* U18 — a small, restrained confirmation on check; killed globally under
   prefers-reduced-motion by the animation-duration override in theme.css. */
@keyframes checkPop {
  0%   { transform: scale(.82); }
  55%  { transform: scale(1.08); }
  100% { transform: scale(1); }
}

.switch { position: relative; width: 40px; height: 23px; flex-shrink: 0; display: inline-block; }
.switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.switch .track {
  position: absolute; inset: 0;
  background: var(--border-strong);
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: background-color .18s var(--ease);
}
.switch .track::before {
  content: "";
  position: absolute;
  width: 17px; height: 17px;
  left: 3px; top: 3px;
  background: #fff;
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
  transition: transform .18s var(--ease);
}
.switch input:checked + .track { background: var(--brand-600); }
.switch input:checked + .track::before { transform: translateX(17px); }
.switch input:focus-visible + .track { box-shadow: var(--ring); }

/* segmented control */

.segmented {
  display: inline-flex;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 3px;
  gap: 2px;
}
.segmented button {
  border: none;
  background: transparent;
  padding: 5px 12px;
  border-radius: 6px;
  font-size: .77rem;
  font-weight: 600;
  color: var(--text-2);
  transition: all .12s var(--ease);
}
.segmented button:hover { color: var(--text); }
.segmented button.active {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-xs);
}

/* chips */

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: var(--radius-full);
  font-size: .765rem;
  font-weight: 600;
  color: var(--text-2);
  transition: all .12s var(--ease);
}
.chip:hover { border-color: var(--border-strong); color: var(--text); }
.chip.active { background: var(--brand-600); border-color: var(--brand-600); color: #fff; }

/* color swatch picker */

.swatches { display: flex; flex-wrap: wrap; gap: 7px; }
.swatch {
  width: 26px; height: 26px;
  border-radius: 8px;
  border: 2px solid transparent;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.08);
  transition: transform .12s var(--ease);
}
.swatch:hover { transform: scale(1.12); }
.swatch.active { border-color: var(--text); }

/* ------------------------------------------------------------ tables -- */

.table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.table th {
  text-align: left;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  /* U32 — stays visible while scrolling a long table; .page is the
     scrolling ancestor these stick relative to. */
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}
.table td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr:hover { background: var(--surface-2); }
.table .right, .table th.right { text-align: right; }

/* `overflow-x: auto` computes overflow-y to auto as well, which makes this
   wrapper the nearest scrollport — so `position: sticky` on the header row
   was anchored to a container that never scrolls vertically, and the headers
   scrolled away with the page. Capping the height makes the wrapper the real
   vertical scroller, which is what the sticky header was written for; short
   tables are unaffected. */
.table-wrap { overflow: auto; max-height: 70vh; }
.table-wrap:focus-visible { outline: 2px solid var(--brand-600); outline-offset: 2px; }
.scroll-x:focus-visible, .week-wrap:focus-visible, .board:focus-visible, .tl:focus-visible {
  outline: 2px solid var(--brand-600); outline-offset: 2px;
}
/* Printing must not clip a long table to one screenful. */
@media print {
  .table-wrap { overflow: visible; max-height: none; }
}

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

.list { display: flex; flex-direction: column; }
.list-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  transition: background-color .1s var(--ease);
}
.list-item:last-child { border-bottom: none; }
.list-item:hover { background: var(--surface-2); }
.list-item .title { font-weight: 600; font-size: .855rem; }
.list-item .meta { font-size: .745rem; color: var(--text-3); margin-top: 1px; }

/* F058 — a feed post wraps its row + an optional comment thread beneath it */
.feed-item:last-child .list-item { border-bottom: none; }
.feed-item:not(:last-child) { border-bottom: 1px solid var(--border); }
.feed-item .list-item { border-bottom: none; }
.feed-comments { padding: 4px 14px 14px 14px; background: var(--surface-2); }
.feed-comment { padding: 6px 0; border-bottom: 1px solid var(--border); }
.feed-comment:last-child { border-bottom: none; }

/* --------------------------------------------------------- progress -- */

.bar {
  height: 7px;
  background: var(--surface-3);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.bar > i {
  display: block;
  height: 100%;
  background: var(--brand-500);
  border-radius: var(--radius-full);
  transition: width .4s var(--ease);
}
.bar.thin { height: 4px; }
.bar.thick { height: 10px; }

.ring-wrap { position: relative; display: inline-grid; place-items: center; }
.ring-wrap .ring-label {
  position: absolute;
  font-weight: 700;
  font-size: .9rem;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ modal -- */

.modal-root {
  position: fixed; inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(8, 11, 20, .55);
  backdrop-filter: blur(3px);
  animation: fade .16s var(--ease);
}

.modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 540px;
  max-height: calc(100vh - 40px);
  display: flex;
  flex-direction: column;
  animation: pop .18s var(--ease);
}
.modal.wide { max-width: 780px; }
.modal.narrow { max-width: 400px; }

.modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.modal-body { padding: 20px; overflow-y: auto; flex: 1; min-height: 0; }
.modal-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 14px 20px;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}
.modal-foot .left { margin-right: auto; }

@keyframes fade { from { opacity: 0; } }
@keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.98); } }

/* --------------------------------------------------- command palette -- */

.palette-root {
  position: fixed; inset: 0;
  z-index: 110;
  padding: 12vh 20px 20px;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  background: rgba(8, 11, 20, .5);
  backdrop-filter: blur(3px);
  animation: fade .14s var(--ease);
}
.palette {
  width: 100%;
  max-width: 580px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: pop .16s var(--ease);
  display: flex;
  flex-direction: column;
  max-height: 62vh;
}
.palette input {
  width: 100%;
  height: 50px;
  padding: 0 18px;
  border: none;
  border-bottom: 1px solid var(--border);
  background: transparent;
  font-size: .95rem;
  outline: none;
}
.palette-list { overflow-y: auto; padding: 6px; }
.palette-group {
  font-size: .65rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
  padding: 8px 12px 4px;
}
.palette-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 8px 12px;
  border: none; background: transparent;
  border-radius: var(--radius-sm);
  text-align: left;
  font-size: .85rem;
  color: var(--text);
}
.palette-item .ico { width: 18px; text-align: center; opacity: .8; }
.palette-item .sub { margin-left: auto; font-size: .72rem; color: var(--text-3); }
.palette-item.sel { background: var(--brand-50); color: var(--brand-700); }
[data-theme="dark"] .palette-item.sel { color: var(--brand-200); }

/* ------------------------------------------------------------ toast -- */

.toast-host {
  position: fixed;
  bottom: 20px; right: 20px;
  z-index: 200;
  display: flex; flex-direction: column-reverse; gap: 9px;
  pointer-events: none;
}

/* On a phone the bottom 55px is the tab bar and the FAB sits just above it,
   so a toast anchored at bottom:20px covered both — including the Undo action
   in a delete toast, which is exactly when you need to reach it. Lift clear of
   them, and clear of the home indicator on a notched device. */
@media (max-width: 860px) {
  .toast-host {
    bottom: calc(124px + env(safe-area-inset-bottom, 0px));
    left: 12px; right: 12px;
    align-items: stretch;
  }
  .toast { width: auto; max-width: none; }
}
.toast {
  display: flex; align-items: flex-start; gap: 10px;
  min-width: 250px; max-width: 380px;
  padding: 11px 14px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--brand-500);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  font-size: .84rem;
  pointer-events: auto;
  animation: slidein .2s var(--ease);
}
.toast.ok     { border-left-color: var(--ok); }
.toast.warn   { border-left-color: var(--warn); }
.toast.danger { border-left-color: var(--danger); }
.toast.out { animation: slideout .18s var(--ease) forwards; }
.toast .t-title { font-weight: 650; }
.toast .t-msg { color: var(--text-2); font-size: .79rem; margin-top: 1px; }

@keyframes slidein { from { opacity: 0; transform: translateX(24px); } }
@keyframes slideout { to { opacity: 0; transform: translateX(24px); } }

/* ------------------------------------------------------- empty state -- */

.empty {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  padding: 44px 20px;
  color: var(--text-3);
}
.empty .e-ico { font-size: 2.1rem; opacity: .55; margin-bottom: 10px; }
.empty .e-ico-drawn { opacity: 1; }
.empty .e-ico-drawn svg { width: 48px; height: 48px; color: var(--brand-600); }
.empty .e-title { font-weight: 650; color: var(--text-2); font-size: .93rem; }
.empty .e-sub { font-size: .81rem; margin-top: 4px; max-width: 330px; }
.empty .btn { margin-top: 14px; }

/* --------------------------------------------- U39 project timeline -- */

.tl { display: flex; flex-direction: column; gap: 2px; }
.tl-row {
  display: grid;
  grid-template-columns: 190px 1fr;
  align-items: center;
  gap: 12px;
  padding: 5px 0;
  border-radius: var(--radius-xs);
  cursor: pointer;
}
.tl-row:hover:not(.tl-head) { background: var(--surface-2); }
.tl-head { cursor: default; padding-bottom: 10px; }
.tl-child .tl-label { padding-left: 12px; }

.tl-label {
  font-size: .84rem;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tl-label .tiny { display: block; font-weight: 500; }

.tl-track {
  position: relative;
  height: 22px;
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
}
.tl-head .tl-track { height: 18px; }

.tl-mark {
  position: absolute; top: 0; bottom: -9999px;
  border-left: 1px dashed var(--border);
  font-size: .68rem;
  color: var(--text-3);
  white-space: nowrap;
}
.tl-mark > span { position: absolute; top: 0; left: 3px; }

.tl-today {
  position: absolute; top: 0; bottom: -9999px;
  border-left: 2px solid var(--brand-600);
  z-index: 1;
}

.tl-bar {
  position: absolute; top: 3px; bottom: 3px;
  border-radius: 999px;
  min-width: 6px;
  opacity: .88;
  overflow: hidden;
}
.tl-row:hover .tl-bar { opacity: 1; }
.tl-bar.is-done { opacity: .4; }
.tl-bar.is-overdue { box-shadow: 0 0 0 2px var(--danger); }
/* Lighter wash showing how much of the item's checklist is finished. */
.tl-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  background: rgba(255, 255, 255, .45);
}

@media (max-width: 640px) {
  .tl-row { grid-template-columns: 120px 1fr; gap: 8px; }
  .tl-label { font-size: .78rem; }
}

/* ------------------------------------------------- U14 class covers -- */

.class-cover {
  height: 92px;
  border-radius: var(--radius) var(--radius) 0 0;
  background-color: var(--cover-tint, var(--surface-2));
  background-size: cover;
  background-position: center;
  position: relative;
}
/* A soft tint in the class colour keeps card text legible over any photo. */
.class-cover::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, transparent 45%, rgba(0, 0, 0, .18) 100%);
}
.class-cover-missing { height: 4px; }
.class-cover-missing::after { content: none; }

/* ------------------------------------------------------- add chooser -- */

.add-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
}
.add-choice {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 14px 14px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  text-align: left;
  transition: border-color .12s ease, transform .12s ease, box-shadow .12s ease;
}
.add-choice:hover {
  border-color: var(--brand-600);
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm, 0 2px 10px rgba(0,0,0,.07));
}
.add-choice:focus-visible { outline: 2px solid var(--brand-600); outline-offset: 2px; }
.add-choice-ico { font-size: 1.2rem; color: var(--brand-600); line-height: 1.4; }
.add-choice-label { font-weight: 700; font-size: .92rem; color: var(--text); }
.add-choice-sub { font-size: .76rem; color: var(--text-3); line-height: 1.35; }

/* --------------------------------------------------------- assistant -- */

.chat-thread {
  display: flex; flex-direction: column; gap: 12px;
  max-height: 52vh; overflow-y: auto;
  padding: 4px 2px 16px;
  margin-bottom: 14px;
}
.chat-row { display: flex; }
.chat-row-user { justify-content: flex-end; }
.chat-row-assistant { justify-content: flex-start; }
.chat-bubble {
  max-width: 80%;
  padding: 10px 14px;
  border-radius: var(--radius);
  font-size: .89rem;
  line-height: 1.5;
  white-space: pre-wrap;
}
.chat-bubble-user { background: var(--brand-600); color: #fff; border-bottom-right-radius: 4px; }
.chat-bubble-assistant { background: var(--surface-2); color: var(--text); border-bottom-left-radius: 4px; }
.chat-bubble-assistant :is(p, ul, ol) { margin: 0 0 6px; }
.chat-bubble-assistant :last-child { margin-bottom: 0; }

/* -------------------------------------------------------------- tabs -- */

.tabs {
  display: flex; gap: 2px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}
.tab {
  border: none; background: transparent;
  padding: 9px 14px;
  font-size: .83rem; font-weight: 600;
  color: var(--text-3);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
  transition: color .12s var(--ease);
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--brand-600); border-bottom-color: var(--brand-600); }
[data-theme="dark"] .tab.active { color: var(--brand-300); border-bottom-color: var(--brand-400); }

/* ---------------------------------------------------------- tooltip -- */

[data-tip] { position: relative; }
[data-tip]:hover::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--text);
  color: var(--bg);
  font-size: .7rem;
  font-weight: 600;
  padding: 4px 8px;
  border-radius: 6px;
  white-space: nowrap;
  z-index: 60;
  pointer-events: none;
}
/* The topbar sits at the very top of the page, so a tooltip opening upward
   (the default above) has nowhere to go — it gets clipped by the browser
   chrome. Everything else in the app keeps opening upward as normal. */
.topbar [data-tip]:hover::after {
  bottom: auto;
  top: calc(100% + 6px);
}

/* ---------------------------------------------------------- divider -- */

.divider { height: 1px; background: var(--border); border: none; margin: 14px 0; }
.vdiv { width: 1px; background: var(--border); align-self: stretch; }

/* ---------------------------------------------------- U17 — skeletons -- */
/* The shape of what's coming, not the word "Loading" — a shimmering
   placeholder sized like the real content it stands in for. */

.skel {
  position: relative;
  overflow: hidden;
  background: var(--surface-3);
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}
.skel::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.5), transparent);
  transform: translateX(-100%);
  animation: skelShimmer 1.5s ease-in-out infinite;
}
[data-theme="dark"] .skel::after { background: linear-gradient(90deg, transparent, rgba(255,255,255,.09), transparent); }
@keyframes skelShimmer { to { transform: translateX(100%); } }

.skel-line { height: 11px; border-radius: 5px; }
.skel-line-sm { height: 9px; margin-top: 6px; }
.skel-avatar { width: 34px; height: 34px; border-radius: 50%; }
.skel-chip { width: 52px; height: 18px; border-radius: var(--radius-full); }

/* ------------------------------------------------ U21/U28 — flyout menu -- */

.ctx-menu {
  position: fixed;
  z-index: 70;
  min-width: 180px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  padding: 5px;
  animation: pop .12s var(--ease);
}
.ctx-menu-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 8px 10px;
  border-radius: var(--radius-xs);
  font-size: .85rem;
  color: var(--text);
  text-align: left;
}
.ctx-menu-item:hover, .ctx-menu-item:focus-visible { background: var(--surface-2); }
.ctx-menu-item.danger { color: var(--danger); }
.ctx-menu-item .ico { width: 16px; text-align: center; flex-shrink: 0; }
.ctx-menu-sep { height: 1px; background: var(--border); margin: 5px 2px; }

/* ------------------------------------------------------ U22 — inline pop -- */

.inline-due-pop {
  position: fixed;
  z-index: 70;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  padding: 8px;
  animation: pop .12s var(--ease);
}

/* ---------------------------------------------- U31 — configurable columns -- */

.col-picker-wrap { position: relative; }
.col-picker {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 140px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  animation: pop .12s var(--ease);
}
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--text); }

/* ---------------------------------------------------------- U50 — help -- */

.help-hint {
  display: inline-grid;
  place-items: center;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-2);
  font-size: .64rem;
  font-weight: 700;
  vertical-align: middle;
  flex-shrink: 0;
}
.help-hint:hover, .help-hint:focus-visible { background: var(--brand-100); color: var(--brand-700); }

.help-pop {
  position: fixed;
  z-index: 70;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  padding: 10px 12px;
  font-size: .8rem;
  line-height: 1.45;
  color: var(--text-2);
  animation: pop .12s var(--ease);
}

/* --------------------------------------------------------- field errors -- */
/* Modal forms used to carry `novalidate`, so a failed constraint produced
   nothing at all — no message, no focus move, just a Save button that
   appeared to do nothing. Validation is on now, and this is where the
   message lands. */
.field-error {
  margin-top: 4px;
  color: var(--danger);
  display: flex;
  align-items: flex-start;
  gap: 4px;
}
.field-error::before {
  content: "⚠";
  flex: none;
  line-height: 1.35;
}
/* Not colour alone: the border weight changes too, so the state survives a
   greyscale print and a colour-vision difference. */
.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  border-color: var(--danger);
  border-width: 2px;
  outline-color: var(--danger);
}

/* Elements the app treats as buttons but renders as div/span (calendar days,
   note cards, class cards, assignment titles) are marked focusable by
   UI.makeActivatable. They need the same visible focus ring as a real
   button — a focusable element with no focus style is worse than one that
   was never reachable, because now you can land on it and not know. */
[data-activatable] { cursor: pointer; }
[data-activatable]:focus-visible {
  outline: 2px solid var(--brand-600);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}
