/* ─────────────────────────────────────────────────────────────────────────────
   home.css — the whole look of Azzan's Home, lifted verbatim out of its <style>
   block so the public /azzan page can wear the SAME stylesheet instead of a
   hand-retyped approximation of it. Not a fork and not a subset: this file is the
   app's CSS, moved. Edit it once and both pages change together.

   /azzan uses the app's own class names for that reason — ul/li for its cards,
   .tile for its links, h2 for its section labels — so it inherits the real rules
   rather than re-declaring near-misses that drift apart over time. What is genuinely
   only on that page (the ascii name, the tile grid, the footer) lives in its own
   <style> block, loaded after this one.

   The two hosts the scene needs (#clouds, #grass) and the .house <pre> are markup,
   and scene.js fills them — see that file.
   ───────────────────────────────────────────────────────────────────────────── */

  :root {
    --bg: #f6f6f4;
    --card: #ffffff;
    --text: #1c1c1a;
    --muted: #9a9a94;
    --line: #e8e8e4;
    --accent: #1c1c1a;
    --hover: #fafaf8;
  }

  @media (prefers-color-scheme: dark) {
    :root {
      --bg: #131312;
      --card: #1c1c1a;
      --text: #f2f2ef;
      --muted: #77776f;
      --line: #2b2b28;
      --accent: #f2f2ef;
      --hover: #232320;
    }
  }

  * { box-sizing: border-box; margin: 0; padding: 0; }

  body {
    min-height: 100vh;
    padding: 6vh 24px 60px;
    background: var(--bg);
    color: var(--text);
    font: 400 16px/1.5 ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif;
    -webkit-font-smoothing: antialiased;
  }

  .wrap { max-width: 960px; margin: 0 auto; }

  header { margin-bottom: 22px; }

  /* Four things now, not two — Todo, Links, the reminder and the way in to the panel —
     and on a phone they did not fit: the row ran off the right edge and Admin was simply
     gone, with nothing to say it was there.

     So the row WRAPS. Not a horizontal scroll: a strip that scrolls sideways hides its
     own contents behind a gesture nobody is told about, which is the same failure with
     an extra step. Two short lines show all four at once. */
  .tabs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    row-gap: 6px;
    margin-bottom: 30px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--line);
  }

  .tab {
    padding: 7px 14px;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--muted);
    background: none;
    border: 0;
    border-radius: 8px;
    cursor: pointer;
    transition: color .15s, background .15s;
  }
  /* A tab is one line. Without this the count drops under its word the moment the row
     is tight, which is what turned "TODO 12" into a two-storey button. */
  .tab { white-space: nowrap; }
  .tab:hover { color: var(--text); background: var(--hover); }
  .tab.on { color: var(--bg); background: var(--accent); }
  /* A tab-shaped link that leaves the row. Pushed to the far end and outlined rather
     than filled, because it is never the "current" anything — it is a door. */
  .tab.out {
    margin-left: auto;
    text-decoration: none;
    line-height: normal;
    border: 1px solid var(--line);
  }
  .tab.out:hover { border-color: var(--muted); }

  /* The reminder sits left of the Admin door, and the margin that pushes the pair to the
     far end belongs to whichever of them comes first.

     Called `remind` and NOT `note`, which was the first name and made the button
     invisible: `.note` is already this file's ✎ pencil, and that one is `opacity: 0`
     until its row is hovered. The button was there, 24px square and fully transparent,
     on a page with no row to hover. Anything added to this shared stylesheet has to be
     checked against the names already in it. */
  /* The same button in a head row — the app and the admin panel put it beside Copy
     tracker rather than in the tab strip, because those two pages have no tab strip on
     the screen it belongs to. Shared, so all three read the same. */
  /* Drawn as a BUTTON, not as the bare label its neighbour is.

     .fold is a word you can press — no border, no box — which works for "Copy tracker"
     because pressing it does the obvious thing to the list it is sitting on. This one
     opens something, and a word that opens a panel has to look like it can be pressed
     before you press it. So it gets the outline, the padding and the hover the rest of
     this app gives anything that is a control rather than a label.

     `set` fills it, which is the only state that moves: there is something to read. */
  .head-row .fold.daily {
    padding: 4px 11px;
    white-space: nowrap;
    color: var(--muted);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 999px;
    transition: color .15s, border-color .15s, background .15s;
  }
  .head-row .fold.daily:hover { color: var(--text); border-color: var(--muted); }
  .head-row .fold.daily.set {
    color: #7a5b00;
    background: #ffe9a8;
    border-color: #f2cf6a;
  }
  @media (prefers-color-scheme: dark) {
    .head-row .fold.daily.set { color: #f5d90a; background: rgba(245,217,10,.16); border-color: rgba(245,217,10,.4); }
  }

  .tab.out.remind + .tab.out { margin-left: 0; }
  /* (the outline comes from .tab.out above — a tab is flat, these two are not) */
  .tab.out.remind { color: var(--text); border-color: var(--muted); }
  .tab.out.remind.set { color: #7a5b00; background: #ffe9a8; border-color: #f2cf6a; }
  @media (prefers-color-scheme: dark) {
    .tab.out.remind.set { color: #f5d90a; background: rgba(245,217,10,.16); border-color: rgba(245,217,10,.4); }
  }

  .tab .count { font-weight: 500; opacity: .65; }
  .tab.on .count { opacity: .7; }

  /* On a narrow screen the two doors stop being pushed to the far end — there is no far
     end left to push to once the row has wrapped, and the auto margin only forced a gap
     that made the wrap happen sooner. All four then flow left to right and break where
     they run out. */
  @media (max-width: 640px) {
    .tabs { gap: 6px; }
    .tab { padding: 6px 11px; font-size: 12px; letter-spacing: .02em; }
    .tab.out { margin-left: 0; }
  }
  @media (max-width: 380px) {
    .tab { padding: 5px 9px; font-size: 11px; letter-spacing: 0; }
  }

  /* ── a sheet ──────────────────────────────────────────────────────────────
     One short thing, on top of everything, dismissed by Escape, a click outside or the
     button. A native <dialog>, so focus trapping and the top layer are the browser's job
     and not a pile of z-index. */
  .sheet {
    /* A <dialog> centres itself with `margin: auto` against its default inset:0 — and
       the reset at the top of this file sets `* { margin: 0 }`, which quietly took that
       away and left every sheet pinned to the top-left corner. Put back explicitly, so
       the next reset cannot do it again. */
    position: fixed;
    inset: 0;
    margin: auto;
    height: fit-content;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    width: min(440px, calc(100vw - 32px));
    padding: 22px 24px;
    color: var(--text);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: 0 24px 60px rgba(0,0,0,.28);
  }
  .sheet:not([open]) { display: none; }
  .sheet::backdrop { background: rgba(0,0,0,.45); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
  .sheet h2 { margin: 0 0 12px; font-size: 15px; }
  .sheet-body {
    margin: 0;
    font-size: 15px;
    line-height: 1.6;
    white-space: pre-wrap;      /* a note written as two paragraphs stays two */
    word-break: break-word;
  }
  /* The admin panel's sheet writes instead of reads: same box, one control. */
  .sheet-box {
    width: 100%;
    padding: 11px 13px;
    font: inherit;
    font-size: 15px;
    line-height: 1.6;
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 10px;
    outline: none;
    resize: vertical;
  }
  .sheet-box:focus { border-color: var(--accent); }
  .sheet-hint { margin: 8px 2px 0; font-size: 12px; color: var(--muted); }

  /* "Nothing for today." is not the news, it is the absence of it. */
  .sheet-body.quiet { color: var(--muted); }

  .sheet-acts { display: flex; justify-content: flex-end; margin-top: 20px; }
  .sheet-acts .add { padding: 7px 16px; font-size: 14px; }

  .panel { display: none; }
  .panel.on { display: block; animation: in .18s ease-out; }
  /* the greeting is drawn in an ascii face by the script — see FONT there */
  .greet {
    margin: 0;
    white-space: pre;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-weight: 700;
    font-size: 8.5px;
    line-height: 1.06;
    letter-spacing: 0;
    color: var(--text);
    align-self: center;
  }

  h1 {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 12px;
    font-size: 30px;
    font-weight: 600;
    letter-spacing: -0.02em;
  }

  .status {
    padding: 4px 11px;
    font-size: 12.5px;
    font-weight: 500;
    letter-spacing: 0;
    white-space: nowrap;
    color: var(--muted);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 999px;
  }
  .status.clear  { color: #30a46c; }
  .status.light  { color: #0091ff; }
  .status.packed { color: #f76b15; }
  .status.heavy  { color: #e5484d; }
  .sub { color: var(--muted); font-size: 14px; margin-top: 2px; }

  .cols {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 40px;
    align-items: start;
  }
  @media (max-width: 760px) {
    .cols { grid-template-columns: 1fr; gap: 44px; }
  }

  h2 {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 14px;
  }

  form { display: flex; gap: 8px; margin-bottom: 16px; }
  form.links { flex-wrap: wrap; }

  input {
    flex: 1;
    min-width: 0;
    padding: 12px 14px;
    font: inherit;
    font-size: 15px;
    color: var(--text);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 10px;
    outline: none;
    transition: border-color .15s;
  }
  input::placeholder { color: var(--muted); }
  input:focus { border-color: var(--accent); }

  button.add {
    flex: none;
    padding: 0 18px;
    font: inherit;
    font-size: 15px;
    font-weight: 500;
    color: var(--bg);
    background: var(--accent);
    border: 0;
    border-radius: 10px;
    cursor: pointer;
    transition: opacity .15s;
  }
  button.add:hover { opacity: .85; }

  ul { list-style: none; display: flex; flex-direction: column; gap: 8px; }

  li {
    display: flex;
    flex-direction: column;
    padding: 12px 8px 12px 14px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 10px;
    animation: in .18s ease-out;
  }

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

  /* The public board's stand-in for a checkbox. Same width as the box it replaced, so a
     row keeps its indent and a description still lines up past it — and centred on the
     line, because a * sits high in its own em box and would otherwise ride the row. */
  .mark {
    flex: none;
    width: 18px;
    font-size: 15px;
    line-height: 1;
    text-align: center;
    color: var(--muted);
    -webkit-user-select: none;
    user-select: none;
    transform: translateY(2px);
  }
  .mark.done { color: var(--accent); }
  .step .mark { width: 16px; font-size: 13px; }

  li input[type="checkbox"] {
    flex: none;
    width: 18px;
    height: 18px;
    padding: 0;
    accent-color: var(--accent);
    cursor: pointer;
  }

  .row { display: flex; align-items: center; gap: 9px; min-height: 22px; }

  .text {
    flex: 1;
    min-width: 0;
    word-break: break-word;
    font-size: 15px;
    text-align: left;
    color: inherit;
    background: none;
    border: 0;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
  }
  /* finished work fades back, but stays readable on hover */
  /* a done step inside an already-faded task isn't faded twice */
  li.done, li:not(.done) .step.done { opacity: .5; transition: opacity .15s; }
  li.done:hover, li:not(.done) .step.done:hover { opacity: 1; }
  li.done .text { color: var(--muted); text-decoration: line-through; }

  /* keyword highlighting — the script picks the tone, this picks the colour.
     Light theme gets darkened shades and dark theme gets lightened ones, so every
     subject clears 4.5:1 against its own background instead of only looking right
     in one of them. Yellow and light blue are the two that force this. */
  .kw { font-weight: 700; }
  .kw-res  { color: #18794e; }   /* green  */
  .kw-leap { color: #5746af; }   /* purple */
  .kw-p6   { color: #bd4b00; }   /* orange */
  .kw-math { color: #0a68a8; }   /* light blue */
  .kw-fil  { color: #946800; }   /* yellow */

  /* CS5 is the one that gets a face of its own: monospace, set in the plain text
     colour rather than a hue — near-black on light, near-white on dark, which is the
     most readable either theme has to offer. */
  .kw-cs {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: .94em;
    letter-spacing: -.01em;
    color: var(--text);
    padding: 1px 4px;
    margin: 0 -1px;
    border-radius: 5px;
    background: rgba(20, 40, 30, .07);
  }

  @media (prefers-color-scheme: dark) {
    .kw-cs   { background: rgba(255, 255, 255, .1); }
    .kw-res  { color: #4cc38a; }
    .kw-leap { color: #a48fff; }
    .kw-p6   { color: #ff9d52; }
    .kw-math { color: #7cd4fd; }
    .kw-fil  { color: #f5d90a; }
  }

  /* editing must not change the row's height */
  .edit {
    flex: 1;
    min-width: 0;
    padding: 1px 6px;
    margin: -1px -6px;
    font-size: 15px;
    line-height: inherit;
    border-radius: 6px;
    background: var(--bg);
  }

  .progress {
    flex: none;
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
  }
  .progress.full { color: #30a46c; }

  /* slim "add a step" affordance, sits beside the date */
  .plus {
    flex: none;
    width: 20px;
    height: 20px;
    font-size: 15px;
    line-height: 1;
    color: var(--muted);
    background: none;
    border: 0;
    padding: 0;
    border-radius: 5px;
    cursor: pointer;
    opacity: 0;
    transition: opacity .15s, color .15s;
  }
  li:hover .plus, .plus:focus { opacity: 1; }
  .plus:hover { color: var(--text); }
  @media (hover: none) { .plus { opacity: 1; } }

  .steps {
    margin: 8px 0 2px 8px;
    padding-left: 14px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    border-left: 2px solid var(--line);
  }

  .step {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 4px 0;
    font-size: 13.5px;
  }
  .step input[type="checkbox"] { width: 15px; height: 15px; }
  .stepText {
    flex: 1;
    min-width: 0;
    text-align: left;
    font: inherit;
    color: inherit;
    background: none;
    border: 0;
    padding: 0;
    word-break: break-word;
    cursor: pointer;
  }
  .step .edit { font-size: 13.5px; }
  .step.done .stepText { color: var(--muted); text-decoration: line-through; }
  .step .del { width: 22px; height: 22px; font-size: 14px; }
  .step:hover .del { opacity: 1; }

  .stepAdd {
    margin-top: 6px;
    padding: 6px 9px;
    width: 100%;
    font-size: 13px;
    border-radius: 7px;
    background: var(--bg);
  }

  .due {
    flex: none;
    padding: 3px 8px;
    font-size: 11px;
    font-weight: 500;
    white-space: nowrap;
    color: var(--muted);
    background: var(--bg);
    border-radius: 6px;
  }
  .due.red    { color: #e5484d; }
  .due.orange { color: #f76b15; }
  .due.yellow { color: #b58900; }   /* darkened so it reads on the light background */
  @media (prefers-color-scheme: dark) { .due.yellow { color: #f5d90a; } }

  .hint {
    font-size: 12px;
    color: var(--muted);
    margin: 22px 0 0 2px;
  }

  h2 .count { font-weight: 500; color: var(--line); }
  @media (prefers-color-scheme: dark) { h2 .count { color: #3d3d38; } }

  /* name · url · Add · New group, all on one line — the inputs give, not the buttons */
  #panel-links form.links { max-width: 640px; margin: 14px 0 0; flex-wrap: nowrap; }
  #panel-links form.links input { min-width: 0; }
  #panel-links form.links button { flex: none; white-space: nowrap; padding: 0 14px; }
  @media (max-width: 560px) {
    #panel-links form.links { flex-wrap: wrap; }
  }
  .hint code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    padding: 1px 5px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 5px;
  }

  .del {
    flex: none;
    width: 26px;
    height: 26px;
    font-size: 17px;
    line-height: 1;
    color: var(--muted);
    background: none;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
    opacity: 0;
    transition: opacity .15s, color .15s;
  }
  li:hover .del, .tile:hover .del, .del:focus { opacity: 1; }
  .del:hover { color: #e5484d; }

  .del.armed {
    width: auto;
    padding: 0 7px;
    font-size: 11px;
    font-weight: 600;
    color: #e5484d;
    background: rgba(229, 72, 77, .1);
    opacity: 1;
  }

  /* links — tiles are as wide as their name needs, no wider, and wrap */
  .grid {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 8px;
  }

  .tile {
    position: relative;
    display: flex;
    flex: 0 1 auto;
    align-items: center;
    gap: 10px;
    min-width: 0;
    max-width: 100%;
    padding: 12px;
    padding-right: 40px;   /* room for rename + delete, so they never sit on the text */
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 10px;
    animation: in .18s ease-out;
    transition: background .15s, border-color .15s;
  }
  .tile:hover { background: var(--hover); border-color: var(--muted); }

  .tile a {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    flex: 1;
    color: inherit;
    text-decoration: none;
  }

  /* long names truncate rather than stretching the tile across the row */
  .meta { min-width: 0; max-width: 190px; }
  .name {
    font-size: 14px;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .tile .del { position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; font-size: 15px; }
  .tile .ren { right: 24px; font-size: 12px; }
  .tile .ren:hover { color: var(--text); }
  .tile .edit { flex: 1; font-size: 14px; }

  /* link groups + drag and drop */
  .tile { cursor: grab; }
  .tile.dragging { opacity: .35; cursor: grabbing; }
  .tile a { cursor: pointer; }

  .drop { outline: 2px dashed var(--muted); outline-offset: 6px; border-radius: 12px; }

  /* groups sit side by side too, each only as wide as the links it holds */
  .groups {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 10px;
  }

  /* the loose pile and its add form sit together at the bottom */
  .links-head { margin: 30px 0 12px; }

  .group {
    flex: 0 1 auto;
    min-width: 190px;
    max-width: 100%;
    padding: 10px 12px 12px;
    border: 1px solid var(--line);
    border-radius: 12px;
    animation: in .18s ease-out;
  }
  .group.drop { outline-offset: 3px; }

  .group-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
  }
  .group-name {
    flex: 1;
    min-width: 0;
    text-align: left;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
  }
  .group-head .edit { font-size: 13px; font-weight: 600; text-transform: none; letter-spacing: 0; }
  .group-head .count { flex: none; font-size: 11px; font-weight: 500; color: var(--muted); }
  .group-head .del { opacity: 0; }
  .group:hover .del, .group-head .del:focus { opacity: 1; }

  .caret {
    flex: none;
    width: 20px;
    height: 20px;
    font-size: 10px;
    line-height: 1;
    color: var(--muted);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    transition: transform .15s;
  }
  /* only the toggle itself turns — the play/copy/stack buttons share its class */
  .group.closed .group-head > .caret:first-child { transform: rotate(-90deg); }
  .caret:hover { color: var(--text); }
  .caret.play { color: #059669; font-size: 11px; }
  .caret.play:hover { color: #047857; background: rgba(5, 150, 105, .12); border-radius: 6px; }

  button.ghost {
    color: var(--text);
    background: none;
    border: 1px solid var(--line);
  }
  button.ghost:hover { background: var(--hover); opacity: 1; }

  .empty {
    padding: 26px 0;
    text-align: center;
    color: var(--muted);
    font-size: 14px;
    background: none;
    border: none;
  }

  /* The line between Azzan's own subjects and everybody else's. Not a heading — a
     heading would imply the block above it needs one too, and it doesn't: the top of
     the list is the default and needs no announcing. This is the point where the list
     stops being about him, said as quietly as that deserves. */
  li.divider {
    display: flex;
    flex-direction: row;      /* li itself is a column; this line is not */
    align-items: center;
    gap: 10px;
    padding: 16px 0 6px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--muted);
    animation: none;
  }
  li.divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--line);
  }

  /* the two accounts' limits, above the macros themselves (the clock moved to the header) */
  .meter-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 14px 26px;
    margin-bottom: 18px;
  }

  .limits { display: flex; flex-wrap: wrap; gap: 10px; flex: 1 1 340px; }

  .acct {
    flex: 1 1 200px;
    min-width: 170px;
    padding: 10px 12px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 10px;
  }
  .acct .who {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
    font-size: 12px;
    font-weight: 500;
  }
  .acct .who .mail {
    font-weight: 400;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .bar { margin-top: 7px; }
  .bar .lab {
    display: flex;
    justify-content: space-between;
    margin-bottom: 3px;
    font-size: 11px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
  }
  .bar .track {
    height: 5px;
    background: var(--hover);
    border-radius: 99px;
    overflow: hidden;
  }
  .bar .fill {
    height: 100%;
    width: 0;
    border-radius: 99px;
    background: #30a46c;
    transition: width .3s ease-out;
  }
  .bar.warn  .fill { background: #f0820b; }
  .bar.crit  .fill { background: #e5484d; }
  .bar.crit  .lab { color: #e5484d; }

  .acct .stale { margin-top: 8px; font-size: 10px; color: var(--muted); }
  .acct.out { opacity: .55; }

  /* macros — the whole tile is a press target, with a close button in the corner */
  .tile.macro { align-items: stretch; max-width: 340px; }

  .tile.macro .press {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    width: 100%;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
    opacity: 1;
  }
  .tile.macro .press:active { transform: scale(.99); }

  .tile.macro .row { display: flex; align-items: center; gap: 8px; }

  .tile.macro .detail {
    font-size: 12px;
    line-height: 1.45;
    color: var(--muted);
    white-space: normal;
  }
  .tile.macro[aria-busy="true"] { opacity: .6; pointer-events: none; }

  /* on / off light — always says something, even "can't tell" */
  .state {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--muted);
  }
  .state::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: .55;
  }
  .state.on { color: #f0820b; }
  .state.on::before { opacity: 1; box-shadow: 0 0 0 3px rgba(240, 130, 11, .22); }

  /* the whole tile goes orange while it's running, so "on" reads at a glance */
  .tile.macro.is-on {
    background: rgba(240, 130, 11, .12);
    border-color: rgba(240, 130, 11, .55);
  }
  .tile.macro.is-on:hover {
    background: rgba(240, 130, 11, .18);
    border-color: rgba(240, 130, 11, .8);
  }
  /* one side missing — same orange, but hollow, so it doesn't read as fully up */
  .tile.macro.is-half { background: rgba(240, 130, 11, .05); }

  .tile.macro .kill {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 24px;
    height: 24px;
    padding: 0;
    font-size: 15px;
    line-height: 1;
    color: var(--muted);
    background: none;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    opacity: 0;
    transition: opacity .15s, background .15s, color .15s;
  }
  .tile.macro:hover .kill, .tile.macro .kill:focus-visible { opacity: 1; }
  .tile.macro .kill:hover { color: #e5484d; background: rgba(229, 72, 77, .1); }
  .tile.macro .kill[hidden] { display: none; }

  /* armed = the polite close didn't take, so this press forces it */
  .tile.macro .kill.armed {
    opacity: 1;
    color: #e5484d;
    background: rgba(229, 72, 77, .12);
    box-shadow: inset 0 0 0 1px rgba(229, 72, 77, .5);
  }

  /* prompts — filed under projects, pressed into a stack, copied as one block */
  .prompts { margin-top: 38px; padding-top: 26px; border-top: 1px solid var(--line); }

  /* the stack tray keeps its height whether or not anything is in it, so pressing
     a prompt doesn't shove the whole list down the page */
  .stack {
    min-height: 62px;
    margin-bottom: 22px;
    padding: 10px 12px;
    border: 1px dashed var(--line);
    border-radius: 12px;
  }
  .stack.has { border-style: solid; background: var(--hover); }
  .stack .empty { padding: 8px 0; font-size: 13px; }

  .stack-line { display: flex; flex-wrap: wrap; gap: 6px; }

  .chip {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 5px 6px 5px 8px;
    font-size: 13px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 999px;
    cursor: grab;
  }
  .chip.dragging { opacity: .35; cursor: grabbing; }
  .chip.over { border-color: var(--accent); }
  .chip .ord {
    font-size: 10px;
    font-weight: 600;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
  }
  .chip .nm { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .chip .x {
    width: 18px;
    height: 18px;
    font-size: 13px;
    line-height: 1;
    color: var(--muted);
    background: none;
    border: 0;
    border-radius: 50%;
    cursor: pointer;
  }
  .chip .x:hover { color: #e5484d; background: rgba(229, 72, 77, .12); }

  .stack-acts { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
  .stack-acts .add { padding: 8px 16px; font-size: 14px; }
  .stack-acts .size { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

  /* a prompt tile is mostly a press target, like a macro, but small enough to grid */
  .tile.prompt { align-items: stretch; max-width: 260px; }
  .tile.prompt .press {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    width: 100%;
    padding: 0;
    padding-right: 52px;      /* room for the corner buttons */
    font: inherit;
    color: inherit;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
  }
  /* with no name to lead with, the prompt's own words are the tile */
  .tile.prompt .preview {
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text);
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .tile.prompt.stacked .preview { color: var(--text); }
  /* in the stack = same orange the macros use for "on" */
  .tile.prompt.stacked { background: rgba(240, 130, 11, .12); border-color: rgba(240, 130, 11, .55); }
  .tile.prompt.stacked:hover { background: rgba(240, 130, 11, .18); }
  .tile.prompt .solo { right: 44px; font-size: 11px; }
  .tile.prompt .solo:hover { color: var(--text); }
  .tile.prompt.editing { border-color: var(--accent); }

  .prompt-form { align-items: flex-start; }
  .prompt-form textarea {
    flex: 1 1 100%;
    min-height: 84px;
    padding: 12px 14px;
    font: inherit;
    font-size: 14px;
    line-height: 1.5;
    resize: vertical;
    color: var(--text);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 10px;
    outline: none;
    transition: border-color .15s;
  }
  .prompt-form textarea::placeholder { color: var(--muted); }
  .prompt-form textarea:focus { border-color: var(--accent); }

  .prompt-form select {
    min-width: 0;
    padding: 12px 14px;
    font: inherit;
    font-size: 15px;
    color: var(--text);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 10px;
    outline: none;
    cursor: pointer;
  }

  /* ---------- the house in the background ---------- */
  /* The same typed house that used to sit in the header, standing in the grass
     instead: brown, faint, and behind everything — scenery rather than a mark. The
     grass is drawn after it, so the blades come up in front of its base. */
  .house {
    position: fixed;
    z-index: 0;                     /* first in the body, so every other layer paints over it */
    right: 3vw;
    bottom: 4px;                    /* stood on the ground, with the grass in front of it */
    margin: 0;
    white-space: pre;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-weight: 700;
    font-size: 19px;
    line-height: 1;
    letter-spacing: 0;
    opacity: .22;
    pointer-events: none;
    background: linear-gradient(180deg, #c08b5c 0%, #7a4a24 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
  @media (prefers-color-scheme: dark) {
    .house {
      opacity: .26;
      background: linear-gradient(180deg, #b07f52 0%, #5f3a1d 100%);
      -webkit-background-clip: text;
      background-clip: text;
    }
  }
  @media (max-width: 700px) { .house { display: none; } }

  /* ---------- grass, blowing along the bottom of the page ---------- */
  /* purely decorative: fixed behind everything, faded out towards the top */
  .grass {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: 170px;
    z-index: 0;
    pointer-events: none;
    opacity: .26;
    -webkit-mask-image: linear-gradient(to top, #000 0%, #000 30%, transparent 100%);
    mask-image: linear-gradient(to top, #000 0%, #000 30%, transparent 100%);
  }
  @media (prefers-color-scheme: dark) { .grass { opacity: .2; } }

  /* every blade leans a little, then rocks either side of that lean, on its own
     clock — the staggered delays are what make it read as wind rather than a wave.
     The blades are typed characters, to match the clouds. */
  .grass .blade {
    position: absolute;
    left: var(--x);
    bottom: -.2em;                  /* the glyph's descender space, hidden below the fold */
    margin: 0;
    white-space: pre;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--h);
    line-height: 1;
    color: var(--tone);
    transform-origin: 50% 100%;
    animation: sway var(--dur, 4s) ease-in-out var(--delay, 0s) infinite alternate;
  }
  @keyframes sway {
    from {
      transform: translateX(calc(var(--drift) * -1)) rotate(calc(var(--lean) - var(--amp)))
                 skewX(calc(var(--amp) * -.5));
    }
    to {
      transform: translateX(var(--drift)) rotate(calc(var(--lean) + var(--amp)))
                 skewX(calc(var(--amp) * .5));
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .grass .blade { animation: none; }
  }

  .wrap { position: relative; z-index: 1; }

  /* ---------- the box fitter ---------- */
  /* groups and projects sit on a shared column grid and each one claims the number
     of columns its contents actually need; `dense` then back-fills the gaps, so a
     1-column box slides up beside a 2-column one instead of leaving a hole */
  .groups {
    display: grid;
    grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));
    grid-auto-flow: row dense;
    align-items: start;
    gap: 10px;
  }
  .groups .group { min-width: 0; max-width: none; }

  /* a closed box collapses to its header and gives its columns back */
  .group-body { overflow: hidden; animation: unfold .18s ease-out; }
  @keyframes unfold {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
  }
  .group.closed { padding-bottom: 10px; }
  .group.closed .group-head { margin-bottom: 0; }

  /* ---------- frosted boxes ---------- */
  /* The cards go part-way transparent and blur whatever is behind them — the grass,
     mostly — so the page has depth without the text losing its footing. The tint is
     kept heavy enough (>55%) that body copy still sits on a near-solid ground. */
  :root {
    --glass: rgba(255, 255, 255, .68);
    --glass-hover: rgba(255, 255, 255, .84);
    --edge: rgba(20, 40, 30, .1);
    --frost: saturate(180%) blur(14px);
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --glass: rgba(28, 28, 26, .62);
      --glass-hover: rgba(42, 42, 38, .76);
      --edge: rgba(255, 255, 255, .09);
    }
  }

  /* frosted glass needs something behind it to be worth blurring — the grass only
     reaches the bottom of the page, so a couple of soft green washes sit behind the
     whole thing. Faint enough to read as paper, not as a colour scheme. */
  body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
      radial-gradient(60% 45% at 12% 8%,  rgba(74, 222, 128, .16), transparent 70%),
      radial-gradient(50% 40% at 88% 22%, rgba(5, 150, 105, .13),  transparent 70%),
      radial-gradient(70% 50% at 50% 96%, rgba(52, 211, 153, .14), transparent 70%);
  }
  @media (prefers-color-scheme: dark) {
    body::before {
      background:
        radial-gradient(60% 45% at 12% 8%,  rgba(74, 222, 128, .1), transparent 70%),
        radial-gradient(50% 40% at 88% 22%, rgba(5, 150, 105, .12), transparent 70%),
        radial-gradient(70% 50% at 50% 96%, rgba(52, 211, 153, .1), transparent 70%);
    }
  }

  li,
  .tile,
  .group,
  .acct,
  .stack.has,
  input,
  .prompt-form textarea,
  .prompt-form select {
    background: var(--glass);
    -webkit-backdrop-filter: var(--frost);
    backdrop-filter: var(--frost);
  }

  li, .tile, .group, .acct, .stack.has {
    border-color: var(--edge);
    box-shadow: 0 1px 2px rgba(16, 24, 20, .04), 0 10px 24px -18px rgba(16, 24, 20, .35);
  }
  .tile:hover { background: var(--glass-hover); }

  /* the empty-state rows and the open stack tray have no card of their own to frost */
  li.empty { background: none; -webkit-backdrop-filter: none; backdrop-filter: none; }

  /* a group already frosts itself, so the tiles inside it would blur a blur —
     lighten those instead, which keeps the nesting readable */
  .group .tile { background: var(--card); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .group .tile:hover { background: var(--hover); }

  /* the macro tile's own colours must still win over the frost */
  .tile.macro.is-on { background: rgba(240, 130, 11, .18); }
  .tile.macro.is-on:hover { background: rgba(240, 130, 11, .26); }
  .tile.macro.is-half { background: rgba(240, 130, 11, .1); }
  .tile.prompt.stacked { background: rgba(240, 130, 11, .16); }
  .tile.prompt.stacked:hover { background: rgba(240, 130, 11, .24); }

  /* no blur available (older browsers): fall back to the solid card */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    li, .tile, .group, .acct, .stack.has, input,
    .prompt-form textarea, .prompt-form select { background: var(--card); }
  }

  /* ---------- ascii clouds ---------- */
  /* Typed clouds, not drawn ones: monospace art drifting across the top of the page,
     each on its own clock so they never line up into a parade. Faint enough to be
     weather rather than content, and always behind the frosted boxes. */
  .clouds {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 62vh;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 100%);
  }

  .cloud {
    position: absolute;
    left: 0;
    top: var(--top);
    margin: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--size);
    line-height: 1.15;
    white-space: pre;
    color: #4f7264;
    opacity: var(--fade);
    animation: drift var(--dur) linear var(--delay) infinite;
  }
  @media (prefers-color-scheme: dark) { .cloud { color: #8fb3a2; } }

  @keyframes drift {
    from { transform: translateX(-40vw); }
    to   { transform: translateX(112vw); }
  }
  /* some weather comes the other way */
  .cloud.back { animation-name: drift-back; }
  @keyframes drift-back {
    from { transform: translateX(112vw); }
    to   { transform: translateX(-40vw); }
  }
  @media (prefers-reduced-motion: reduce) {
    .cloud { animation: none; transform: translateX(var(--rest)); }
  }

  /* ---------- passwords ---------- */
  .tile.pass { flex-direction: column; align-items: stretch; gap: 3px; max-width: 300px; padding-right: 12px; }
  .tile.pass .top { display: flex; align-items: baseline; gap: 8px; }
  .tile.pass .name { flex: 1; min-width: 0; }
  .tile.pass .who {
    font-size: 12px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .tile.pass .secret {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 2px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
    color: var(--text);
    word-break: break-all;
  }
  .tile.pass .secret .val { flex: 1; min-width: 0; letter-spacing: .06em; }
  .tile.pass .acts { display: flex; gap: 2px; margin-left: auto; }
  /* the link tiles park their × in the corner; these sit in a row instead */
  .tile.pass .acts .del {
    position: static;
    opacity: .5;
    width: 24px;
    height: 24px;
    font-size: 13px;
  }
  .tile.pass:hover .acts .del { opacity: 1; }
  .pass-form input[type="password"] { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

  /* the lock screen in front of the passwords */
  .gate {
    max-width: 420px;
    margin: 4px 0 8px;
    padding: 18px 18px 16px;
    background: var(--glass);
    -webkit-backdrop-filter: var(--frost);
    backdrop-filter: var(--frost);
    border: 1px solid var(--edge);
    border-radius: 12px;
    box-shadow: 0 1px 2px rgba(16, 24, 20, .04), 0 10px 24px -18px rgba(16, 24, 20, .35);
  }
  .gate h3 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
  .gate p { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin-bottom: 12px; }
  .gate form { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }
  .gate input { flex: 1 1 100%; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  .gate .add { flex: none; }
  .gate .warn { margin: 10px 0 0; color: #bd4b00; }
  @media (prefers-color-scheme: dark) { .gate .warn { color: #ff9d52; } }

  .lock-row { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
  .lock-row .add { padding: 6px 12px; font-size: 13px; }
  .lock-row .note { font-size: 11.5px; color: var(--muted); }

  /* ── subjects ──────────────────────────────────────────────────────────────
     A graded task belongs to one subject, and every subject is either a CORE or
     an ELECTIVE — that split is the whole reason the filter row can offer "Cores"
     and "Electives" as presets rather than making you tick five boxes.

     Cores read filled, electives read outlined, and the subjects everybody takes read
     quiet. Same size, same shape: the weight is doing the work, so a row of them is
     scannable without a legend.

     The .filt chips below belong to /azzan/home?r=public alone, where a reader picks the
     one core and the one elective they are actually taking. The app has no such control:
     Azzan's answer is Physics and CS every day of the week, so his page sorts them to the
     top instead of asking. The .subj chips are shared by both pages and must not drift
     from each other. */
  .filters {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 14px;
  }

  .filt {
    padding: 5px 11px;
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    color: var(--muted);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 999px;
    cursor: pointer;
    transition: color .15s, background .15s, border-color .15s;
  }
  .filt:hover { color: var(--text); border-color: var(--muted); }
  .filt.on {
    color: var(--bg);
    background: var(--accent);
    border-color: var(--accent);
  }

  .subj {
    flex: none;
    max-width: 40%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .02em;
    border-radius: 999px;
    border: 1px solid var(--line);
    color: var(--muted);
  }
  /* TWO weights, not three.

     Core and elective are both a CHOICE — the science you were put in and the one you
     picked on top of it — and telling them apart by the chip was never worth the price:
     an outlined chip beside a filled one reads as the lesser of the two, and neither of
     them is. So they share the solid chip, and which track it is is a thing the page
     around them already says (the board's own Core and Elective rows) rather than a thing
     every row has to re-state.

     Everything everybody takes stays quiet and grey, because there was never a choice to
     show there — that is the one distinction the chip is actually carrying. */
  .subj.core,
  .subj.elective { color: var(--bg); background: var(--accent); border-color: var(--accent); }
  .subj.other { color: var(--muted); background: transparent; border-color: var(--line); }

  /* An EVENT — something that happens at a time rather than something handed in. Read
     off the title by tracker.js, which all three pages load, so the tag looks and means
     the same on the app, the public board and the admin panel.

     Yellow, because it is the one colour on this board not already spoken for by a due
     date's urgency, and because it has to be tellable apart from a subject at a glance
     while still reading as the same kind of thing. */
  .subj.event {
    color: #7a5b00;
    background: #ffe9a8;
    border-color: #f2cf6a;
  }
  @media (prefers-color-scheme: dark) {
    .subj.event { color: #f5d90a; background: rgba(245,217,10,.14); border-color: rgba(245,217,10,.4); }
  }

  /* A link pasted into a description, shown as its site rather than as 80 characters of
     id and query string. The whole link is still the href and the tooltip — only the
     reading of it is short. */
  .desc a.link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 100%;
    padding: 1px 7px;
    font-size: 12px;
    font-weight: 500;
    color: var(--text);
    text-decoration: none;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 999px;
    vertical-align: baseline;
    word-break: break-all;
  }
  .desc a.link:hover { border-color: var(--accent); color: var(--accent); }

  /* ── the badges wrap, and on a narrow screen they go under the title ──────
     All three lists: #list is the public board and the admin panel, #gradedList and
     #ownList are the app's own. One rule, so a row that fits on one screen fits on all
     of them and nobody has to remember which page a fix landed on.

     A row was one non-wrapping line, so a title, a subject, an Event tag and a due date
     fought over it and the title lost every time: it is the only flexible thing there,
     so it absorbed whatever the chips did not want and collapsed into a column one word
     wide while three chips sat comfortably beside it.

     Wrapping alone does not fix that — the title has to be told to take the whole line,
     and then the badges have nowhere to go but underneath, which is where they were
     readable all along. Two rules rather than one because a row with a * has 27px of it
     already spoken for (18px mark + the row's 9px gap), and a row with steps has none. */
  #list .row, #gradedList .row, #ownList .row { flex-wrap: wrap; row-gap: 6px; }
  #list .subj, #gradedList .subj, #ownList .subj { max-width: none; overflow: visible; text-overflow: clip; }

  @media (max-width: 640px) {
    #list li:not(.headed):not(.editing) .row .text,
    #gradedList li:not(.headed) .row .text,
    #ownList li:not(.headed) .row .text { flex: 1 1 calc(100% - 27px); }

    #list li.headed:not(.editing) .row .text,
    #gradedList li.headed .row .text,
    #ownList li.headed .row .text { flex: 1 1 100%; }

    /* The × keeps the far end of whatever line it lands on. */
    #list li:not(.editing) .row .del,
    #gradedList .row .del, #ownList .row .del { margin-left: auto; }
  }

  /* the picker that rides in the add form, and the one on a task's own row */
  select.pick {
    flex: none;
    max-width: 34%;
    padding: 0 10px;
    font: inherit;
    font-size: 13px;
    color: var(--text);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 10px;
    outline: none;
    cursor: pointer;
  }
  select.pick:focus { border-color: var(--accent); }
  .row select.pick { height: 22px; font-size: 11px; border-radius: 999px; max-width: 40%; }

  /* the subject list editor, folded away under the graded column */
  .subjects { margin: 14px 0 0; }
  .subjects .rowline { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
  .subjects .rowline input { padding: 6px 10px; font-size: 13px; border-radius: 8px; }
  .subjects .rowline select.pick { height: 30px; max-width: 120px; }
  .subjects .del { opacity: 1; }
  .subjects .foot { display: flex; gap: 8px; margin-top: 8px; }
  .subjects .foot .add { padding: 6px 12px; font-size: 13px; }

  .fold {
    padding: 0;
    font: inherit;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--muted);
    background: none;
    border: 0;
    cursor: pointer;
  }
  .fold:hover { color: var(--text); }

  /* ── public / private ──────────────────────────────────────────────────────
     Links are private by default; the eye on a tile is what puts one on the
     public board. Shown always rather than on hover, because "is this one out
     there" is the kind of thing you want to be able to read at a glance rather
     than discover by pointing at it. */
  .tile .pub {
    position: absolute;
    top: 4px;
    right: 44px;
    width: 22px;
    height: 22px;
    font-size: 12px;
    line-height: 22px;
    text-align: center;
    color: var(--muted);
    background: none;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
  }
  .tile .pub.on { color: #30a46c; }
  .tile .pub:hover { background: var(--hover); color: var(--text); }

  .banner-pub {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--muted);
    text-decoration: none;
    border-bottom: 1px solid var(--line);
    padding-bottom: 1px;
  }
  .banner-pub:hover { color: var(--text); border-color: var(--muted); }

  /* ── a heading with a control beside it ───────────────────────────────────
     "Graded" and "Copy tracker" belong on the same line: the button acts on
     exactly the list under that heading, and putting it anywhere else makes you
     work out which list it means. Used by the app, the board and the admin panel. */
  .head-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
  }
  .head-row h2 { flex: 1; min-width: 0; }
  .head-row .fold { flex: none; margin-bottom: 14px; }

  /* ── a task's description ─────────────────────────────────────────────────
     The title says WHAT, this says the rest of it — where the file is, what the
     activity actually asks for, until when. Quiet and smaller, because it is read
     second: a list of titles has to stay scannable, and a description that
     competed with them would make every row a paragraph.

     Sits under the row rather than beside it, aligned past the checkbox, so a
     wrapped sentence reads as belonging to the task above it and not to the list. */
  .desc {
    margin: 5px 0 0 27px;
    font-size: 13px;
    line-height: 1.55;
    color: var(--muted);
    white-space: pre-wrap;      /* a description written as two paragraphs stays two */
    word-break: break-word;
  }
  /* A task with steps has no checkbox, so there is nothing to align past. */
  li.headed .desc { margin-left: 0; }
  li.done .desc { text-decoration: none; }

  /* Editing one in place: same box as .edit, given room to be a paragraph. */
  textarea.edit {
    width: 100%;
    margin-top: 6px;
    padding: 8px 10px;
    font: inherit;
    font-size: 13px;
    line-height: 1.55;
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 8px;
    outline: none;
    resize: vertical;
  }
  textarea.edit:focus { border-color: var(--accent); }

  /* The ✎ that opens it. Hidden until the row is pointed at, exactly like the +
     beside it — a row full of controls is a row you have to read past. */
  .note {
    flex: none;
    width: 24px;
    height: 24px;
    font-size: 13px;
    line-height: 24px;
    text-align: center;
    color: var(--muted);
    background: none;
    border: 0;
    border-radius: 7px;
    cursor: pointer;
    opacity: 0;
    transition: opacity .15s, background .15s, color .15s;
  }
  li:hover .note, .note:focus { opacity: 1; }
  .note:hover { background: var(--hover); color: var(--text); }
  /* A row that already HAS a note keeps its pencil visible: the mark is how you can
     tell there is more to this one without pointing at every row to find out. */
  .note.has { opacity: .75; color: var(--text); }
  @media (hover: none) { .note { opacity: 1; } }

  /* ── who a row came from ──────────────────────────────────────────────────
     The board has two writers now. This chip is the only thing that says so —
     small and grey, because who typed a row matters far less than what it says,
     and it earns its space only on the screen where you can change one. */
  .from {
    flex: none;
    padding: 2px 7px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--muted);
    background: var(--bg);
    border-radius: 999px;
  }
  .from.edited { color: #bd4b00; }

  /* A row taken off the board. Still listed, because the way back is on it. */
  li.off { opacity: .45; }
  li.off:hover { opacity: .8; }
  li.off .text { text-decoration: line-through; }

  /* Undo. Sits where the × sits on every other row and does the opposite. */
  .undo {
    flex: none;
    width: 24px;
    height: 24px;
    font-size: 13px;
    line-height: 24px;
    text-align: center;
    color: var(--muted);
    background: none;
    border: 0;
    border-radius: 7px;
    cursor: pointer;
    transition: background .15s, color .15s;
  }
  .undo:hover { background: var(--hover); color: var(--text); }
