/* --- Design tokens ---
   The block below is the CLAUDE.md contract. Every value in it is spelled
   out in the project's design spec. Do not add to it and do not change it;
   the extension block underneath is where anything new belongs. */

:root {
  --void:     #121A13;
  --surface:  #202B21;
  --raised:   #2C3A2D;
  --border:   #3A4C3B;

  --forest:   #14532D;
  --pine:     #1E7A45;
  --fern:     #3FA35F;
  --sage:     #6FBF73;

  --olive:    #7CB518;
  --lime:     #A3E635;
  --electric: #CCFF33;
  --wash:     #E4FBA8;

  --text:     #ECF3E9;
  --text-2:   #A6B5A2;
  --text-3:   #6E7D6B;
  --danger:   #FF6B4A;

  --r-control: 8px;
  --r-card: 12px;
  --r-pill: 999px;

  --font-head: "Space Grotesk", system-ui, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, sans-serif;
  --hairline: 0.5px solid var(--border);
}

/* --- Token extensions ---
   The scales the spec leaves open. New rules use these; old rules keep
   their literals. Do not sweep the file converting one to the other --
   every literal here was hand-picked, so a mechanical swap moves pixels
   on pages nobody meant to touch. */

:root {
  /* Spacing, 4px base. */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;

  /* Type. Absolute, never em: .badge once sat at 0.8em inside .meta's
     0.85rem and landed on 10.9px. A scale that compounds is not a scale. */
  --t-3xs: 0.75rem;
  --t-2xs: 0.8rem;
  --t-xs:  0.85rem;
  --t-sm:  0.9rem;
  --t-base: 1rem;
  --t-md:  1.15rem;
  --t-lg:  1.3rem;
  --t-xl:  1.6rem;
  --t-2xl: 2.25rem;
  --t-3xl: 3rem;

  /* Under 14px the spec asks for a touch more tracking. */
  --track-small: 0.01em;
  --track-display: -0.02em;

  /* Layers. The mention menu must outrank the sticky top bar: it is
     positioned in document coordinates and can overlap it. */
  --z-raised: 1;
  --z-sticky: 10;
  --z-menu:   100;
  --z-toast:  300;

  /* Motion. Nothing here is long enough to wait on. */
  --dur-1: 90ms;
  --dur-2: 150ms;
  --dur-3: 240ms;
  --dur-4: 400ms;
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);

  /* Shell. */
  --page: 960px;
  --page-pad: 1rem;
}

/* Breakpoints cannot be custom properties -- a media query does not
   resolve var() -- and there is no build step to give us @custom-media.
   So the three widths live here as a contract and are written literally:
     60rem  the column stops needing the page to breathe
     40rem  the long-standing break: grids collapse, the top bar stacks
     30rem  small phones: padding and headings step down
   (pointer: coarse) is separate. It asks about the finger, not the
   viewport, and the two are not the same question. */

/* Self-hosted (latin subset, variable) so the app works offline. One file
   per family covers every weight we use. */
@font-face {
  font-family: "Space Grotesk";
  src: url("/assets/fonts/space-grotesk-latin-var.woff2") format("woff2");
  font-weight: 300 700;
  font-display: swap;
}

@font-face {
  font-family: "Inter";
  src: url("/assets/fonts/inter-latin-var.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}

* { box-sizing: border-box; }

/* A display rule (.inline-form is a flex box) outranks the browser's
   own [hidden] rule, so state it here. */
[hidden] { display: none !important; }

:focus-visible {
  outline: 1px solid var(--electric);
  outline-offset: 2px;
}

trix-editor{
  min-height:10rem;
}

/* A column down the viewport, so the footer sits under short pages
   rather than halfway up them. .mention-menu is absolutely positioned
   against the body and so is not a flex item: it is unaffected. */
body {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.7;
  color: var(--text);
  background: var(--void);
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* A grain over the whole canvas: enough texture that the flat surfaces
   read as a material rather than as an empty buffer, and far too faint
   to be seen as a pattern. Not a gradient and not a shadow, so the
   spec's ban on both stands. position: fixed keeps it out of the flex
   flow above, so it never becomes a flex item of its own. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.025;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Shares its column with .topbar-inner, through --page. */
main {
  max-width: var(--page);
  margin: 0 auto;
  padding: 2rem var(--page-pad) 4rem;
  /* Takes the room the top bar and the footer leave, and its own width
     stays the column: flex would otherwise stretch it edge to edge. */
  flex: 1 0 auto;
  width: 100%;
}

/* Login carries no top bar, so it sets its own narrow column. */
main:has(form[action="/login"]) {
  max-width: 360px;
  margin: 80px auto;
  padding: 0 1rem;
  /* The body is a flex column now, and this one keeps its own margins
     rather than being stretched to fill the page. */
  flex: none;
}

.login-head { margin-bottom: var(--s-5); }

/* The one hero on the site, and so the one place weight 700 belongs. */
.login-mark {
  margin: 0;
  font-size: var(--t-2xl);
  color: var(--text);
}

.login-head h1 {
  margin: 0;
  font-size: var(--t-md);
  font-weight: 400;
  letter-spacing: var(--track-small);
  color: var(--text-3);
}

/* The only action on the page, so it takes the whole width. */
.login-go { width: 100%; }

h1, h2 {
  font-family: var(--font-head);
  font-weight: 500;
}

h1 {
  margin: 0 0 1.5rem;
  font-size: 1.6rem;
  letter-spacing: -0.02em;
}

h2 {
  margin: 2rem 0 0.75rem;
  font-size: 1.15rem;
}

a {
  color: var(--text);
  text-decoration: none;
  transition: color var(--dur-2) var(--ease-out);
}
a:hover { text-decoration: underline; }

.row-title:hover { color: var(--sage); }

/* IDs, counts and clock values line up column-wise. */
.meta, .totals, .timer-badge, time, input[type="date"] {
  font-variant-numeric: tabular-nums;
}

.flash {
  padding: 0.6rem 0.9rem;
  border: var(--hairline);
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--text-2);
}

.flash.error {
  border-color: var(--danger);
  color: var(--danger);
}

.flash.ok {
  border-color: var(--pine);
  color: var(--fern);
}

/* --- Top bar --- */

/* Stays put, so the running timer and the unread count are always in
   reach. --surface is opaque, so nothing shows through. */
.topbar {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: var(--surface);
  border-bottom: var(--hairline);
  flex: none;
}

/* Three slots: the brand, the nav, and the user's own end of the bar. */
.topbar-inner {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0.6rem var(--page-pad);
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.topnav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: var(--s-5);
}

.topnav-link {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem var(--s-3);
  border-bottom: 2px solid transparent;
  font-size: var(--t-sm);
  letter-spacing: var(--track-small);
  color: var(--text-2);
}

.topnav-link:hover {
  color: var(--text);
  text-decoration: none;
}

/* The lit tab. A lime rule, never lime text: at this size the spec
   forbids the second, and a few hundred pixels of fill stays well under
   the 5% the accent is allowed. */
.topnav-link.is-active {
  color: var(--text);
  border-bottom-color: var(--lime);
}

.topbar-end {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-left: auto;
}

.brand {
  font-family: var(--font-head);
  font-weight: 500;
  text-decoration: none;
  color: var(--text);
}

/* Sits in .topbar-end, which already holds it to the right. */
.timer-badge {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.9rem;
  color: var(--text-2);
}

/* Outranks a:hover, which underlines every other link. The row brightens
   instead. */
.timer-badge:hover {
  text-decoration: none;
  color: var(--text);
}

/* Lime reads as a state light here, never as text. */
.timer-play {
  color: var(--lime);
  font-size: 0.75rem;
  line-height: 1;
  flex: none;
}

.topbar-user {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  letter-spacing: 0.01em;
  color: var(--text-2);
}

.topbar-user-link {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: inherit;
  text-decoration: none;
}

.topbar-user-link:hover {
  color: var(--text);
  text-decoration: none;
}

.topbar-user-link.is-active { color: var(--text); }

/* --- Footer --- */

.footer {
  flex: none;
  border-top: var(--hairline);
  background: var(--surface);
}

.footer-inner {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--s-5) var(--page-pad);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  align-items: baseline;
  justify-content: space-between;
  font-size: var(--t-xs);
  letter-spacing: var(--track-small);
}

.footer-mark {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.footer-name {
  font-family: var(--font-head);
  font-weight: 500;
  color: var(--text-2);
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-4);
}

.footer-links a { color: var(--text-3); }
.footer-links a:hover { color: var(--text-2); }

/* .btn-link is coral, which is for deletes. Log out is not one. */
.footer-out {
  color: var(--text-3);
  font-size: inherit;
  letter-spacing: inherit;
  padding: 0;
}

.footer-out:hover {
  color: var(--text-2);
  text-decoration: underline;
}

.crumbs {
  margin: 0 0 0.5rem;
  padding-bottom: 0.4rem;
  border-bottom: var(--hairline);
  font-size: 0.9rem;
  color: var(--text-3);
}

.crumbs a { color: var(--sage); }
.crumbs a:hover { color: var(--text-2); }

/* --- Cards and forms --- */

.card {
  background: var(--surface);
  border: var(--hairline);
  border-radius: var(--r-card);
  padding: 1rem;
  margin-bottom: 1.5rem;
}

.card label {
  display: block;
  margin-bottom: 0.75rem;
  font-size: 0.9rem;
  letter-spacing: 0.01em;
  color: var(--text-2);
}

/* Same slot as .card label, for fields whose control is a trix-editor
   (a contenteditable host must not sit inside a <label>). */
.card .field { margin-bottom: 0.75rem; }

.card .field-label {
  display: block;
  font-size: 0.9rem;
  letter-spacing: 0.01em;
  color: var(--text-2);
}

.card trix-editor { margin-top: 0.2rem; }

.card input[type="text"],
.card input[type="email"],
.card input[type="password"],
.card input[type="date"],
.card select,
.card textarea {
  display: block;
  width: 100%;
  margin-top: 0.2rem;
  padding: 0.5rem 0.6rem;
  border: var(--hairline);
  border-radius: var(--r-control);
  font: inherit;
  color: var(--text);
  background: var(--raised);
}

.card input::placeholder,
.card textarea::placeholder { color: var(--text-3); }

/* Date inputs and selects size themselves from their own widget, so pin
   one height to keep a row of fields level. Textarea keeps growing. */
.card input[type="text"],
.card input[type="email"],
.card input[type="password"],
.card input[type="date"],
.card select {
  height: 2.5rem;
  line-height: 1.4;
}

.field-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.field-grid label { flex: 1 1 12rem; }

.inline-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.inline-form input[type="text"] { flex: 1 1 14rem; width: auto; }

/* The one lime action per view. Everything else is a ghost or a link. */
.btn {
  padding: 0.55rem 1.1rem;
  border: none;
  border-radius: var(--r-control);
  background: var(--lime);
  color: var(--forest);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out),
    color var(--dur-2) var(--ease-out);
}

.btn:hover { background: var(--electric); }
.btn:active { background: var(--olive); }

/* Same box as .btn: a ghost and a primary sit side by side in
   .form-actions, and two heights there read as a mistake. The hairline
   is drawn inside the same padding, so nothing shifts but the height. */
.btn-ghost {
  background: none;
  border: var(--hairline);
  color: var(--text);
  padding: 0.55rem 1.1rem;
}

.btn-ghost:hover { background: var(--raised); }
.btn-ghost:active { background: var(--raised); }

.btn-link {
  background: none;
  border: none;
  color: var(--danger);
  padding: 0.2rem 0.3rem;
}

.btn-link:hover,
.btn-link:active {
  background: none;
  text-decoration: underline;
}

/* The spec has no destructive button, and lime is the wrong answer: it
   is the colour of the one thing a view wants you to do. A bordered
   ghost in coral, filled once it is the button under the pointer. */
.btn-danger {
  background: none;
  border: 0.5px solid var(--danger);
  color: var(--danger);
}

.btn-danger:hover,
.btn-danger:active {
  background: var(--danger);
  color: var(--void);
}

.btn:disabled,
.btn:disabled:hover {
  color: var(--text-3);
  background: none;
  cursor: not-allowed;
}

.form-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Delete sits apart, on the right. */
.form-actions .btn-link { margin-left: auto; }

.section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}

.head-actions {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  flex: none;
}

.comment-form { margin-top: 1.5rem; }
.comment-form .form-actions { margin-top: 0.75rem; }

/* A keyboard way in, said once and quietly. Never lime: it sits under
   14px, where lime vibrates. */
.hint {
  margin: 0;
  color: var(--text-3);
  font-size: var(--t-3xs);
  letter-spacing: var(--track-small);
}

kbd {
  padding: 0.1rem 0.35rem;
  border: 0.5px solid var(--border);
  border-radius: var(--r-control);
  background: var(--raised);
  color: var(--text-2);
  font-family: var(--font-head);
  font-size: var(--t-3xs);
}

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

.rows { display: flex; flex-direction: column; gap: 0.5rem; }

/* Never transition transform on this element. A drag writes
   .style.transform on every pointermove and cardUnder() reads the live
   rectangle to decide the drop target: an animated transform would make
   the card lag the pointer and hand cardUnder() a box that is still on
   its way, so the card would land in the wrong place. The properties
   below are named one by one for the same reason -- "transition: all"
   would quietly take transform with it. */
.row {
  background: var(--surface);
  border: var(--hairline);
  border-radius: var(--r-card);
  padding: 0.75rem 1rem;
  display: flex;
  gap: 0.5rem;
  align-items: center;
  justify-content: space-between;
  transition: background-color var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out),
    opacity var(--dur-3) var(--ease-out);
}

.row:hover { border-color: var(--text-3); }

.row-title {
  font-family: var(--font-head);
  font-weight: 500;
}

/* The grip that starts a drag. Furniture, so it stays quiet. */
.drag-handle {
  flex: none;
  background: none;
  border: 0;
  padding: 0 0.25rem;
  color: var(--text-3);
  font-size: 1rem;
  line-height: 1;
  cursor: grab;
  touch-action: none; /* a touch drag must not scroll the page */
}

.drag-handle:hover { color: var(--text-2); }

.row.is-dragging {
  background: var(--raised);
  position: relative;
  z-index: var(--z-raised);
}

.row.is-dragging .drag-handle { cursor: grabbing; }

/* The handle sits beside the card body, which takes the rest. */
.drag-handle + div { flex: 1; }

/* The project cards sit two to a row, and stack on a narrow screen. */
.rows-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 40rem) {
  .rows-grid { grid-template-columns: minmax(0, 1fr); }
}

/* A card of its own: the name on top, the facts on the bottom edge. */
.rows-grid .row {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 0.75rem;
  min-height: 7rem;
  padding: 1rem;
}

.rows-grid .empty { grid-column: 1 / -1; }

.row-top {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  min-width: 0;
}

.row-top .row-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Pushed to the bottom edge: the count on the left, the clock right.
   The selector outweighs the .row > .meta rules further down. */
.rows-grid .row > .row-foot {
  margin: auto 0 0;
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  text-align: left;
}

/* Title and its count share one line. */
.row-head {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  min-width: 0;
}

/* A todo row is one line: title on the left, its facts on the right. */
.row > .row-head { align-items: center; }

/* Names give way with an ellipsis; the facts beside them keep their
   width, on both the todolist rows and the project sublists. */
.row > div,
.sub-rows li { min-width: 0; }

.row-head .row-title,
.sub-rows li > a {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-head .meta,
.sub-rows li .meta {
  flex: none;
  white-space: nowrap;
}

.row > .meta {
  margin: 0;
  flex: none;
  white-space: nowrap;
  text-align: right;
}

.row > .meta .avatar { margin-right: 0.3rem; vertical-align: middle; }

.sub-rows {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.sub-rows li {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

/* A done todo is out of the way until it is asked for: per list on the
   project page, and for the whole listing on a todolist page. Hidden by
   the stylesheet, so it stays hidden with no script. */
.sub-rows li.is-done,
.rows > .row.is-done { display: none; }

.row.show-done .sub-rows li.is-done,
.rows.show-done > .row.is-done { display: flex; }

/* A list with everything in it done looks the same as a list with
   nothing in it. Say which one it is, and name the control that brings
   the finished todos back. The stylesheet decides, not the script, so
   ticking the last open todo reveals this on its own. */
.blank--done,
.all-done { display: none; }

.rows:not(.show-done):not(:has(> .row:not(.is-done))) > .blank--done,
.row:not(.show-done):not(:has(.sub-rows > li:not(.is-done))) .all-done {
  display: block;
}

/* Nothing left in it to show, so it keeps none of its room either. */
.row:not(.show-done):not(:has(.sub-rows > li:not(.is-done))) .sub-rows {
  display: none;
}

/* The per-list toggle sits with the count, and reads as quietly. The
   one in a section head keeps its button looks. */
.row-head .toggle-done {
  flex: none;
  background: none;
  border: 0;
  padding: 0;
  color: var(--text-3);
  font-size: 0.85rem;
  letter-spacing: 0.01em;
  cursor: pointer;
}

.row-head .toggle-done:hover {
  color: var(--text-2);
  text-decoration: underline;
}
.sub-rows .meta { margin: 0; }
.sub-rows .meta .avatar { margin-right: 0.3rem; vertical-align: middle; }
.sub-rows a { color: var(--text-2); }

/* A row that grows a sublist starts at the top, not centred on it. */
.row:has(.sub-rows) { align-items: flex-start; }

/* The completed checkbox posts on change; the form is only its wrapper. */
.status { display: inline-flex; align-items: center; gap: 0.4rem; }

/* Drawn here rather than left to the platform, so the box keeps the
   surface steps and the lime fill of every other control. */
.status input[type="checkbox"] {
  appearance: none;
  margin: 0;
  width: 1.15rem;
  height: 1.15rem;
  border: var(--hairline);
  border-radius: 2px;
  background: var(--raised);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out);
}

.status input[type="checkbox"]:hover { border-color: var(--sage); }

.status input[type="checkbox"]:checked {
  background: var(--lime);
  border-color: var(--lime);
}

/* The tick: two borders of an empty box, rotated. */
.status input[type="checkbox"]:checked::after {
  content: "";
  display: block;
  width: 0.3rem;
  height: 0.55rem;
  margin: 0.1rem auto 0;
  border: solid var(--forest);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* The no-script way to post a tick. The class lands on <html> before
   the first paint, so with a script this is never seen. */
.js .status-go { display: none; }

.status-go {
  margin-left: 0.4rem;
  padding: 0.15rem 0.5rem;
  font-size: var(--t-3xs);
}

/* On the way out. Done rows are hidden by the stylesheet, so without
   this one they would simply blink away. */
.row.is-settling { opacity: 0.4; }

.status label { cursor: pointer; }
.row .status { margin-right: 0.4rem; vertical-align: middle; }

.is-done .row-title,
.is-done > a {
  text-decoration: line-through;
  color: var(--text-3);
}

.meta {
  margin: 0.2rem 0 0;
  color: var(--text-3);
  font-size: 0.85rem;
  letter-spacing: 0.01em;
}

/* Absolute, not 0.8em: inside .meta's 0.85rem that compounded down to
   about 11px, which is too small for the coral to stay readable. */
.badge {
  display: inline-block;
  padding: 0 0.55em;
  border-radius: var(--r-pill);
  background: var(--raised);
  border: var(--hairline);
  font-size: var(--t-3xs);
  letter-spacing: var(--track-small);
  font-weight: 400;
}

.badge-high   { color: var(--danger); }
.badge-medium { color: var(--sage); }
.badge-low    { color: var(--text-3); }

/* --- Timer --- */

/* The heading and the line below it carry their own spacing; drop the
   defaults so the card stays tight. */
/* The facts spread over the width, and each one keeps its label with
   its value. Below the phone break they stack. */
.facts {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.4rem 1.5rem;
  color: var(--text-2);
  border-top: var(--hairline);
  border-bottom: var(--hairline);
  margin: 0.4rem 0;
  padding: 0.4rem 0;
}

.facts p { margin: 0; }
.facts .fact-label { color: var(--text-3); }

@media (max-width: 40rem) {
  .facts {
    flex-direction: column;
    align-items: flex-start;
  }
}

#todo-view .meta .avatar {
  margin-right: 0.3rem;
  vertical-align: middle;
}

/* The checkbox leads the title, as it does in both listings. */
.title-line {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}

#todo-view .section-head { align-items: center; }

#todo-view h1 {
  font-size: 1.3rem;
  line-height: 1.2;
}

/* The line that sets the time tracking off from the description. */
.card hr {
  border: 0;
  border-top: var(--hairline);
  margin: 1rem 0;
}

/* Play and stop carry the meaning, so the button holds only the mark.
   The fill is the one lime thing in the card. */
.timer-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.6rem;
  height: 2.6rem;
  padding: 0;
  border-radius: var(--r-pill);
  color: var(--lime);
  line-height: 0;
}

.timer h2 { margin: 0; }
.timer form { display: flex; align-items: center; gap: 0.6rem; }
.timer .meta { margin: 0; }
.timer .empty { margin: 0.35rem 0 0; }

/* Same for the collapsed todo card, which also carries the title. */
#todo-view .empty { margin: 0.35rem 0 0; }
#todo-view h1 { margin-bottom: 0; }

.totals {
  margin: 0.75rem 0 0;
  padding-left: 0;
  list-style: none;
  font-size: 0.9rem;
  color: var(--text-2);
}

/* The picture leads the row, as it does on a comment. */
.totals .avatar { margin-right: 0.4rem; vertical-align: middle; }

/* The sum of the rows above, ruled off from them. */
.totals .total {
  margin-top: 0.35rem;
  padding-top: 0.35rem;
  border-top: var(--hairline);
  color: var(--text);
}

.totals strong {
  font-family: var(--font-head);
  font-weight: 500;
  color: var(--text);
}

/* --- Comments --- */

/* The one-line kind, for a slot inside a card where a block would be
   pompous. The .blank below is the other kind. */
.empty {
  color: var(--text-3);
  font-size: var(--t-xs);
  letter-spacing: var(--track-small);
}

/* --- Empty states ---
   A place with nothing in it yet. The dashed hairline reads as a slot
   waiting to be filled, and it is neither a shadow nor a gradient. No
   background, so it sits a step below the cards around it rather than
   pretending to be one. */

.blank {
  padding: var(--s-7) var(--s-5);
  border: 0.5px dashed var(--border);
  border-radius: var(--r-card);
  text-align: center;
}

/* Sits in a flex column and takes the room that is left. */
.blank--fill {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* Already inside a card or a panel, so it brings no second border. */
.blank--bare {
  border: none;
  padding: var(--s-5) var(--s-4);
}

.blank-mark {
  margin: 0 0 var(--s-3);
  color: var(--text-3);
  line-height: 1;
}

.blank-title {
  margin: 0;
  font-family: var(--font-head);
  font-weight: 500;
  font-size: var(--t-md);
  color: var(--text-2);
}

.blank-body {
  margin: var(--s-1) auto 0;
  max-width: 34ch;
  font-size: var(--t-xs);
  letter-spacing: var(--track-small);
  color: var(--text-3);
}

.blank-action { margin: var(--s-5) 0 0; }

/* The grid places children in cells; this one spans the row. */
.rows-grid .blank { grid-column: 1 / -1; }

/* Set the comment list off from the card above it. */
h2:has(+ .comments) {
  padding-bottom: 0.4rem;
  border-bottom: var(--hairline);
}

.comments { display: flex; flex-direction: column; gap: 1rem; }

.comment {
  background: var(--surface);
  border: var(--hairline);
  border-radius: var(--r-card);
  padding: 1rem;
}

.comment header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.5rem;
}

/* The name is its own box, so it can be centred against the picture
   rather than sat on the line's baseline under it. */
.comment .author {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-head);
  font-weight: 500;
}

/* A hair of room above, because a line of capitals reads high inside a
   box measured from its descenders. */
.comment .author .author-name { padding-top: 0.05rem; }

.comment-meta {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
}

.comment time,
.comment .edited {
  color: var(--text-3);
  font-size: 0.85rem;
  letter-spacing: 0.01em;
}

.comment-edit-form trix-editor { margin-top: 0.5rem; }
.comment-edit-form .form-actions { margin-top: 0.75rem; }

.comment .body img {
  max-width: 100%;
  height: auto;
  border-radius: var(--r-control);
}

.activity {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: var(--hairline);
}

.activity li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0;
  border-bottom: var(--hairline);
  color: var(--text-2);
}

/* One line, cut with an ellipsis: a long todo or project name never
   pushes the time off the row. An initials avatar is a span as well,
   and it keeps the size .avatar gives it. */
.activity li > span:not(.avatar) {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.activity time {
  flex: none;
  white-space: nowrap;
  color: var(--text-3);
  font-family: var(--font-head);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
}

trix-editor { position: relative; }

trix-editor.is-dragover {
  outline: 2px dashed var(--lime);
  outline-offset: -4px;
  background: var(--raised);
}

trix-editor.is-dragover::after {
  content: "Drop a file to upload";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--raised);
  color: var(--wash);
  font-weight: 500;
  pointer-events: none;
  border-radius: var(--r-control);
}

/* trix.css hands a preview figure width:100%, so one image fills the
   column - some 900px in a comment. Cap it and let the height follow.
   min() keeps it inside a narrower box, such as a chat bubble. */
.trix-content .attachment--preview,
trix-editor .attachment--preview {
  display: inline-block;
  width: auto;
  max-width: min(600px, 100%);
  vertical-align: top;
}

/* The editor has no cap of its own: without this the file is drawn at its
   own pixel size while it is being written. */
.trix-content .attachment--preview img,
trix-editor .attachment--preview img {
  max-width: 100%;
  height: auto;
}

/* Two or more images read as one set, so they tile. Proportions are kept,
   which leaves a row ragged along the bottom; the straight edge would cost
   a crop of somebody's picture. A tile cannot be a grid item: the figure
   is a sibling of the paragraphs around it. */
.trix-content:has(.attachment--preview ~ .attachment--preview)
  .attachment--preview,
trix-editor:has(.attachment--preview ~ .attachment--preview)
  .attachment--preview {
  max-width: 180px;
  margin: 0.25rem 0.5rem 0.25rem 0;
}

.body p { margin: 0 0 0.75em; }
.body p:last-child { margin-bottom: 0; }

.body ul,
.body ol { margin: 0 0 0.75em; padding-left: 1.5em; }
.body li { margin: 0.15em 0; }
.body li > ul,
.body li > ol,
.body ul:last-child,
.body ol:last-child { margin-bottom: 0; }

/* A video is capped the same way, and on the same surfaces: a chat bubble
   is narrower than the column, and nothing else held one back there. */
.trix-content video,
trix-editor video {
  display: block;
  max-width: min(600px, 100%);
  height: auto;
  border-radius: var(--r-control);
}

.trix-content {
  line-height: 1.8;
  color: rgba(255, 255, 255, 0.7);
}

/* trix.css zeroes the margin of every child (.trix-content *), which
   runs the paragraphs of a long description together. Give the blocks
   their rhythm back; this sheet loads last, so it wins. */
.trix-content p,
.trix-content ul,
.trix-content ol,
.trix-content blockquote,
.trix-content pre { margin: 0 0 0.75em; }

.trix-content h1,
.trix-content h2,
.trix-content h3,
.trix-content h4,
.trix-content h5,
.trix-content h6 { margin: 1.25em 0 0.5em; }

/* The first and last block sit flush, so the box keeps its own padding. */
.trix-content > :first-child { margin-top: 0; }
.trix-content > :last-child { margin-bottom: 0; }

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

/* A document cannot be previewed, so it is named: the page shape holding
   its extension, then the name it was uploaded with and its size. The
   shape is drawn here rather than written into the markup, because the
   sanitizer keeps no <svg>. */
.file-card {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  max-width: 100%;
  padding: 0.4rem 0.75rem 0.4rem 0.4rem;
  border: 0.5px solid var(--border);
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  transition: background-color var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out);
  vertical-align: middle;
  /* Two cards in a row are written with nothing between them, so the gap
     has to come from here. The same step the image tiles keep. */
  margin-right: 0.5rem;
}

/* The card answers a hover with its own surface step, so it outranks
   a:hover, which underlines every other link. */
.file-card:hover { background: var(--raised); text-decoration: none; }

.file-card:focus-visible {
  outline: 1px solid var(--electric);
  outline-offset: 2px;
}

/* The page: a rectangle with its top corner cut away, and a flap painted
   back into the cut. */
.file-card__ext {
  position: relative;
  flex: none;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  width: 2.1rem;
  height: 2.6rem;
  padding-bottom: 0.3rem;
  background: var(--raised);
  clip-path: polygon(0 0, 65% 0, 100% 28%, 100% 100%, 0 100%);
  font-family: var(--font-head);
  font-variant-numeric: tabular-nums;
  font-size: 0.65rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-2);
}

.file-card:hover .file-card__ext { background: var(--border); }

.file-card__ext::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 35%;
  height: 28%;
  background: var(--border);
  clip-path: polygon(0 0, 0 100%, 100% 100%);
}

.file-card:hover .file-card__ext::after { background: var(--text-3); }

.file-card__meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.4;
}

.file-card__name {
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.95rem;
}

.file-card__size {
  font-family: var(--font-head);
  font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
  letter-spacing: 0.01em;
  color: var(--text-3);
}

/* --- @mentions --- */

/* Render mention content attachments inline instead of as block figures. */
.trix-content .attachment--content,
trix-editor .attachment--content {
  display: inline-block;
  margin: -7px 0 0 0;
  vertical-align: middle;
}

/* A file card is a content attachment too, but it stands as tall as a
   control, so it does not want the nudge that lines a chip up with text.
   Only the editor needs this: formatParagraphs() unwraps the figure, so a
   posted card is a bare link inside its paragraph. */
trix-editor .attachment--content:has(.file-card) { margin: 0; }

.mention {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  gap: 0.3rem;
  padding: 0.05rem;
  border-radius: var(--r-pill);
  color: var(--wash);
  font-weight: 400;
  line-height: 1.1;
  white-space: nowrap;
}

/* The box is twice the letter height, which is what keeps the initials
   off the rim. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2em;
  height: 2em;
  border-radius: var(--r-pill);
  background: var(--forest);
  color: var(--wash);
  object-fit: cover;
  font-size: 0.7em;
  font-weight: 500;
  letter-spacing: 0.02em;
  flex: none;
}

.comment .author .avatar { margin-right: 0.4rem; vertical-align: middle; }

.avatar-pick {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.2rem;
}

.avatar-pick .avatar { width: 3rem; height: 3rem; font-size: 1.2rem; }

.avatar-pick input[type="file"] {
  color: var(--text-2);
  font-size: 0.9rem;
}

/* The same box .avatar takes, for the same reason. */
.mention-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2em;
  height: 2em;
  border-radius: 50%;
  background: var(--forest);
  color: var(--wash);
  object-fit: cover;
  font-size: 0.7em;
  font-weight: 500;
  letter-spacing: 0.02em;
  flex: none;
}

/* Elevation is a surface step, not a shadow. */
.mention-menu {
  position: absolute;
  z-index: var(--z-menu);
  min-width: 12rem;
  max-height: 14rem;
  overflow-y: auto;
  background: var(--surface);
  border: var(--hairline);
  border-radius: var(--r-card);
  padding: 0.25rem;
  animation: rise var(--dur-2) var(--ease-out);
}

@keyframes rise {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.mention-menu__item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.5rem;
  border-left: 1px solid transparent;
  border-radius: var(--r-control);
  cursor: pointer;
}

.mention-menu__item:hover { background: var(--raised); }

.mention-menu__item.is-active {
  background: var(--raised);
  border-left-color: var(--lime);
}

/* --- People --- */

.person-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.person-head .meta { margin: 0; }
.person-head .avatar { margin-right: 0.4rem; vertical-align: middle; }

/* The remove button sits at the far end of the row. */
.person-head form { margin-left: auto; }

/* A tick and its project name read as one line, not as a stacked
   label over a control. */
.card .tick {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.35rem;
  color: var(--text);
}

.card .tick input { margin: 0; }

/* --- Error page ---
   No card. A dead end reads better as open page than as a panel
   pretending there is something in it. */

.error-page {
  max-width: 34rem;
  margin: var(--s-8) 0;
}

/* The status code is the one hero on this page, so it takes the display
   weight the spec reserves for one. */
.error-page .code {
  margin: 0;
  color: var(--text-3);
  line-height: 1;
}

.error-message {
  margin: var(--s-3) 0 var(--s-2);
  font-size: var(--t-xl);
}

.error-page .meta { margin: 0; }

.error-actions { margin: var(--s-5) 0 0; }

.error-detail {
  margin: var(--s-5) 0 0;
  padding: var(--s-3);
  border: var(--hairline);
  border-radius: var(--r-control);
  background: var(--surface);
  font-family: var(--font-head);
  font-size: var(--t-3xs);
  color: var(--text-2);
  white-space: pre-wrap;
  overflow-x: auto;
}

/* The display treatment: the one place weight 700 is allowed. */
.display {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--t-3xl);
  letter-spacing: var(--track-display);
  font-variant-numeric: tabular-nums;
}

/* --- Chat --- */

/* People across the top, the conversation under them taking whatever
   room is left down the page. */
/* The height is measured, not guessed. main already takes what the top
   bar and the footer leave; the crumbs and the heading keep their own
   height, and the chat takes the rest. A taller top bar is now correct
   by construction rather than by arithmetic that has to be kept in step.
   Scoped with :has() so the other pages keep an ordinary block main. */
body:has(.chat-layout) main {
  display: flex;
  flex-direction: column;
  /* Without this a flex item refuses to shrink below its content, and
     the thread's own scrollbar never appears. */
  min-height: 0;
}

.chat-layout {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  flex: 1 1 auto;
  min-height: 24rem;
}

.chat-bar {
  flex: none;
  padding: 0.75rem 1rem;
  overflow-x: auto;
}

/* Everybody, as faces: one already talked to and one not yet talked to
   look the same, and clicking either opens the conversation. One line,
   so a big company scrolls sideways rather than pushing the
   conversation down the page. */
.chat-people {
  display: flex;
  gap: 0.5rem;
}

.chat-person {
  position: relative;
  display: block;
  flex: none;
  border-radius: var(--r-pill);
  line-height: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}

.chat-person .avatar {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 0.9rem;
}

.chat-person:hover { opacity: 0.85; }

/* The open conversation, ringed rather than filled: a face is already
   a colour of its own. */
.chat-person.active {
  outline: 1px solid var(--lime);
  outline-offset: 2px;
}

/* The count rides the corner of the face it belongs to. */
.chat-person .chat-pill {
  position: absolute;
  top: -0.25rem;
  right: -0.25rem;
  line-height: 1.3;
}

/* The one place a count is loud enough to be lime. */
.chat-pill,
.chat-badge {
  background: var(--lime);
  color: var(--forest);
  border-radius: var(--r-pill);
  padding: 0 0.4rem;
  font-family: var(--font-head);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  line-height: 1.4;
}

/* Nobody picked: no panel at all, only the invitation, in the middle of
   the room the conversation would have taken. .blank--fill does that
   now, for this and for a thread with nothing in it yet. */

/* The conversation takes the rest of the page: the thread grows, the
   head and the composer keep their own height. */
.chat-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Rules the name off from the words under it. */
.chat-panel .section-head {
  padding-bottom: 0.5rem;
  border-bottom: var(--hairline);
}

/* What the clock says where the other person is sitting. */
.chat-clock {
  color: var(--text-3);
  font-family: var(--font-head);
  font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
}

.chat-panel .section-head h2 {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
}

.chat-thread {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* Room on the right for the scrollbar, which would otherwise sit on
     top of the reader's own pictures. The gutter is held whether it is
     needed or not, so the thread does not shift when it fills up. */
  padding: 0.5rem 0.75rem 0.5rem 0;
  scrollbar-gutter: stable;
}

/* The speaker's picture leads the line, and changes side for the
   reader's own words. */
.chat-message {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  max-width: 80%;
  animation: rise var(--dur-3) var(--ease-out);
}

.chat-message.mine {
  align-self: flex-end;
  flex-direction: row-reverse;
}

.chat-message .avatar {
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  font-size: 0.7rem;
}

/* A message that carries on the one above it keeps the picture's room
   but not the picture, drops the time, and sits closer. */
.chat-message.grouped { margin-top: -0.35rem; }
.chat-message.grouped .avatar { visibility: hidden; }
.chat-message.grouped .chat-meta { display: none; }

/* Lets a long word shrink the bubble rather than the row. */
.chat-said { min-width: 0; }

/* No borders: the step from the card to the bubble is the edge. Own
   words sit a step up, the other side a step down. */
.chat-bubble {
  background: var(--void);
  border-radius: var(--r-card);
  padding: 0.4rem 0.75rem;
}

.chat-message.mine .chat-bubble { background: var(--raised); }

/* Above the words, so a turn is headed by its time and the messages
   under it follow unbroken. */
.chat-meta {
  margin: 0 0 0.15rem;
  color: var(--text-3);
  font-family: var(--font-head);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
}

.chat-message.mine .chat-meta { text-align: right; }

/* The other side is writing: three dots after their name, breathing so
   they do not read as part of it. */
.chat-typing {
  color: var(--text-3);
  letter-spacing: 0.15em;
  animation: chat-typing 1.4s ease-in-out infinite;
}

@keyframes chat-typing {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1; }
}

/* Asked for stillness, so everything stops, not only the one animation
   that used to be named here. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* The dots stand for "is writing". Stopped at full strength they read
     as three full stops somebody typed, so dim them instead. */
  .chat-typing {
    animation: none;
    opacity: 0.6;
  }
}

/* One row: the editor takes the room that is left, the button keeps its
   own. */
.chat-composer {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  /* Ruled off from the thread, as the name above it is. */
  padding-top: 0.75rem;
  border-top: var(--hairline);
}

/* A message is a line or two, not a document. It grows with what is
   typed and stops well short of the thread. */
.chat-composer trix-editor {
  flex: 1;
  min-height: 2.75rem;
  max-height: 8rem;
  overflow-y: auto;
}

.chat-composer .btn { flex: none; }

/* A chat line is one message, not a document: the formatting bar only
   gets in the way. */
.chat-composer trix-toolbar { display: none; }

/* --- Asking first --- */

.confirm {
  padding: 0;
  border: var(--hairline);
  border-radius: var(--r-card);
  background: var(--surface);
  color: var(--text);
  max-width: 28rem;
  width: calc(100% - 2rem);
}

.confirm[open] { animation: settle var(--dur-3) var(--ease-out); }

@keyframes settle {
  from { opacity: 0; transform: scale(0.98); }
  to   { opacity: 1; transform: scale(1); }
}

.confirm form { padding: var(--s-5); }

.confirm-message {
  margin: 0 0 var(--s-5);
  font-size: var(--t-base);
}

/* --void at 80%. No blur: the page behind stays what it is. */
.confirm::backdrop { background: rgba(18, 26, 19, 0.8); }

.confirm .form-actions { justify-content: flex-end; }

/* --- Saying so ---
   Toasts, the working state, and the chat's reconnecting line. The
   toast borrows .flash's colours on purpose: the same news should not
   look like two different systems depending on whether the page was
   reloaded to deliver it. */

.toasts {
  position: fixed;
  z-index: var(--z-toast);
  bottom: var(--s-4);
  left: var(--s-4);
  right: var(--s-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  pointer-events: none;
}

@media (min-width: 40rem) {
  .toasts {
    left: auto;
    align-items: flex-end;
    max-width: 26rem;
  }
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  animation: rise var(--dur-3) var(--ease-out);
  transition: opacity var(--dur-3) var(--ease-out),
    transform var(--dur-3) var(--ease-out);
  padding: 0.6rem 0.9rem;
  border: var(--hairline);
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--text-2);
  font-size: var(--t-xs);
  letter-spacing: var(--track-small);
  pointer-events: auto;
}

.toast-error {
  border-color: var(--danger);
  color: var(--danger);
}

.toast-ok {
  border-color: var(--pine);
  color: var(--fern);
}

.toast-close {
  flex: none;
  padding: 0 0.2rem;
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  line-height: 1;
  opacity: 0.6;
  cursor: pointer;
}

.toast-close:hover { opacity: 1; }

.toast.is-going {
  opacity: 0;
  transform: translateY(8px);
}

/* A ring, not a shimmer: a shimmer is a gradient. */
.is-busy {
  position: relative;
  color: transparent !important;
}

.is-busy::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1em;
  height: 1em;
  margin: -0.5em 0 0 -0.5em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--r-pill);
  color: var(--forest);
  animation: spin 700ms linear infinite;
}

.btn-ghost.is-busy::after { color: var(--text-2); }

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

.chat-offline {
  flex: none;
  margin: 0;
  padding: 0.35rem 0.6rem;
  border: 0.5px solid var(--danger);
  border-radius: var(--r-control);
  font-size: var(--t-3xs);
  letter-spacing: var(--track-small);
  color: var(--danger);
  text-align: center;
}

/* --- Responsive ---
   The narrow rules that belong to a single component stay beside that
   component (the projects grid, the todo facts). What is left is here,
   because it crosses components and reads better as one pass down the
   widths. */

/* The column no longer needs the page to breathe around it. */
@media (max-width: 60rem) {
  :root { --page-pad: 1.5rem; }
}

@media (max-width: 40rem) {
  /* Two rows: the brand and the person, then the nav across the full
     width. Two links do not earn a drawer, and a drawer is one more
     thing that can be broken. */
  .topbar-inner {
    flex-wrap: wrap;
    row-gap: 0;
  }

  .topnav {
    order: 3;
    width: 100%;
    margin-left: 0;
    border-top: var(--hairline);
  }

  .topnav-link {
    flex: 1;
    justify-content: center;
    padding: 0.6rem var(--s-2);
  }

  .topbar-end { gap: var(--s-3); }

  /* The face still names the person, and the settings link is in the
     footer as well. */
  .topbar-user-name { display: none; }

  .timer-badge { font-size: var(--t-xs); }

  /* A heading and its actions stop competing for one line. */
  .section-head,
  .head-actions {
    flex-wrap: wrap;
  }

  .field-grid label { flex: 1 1 100%; }

  .comment header {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.2rem;
  }

  .chat-message { max-width: 92%; }

  /* Sideways scroll with nothing to say so is the worst way to hide
     people on a phone. Wrap them and scroll down instead. */
  .chat-bar { overflow-x: visible; }

  .chat-people {
    flex-wrap: wrap;
    max-height: 8rem;
    overflow-y: auto;
  }

  .footer-inner {
    flex-direction: column;
    gap: var(--s-3);
  }
}

@media (max-width: 30rem) {
  :root { --page-pad: 0.75rem; }

  .card { padding: var(--s-3); }

  h1 { font-size: var(--t-lg); }
}

/* Asks about the finger, not the viewport: a touch laptop needs this and
   a narrow desktop window does not. */
@media (pointer: coarse) {
  .btn,
  .btn-ghost,
  .topnav-link,
  .toggle-done,
  .drag-handle {
    min-height: 44px;
  }

  .btn-link { min-height: 32px; }

  /* The drawn box stays small; the target around it does not. */
  .status input[type="checkbox"] { position: relative; }

  .status input[type="checkbox"]::before {
    content: "";
    position: absolute;
    inset: -12px;
  }
}

/* --- Trix (vendor override) ---
   trix.css is a light-theme vendor file loaded before this one. Keep it
   untouched and repaint it here. */

trix-editor {
  border: var(--hairline);
  border-radius: var(--r-control);
  background: var(--raised);
  color: var(--text);
}

/* Trix injects its own placeholder rule at runtime, after this file, so
   outrank it on specificity rather than order. */
trix-editor[placeholder]:empty:not(:focus)::before {
  color: var(--text-3);
}

trix-toolbar .trix-button {
  color: var(--text-2);
  font-weight: 400;
  border-bottom: var(--hairline);
}

trix-toolbar .trix-button:not(:first-child) {
  border-left: var(--hairline);
}

trix-toolbar .trix-button.trix-active {
  background: var(--raised);
  color: var(--text);
}

trix-toolbar .trix-button:disabled { color: var(--text-3); }

/* The icons are black-stroked data-URI SVGs; flip them for a dark bar. */
trix-toolbar .trix-button--icon::before { filter: invert(1); }

trix-toolbar .trix-dialog {
  background: var(--surface);
  border-top: 2px solid var(--border);
  border-radius: var(--r-card);
  box-shadow: none;
  color: var(--text);
}

trix-toolbar .trix-input--dialog {
  background-color: var(--raised);
  border: var(--hairline);
  border-radius: var(--r-control);
  color: var(--text);
}

trix-editor .trix-button { color: var(--text-2); }
