:root{
  --bg:#eef1ef;--page:#f6f8f7;--surface:#fff;--ink:#14201b;--muted:#5c6b64;--faint:#8a978f;
  --line:#dde4e0;--line-2:#e8edea;--accent:#2f6f4f;--col:#edf1ef;
  --todo:#6b7686;--prog:#2563eb;--review:#b45309;--done:#2f855a;
  --shadow:0 1px 2px rgba(20,32,27,.10),0 2px 8px rgba(20,32,27,.05);
  --mono:ui-monospace,"SF Mono","Cascadia Mono",Menlo,Consolas,monospace;
  --sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --l-purple:#7c3aed;--l-blue:#2563eb;--l-green:#2f855a;--l-amber:#b45309;--l-teal:#0d9488;--l-rose:#be123c;--l-slate:#475569;
}
*{box-sizing:border-box;}
html,body{margin:0;height:100%;}
body{background:var(--page);color:var(--ink);font:15px/1.5 var(--sans);-webkit-font-smoothing:antialiased;}

.topbar{
  display:flex;align-items:center;gap:1rem;flex-wrap:wrap;
  padding:.7rem 1.25rem;background:var(--surface);border-bottom:1px solid var(--line);
}
.brand{display:flex;align-items:center;gap:.55rem;font-weight:700;color:var(--ink);text-decoration:none;}
.brand .logo{width:26px;height:26px;border-radius:7px;display:grid;place-items:center;background:var(--accent);color:#fff;font-size:14px;font-weight:800;}
.sprintmeta{font-size:.86rem;color:var(--muted);}
.sprintmeta b{color:var(--ink);font-weight:600;}
.spacer{flex:1 1 auto;}
.nav a{color:var(--muted);text-decoration:none;font-size:.88rem;margin-left:1rem;}
.nav a:hover{color:var(--accent);}
.avatars{display:flex;}
.avatars .av{margin-left:-6px;border:2px solid var(--surface);}
.livechip{display:inline-flex;align-items:center;gap:.45rem;font-size:.78rem;color:var(--muted);
  border:1px solid var(--line);border-radius:20px;padding:.25rem .65rem;background:var(--surface);}
.livechip .dot{width:8px;height:8px;border-radius:50%;background:var(--faint);}
.livechip.on .dot{background:var(--done);animation:pulse 1.3s infinite;}
.livechip.on{color:var(--done);border-color:var(--done);}

.sprintbar{display:flex;align-items:baseline;gap:1rem;flex-wrap:wrap;padding:.75rem 1.25rem .2rem;}
.sprintbar h1{font-size:1.15rem;margin:0;letter-spacing:-.01em;}
.sprintbar .goal{color:var(--muted);font-size:.9rem;}
.sprintbar .days{margin-left:auto;font-size:.85rem;color:var(--muted);}

/* align-items:stretch (not start) so every column is as tall as the tallest one:
   .cards below flex:1 into that height, which makes the whole lane a drop target
   rather than just the strip its own cards happen to occupy. */
.board{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(270px,1fr);gap:.75rem;
  padding:.85rem 1.25rem 2rem;overflow-x:auto;align-items:stretch;min-height:60vh;}
.col{background:var(--col);border-radius:10px;display:flex;flex-direction:column;min-height:120px;}
.col-head{display:flex;align-items:center;gap:.5rem;padding:.65rem .8rem .55rem;
  font-size:.74rem;letter-spacing:.09em;text-transform:uppercase;font-weight:700;color:var(--muted);}
.col-head .bar{width:22px;height:3px;border-radius:2px;}
.col[data-cat="new"] .bar{background:var(--todo);}
.col[data-cat="indeterminate"] .bar{background:var(--prog);}
.col[data-cat="done"] .bar{background:var(--done);}
/* Ideation, Analysis and To Do are all category "new", so the category alone would
   draw the three leftmost columns identically - and telling "nobody has agreed this
   is an idea yet" from "nobody has made sense of it yet" from "ready, waiting to be
   picked up" is the point of having all three. Keyed on the slug, not the column's
   name, so a board that renames a column keeps its colour (see Status.slug). */
.col[data-slug="ideation"] .bar{background:var(--l-rose);}
.col[data-slug="analysis"] .bar{background:var(--l-purple);}
.count{margin-left:auto;background:var(--surface);border:1px solid var(--line);color:var(--muted);
  min-width:22px;text-align:center;border-radius:6px;padding:0 .35rem;font-size:.72rem;font-variant-numeric:tabular-nums;}
/* The tail a capped column is not showing (public board only). Quieter than the
   count beside it: it is a footnote about the column, not a number for it. */
.col-head .more{font-size:.68rem;font-weight:600;color:var(--faint);font-variant-numeric:tabular-nums;}
.pts-sum{font-size:.68rem;color:var(--faint);font-variant-numeric:tabular-nums;}
.cards{display:flex;flex-direction:column;gap:.5rem;padding:.15rem .5rem .7rem;flex:1;min-height:40px;}
.cards.sortable-drag{opacity:.9;}
.col.hi{outline:2px dashed var(--accent);outline-offset:-3px;}

/* Sections inside a column. Only the reviewing one has them (board.review_sections):
   In Review is a single status covering five situations that hand the work to
   different people, and the difference is recorded on the issue rather than as five
   statuses - so the column stays one column and stacks its cards by what the
   `Review State` field says.

   A section is a drop target as much as a heading, so an empty one is still drawn.
   It collapses to its heading through `.cards:empty` rather than through a class the
   render works out, and that is deliberate: Sortable moves the dragged card into
   whichever container it is over, so an empty section opens as the card arrives and
   closes again when it leaves. A class computed at render time would leave a
   one-line target nobody can hit. */
.sec{display:flex;flex-direction:column;}
.sec + .sec{border-top:1px solid var(--line);}
/* --muted, not --faint, and the column head above it made the same choice for the
   same reason (see app_shell.css on the nav counts): at 10.56px bold, --faint on
   --col is 2.66:1, which axe fails as a serious contrast violation. --muted is
   4.90:1 and clears AA for small text. A section label is quieter than a column
   label by being smaller and unbarred, not by being fainter. */
.sec-head{display:flex;align-items:center;gap:.5rem;padding:.42rem .8rem .28rem;
  font-size:.66rem;letter-spacing:.08em;text-transform:uppercase;font-weight:700;color:var(--muted);}
.sec-head .count{min-width:19px;padding:0 .3rem;font-size:.66rem;}
/* No flex:1 here, unlike a plain column's single stack: five of them sharing the
   height would each claim a fifth of the lane whether or not it held anything. */
.sec .cards{flex:0 0 auto;padding:.05rem .5rem .5rem;min-height:36px;}

/* Who is holding the cards in this section, as a rule down the side of the stack.
   Red where somebody owes a push, quiet where the wait is a machine's or a merge
   queue's and nobody is blocked, and the board's own review amber for the one
   section that is actually waiting on a reviewer. Nothing on "not answered": an
   absent answer is not a state to colour-code. */
.sec[data-review-state="checks-running"] .cards{box-shadow:inset 2px 0 0 var(--faint);}
.sec[data-review-state="checks-failed"] .cards{box-shadow:inset 2px 0 0 var(--l-rose);}
.sec[data-review-state="awaiting-review"] .cards{box-shadow:inset 2px 0 0 var(--review);}
.sec[data-review-state="changes-requested"] .cards{box-shadow:inset 2px 0 0 var(--l-rose);}
.sec[data-review-state="approved"] .cards{box-shadow:inset 2px 0 0 var(--l-teal);}
/* Last, so it wins the tie on specificity with the five rules above: a collapsed
   section is a heading and a rule, with no stack for a side-rule to run down. */
.sec .cards:empty{min-height:0;padding-top:0;padding-bottom:0;box-shadow:none;}

.card{background:var(--surface);border:1px solid var(--line-2);border-radius:9px;
  padding:.62rem .68rem .55rem;box-shadow:var(--shadow);cursor:grab;user-select:none;border-left:3px solid var(--l-slate);}
.card:active{cursor:grabbing;}
.card.sortable-ghost{opacity:.35;}
.card.moved{animation:flash 1.6s ease;}
.card.done{border-left-color:var(--done);}
.card .title{font-size:.86rem;line-height:1.35;margin:0 0 .5rem;color:var(--ink);}
.card .labels{display:flex;flex-wrap:wrap;gap:.28rem;margin-bottom:.5rem;}
.pill{font-size:.66rem;font-weight:600;padding:.06rem .4rem;border-radius:4px;border:1px solid;background:transparent;
  white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis;
  /* the pill is a flex item in .labels; without min-width:0 a long unbroken
     label keeps its full min-content width and the ellipsis never fires. */
  min-width:0;}
.foot{display:flex;align-items:center;gap:.4rem;}
.key{font-family:var(--mono);font-size:.72rem;font-weight:600;color:var(--muted);letter-spacing:.02em;}
.card.done .key{text-decoration:line-through;}
.check{color:var(--done);font-size:.8rem;}
.pts{margin-left:auto;background:var(--col);border:1px solid var(--line);min-width:18px;height:18px;border-radius:4px;
  display:grid;place-items:center;font-size:.7rem;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums;}
.prio{color:var(--muted);font-size:.9rem;line-height:1;}

/* work-type tiles: one small coloured square per issue type, shared by the board
   cards and the timeline rows so a Sub-task looks the same wherever it appears. */
.ti{width:14px;height:14px;flex:none;border-radius:3px;display:inline-grid;place-items:center;
  font-size:9px;line-height:1;color:#fff;}
.ti-epic{background:var(--l-purple);}
.ti-story{background:var(--l-green);}
.ti-task{background:var(--l-blue);}
.ti-sub{background:#4f9bea;}
.ti-bug{background:var(--l-rose);}

/* a sub-task card wears its parent above the summary (and a blue spine), so the
   column reads as "this piece, of that work" rather than a loose fragment */
.card.child{border-left-color:var(--l-blue);}
.cardparent{display:flex;align-items:center;gap:.35rem;margin-bottom:.45rem;padding-bottom:.35rem;
  border-bottom:1px solid var(--line-2);font-size:.72rem;color:var(--muted);cursor:pointer;min-width:0;}
.cardparent .k{font-family:var(--mono);font-weight:600;flex:none;}
.cardparent .s{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cardparent:hover{color:var(--accent);}
.kids{font-size:.68rem;font-weight:600;color:var(--muted);border:1px solid var(--line);
  border-radius:4px;padding:0 .28rem;font-variant-numeric:tabular-nums;}
.av{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;font-size:.64rem;font-weight:700;color:#fff;flex:none;}

.toast{position:fixed;left:50%;bottom:22px;transform:translateX(-50%) translateY(18px);
  background:var(--ink);color:var(--page);padding:.6rem 1rem;border-radius:9px;font-size:.83rem;
  box-shadow:0 8px 30px rgba(0,0,0,.3);opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;z-index:50;max-width:88vw;}
.toast code{background:rgba(255,255,255,.14);border-radius:4px;padding:.05rem .3rem;font-family:var(--mono);}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast.err{background:#7f1d1d;}

/* --- the public board (signed out) ------------------------------------------
   Same board, read-only. The cursor is the honest part: .card offers a grab
   handle, and offering one where nothing can be dragged is a promise the page
   cannot keep, so it goes back to a plain pointer. */
body.public .card{cursor:default;}
body.public .card:active{cursor:default;}
.nav a.cta{background:var(--accent);color:#fff;padding:.34rem .7rem;border-radius:7px;font-weight:600;}
.nav a.cta:hover{color:#fff;filter:brightness(1.08);}
.publicnote{margin:0;padding:.7rem 1.25rem;background:var(--surface);border-bottom:1px solid var(--line);
  font-size:.85rem;color:var(--muted);}
.publicnote a{color:var(--accent);font-weight:600;}

@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(47,133,90,.5);}70%{box-shadow:0 0 0 7px rgba(47,133,90,0);}100%{box-shadow:0 0 0 0 rgba(47,133,90,0);}}
@keyframes flash{0%{box-shadow:0 0 0 3px var(--accent);}100%{box-shadow:var(--shadow);}}
@media (prefers-reduced-motion:reduce){.card.moved{animation:none;}.livechip.on .dot{animation:none;}}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}

/* --- published spaces (templates/public_space.html) ----------------------- */
/* The page reuses the shell's own class names so board.css and app_shell.css
   style it as they style the real product. Only the few things a signed-out page
   has that the shell does not are defined here: the top-bar replacements, and
   the closing of the gap the absent sidebar leaves. */
.publicchip {
  margin-left: .75rem; padding: .2rem .55rem; border-radius: 999px;
  background: #fef3c7; color: #92400e; font-size: .72rem; font-weight: 600;
  white-space: nowrap;
}
.publicnav { display: flex; align-items: center; gap: .75rem; margin-left: auto; }
.publicnav a { color: #475569; text-decoration: none; font-size: .875rem; }
.publicnav a.cta {
  background: #2563eb; color: #fff; padding: .4rem .8rem; border-radius: 6px;
}
/* No sidebar on a public page - it lists the spaces an account can reach, and
   there is no account. Nothing to reclaim: app_shell.css lays .shell out as a
   flex row and .main is `flex:1`, so the absent sidebar costs no width. An
   earlier `margin-left:0;width:100%` here was fighting a rule that does not
   exist and broke the row instead. */
/* Backlog / timeline / releases rows. The shell draws these inside a table it
   builds for drag, selection and inline edit - none of which exists here - so
   the row is a flex line that borrows the shell's pills and tiles. */
.bl-group { margin: 0 0 1.5rem; }
.bl-head { display: flex; align-items: baseline; gap: .6rem; padding: .6rem 1rem .3rem; }
.bl-head h3 { margin: 0; font-size: .95rem; }
/* What a published Backlog or Funnel group is, beside its heading. */
.bl-head .bl-note { font-size: .75rem; color: var(--muted); }
.bl-rows { display: flex; flex-direction: column; }
.bl-row {
  display: flex; align-items: center; gap: .55rem; padding: .4rem 1rem;
  border-bottom: 1px solid #f1f5f9; cursor: pointer; font-size: .875rem;
}
.bl-row:hover { background: #f8fafc; }
/* A row that opens nothing (the release notes at /releases, which publish no
   issue detail - see `standalone` in public_space.js). The click handler is
   withheld there; this is so the row stops inviting one. */
.bl-row.flat { cursor: default; }
.bl-row.flat:hover { background: none; }
.bl-row .key { color: #64748b; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bl-row .sum { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pub-when { color: var(--faint); font-size: .8rem; white-space: nowrap; }
/* Activity and members reuse .act-* / .mem-* from app_shell.css; these are the
   two containers the shell builds inline and this page needs by name. */
.act-sec, .mem-wrap { margin: 0 0 1.5rem; }
.act-head, .mem-head { display: flex; align-items: baseline; gap: .5rem; padding: .6rem 1rem .3rem; font-weight: 600; }
.act-row { display: flex; align-items: flex-start; gap: .6rem; padding: .4rem 1rem; border-bottom: 1px solid #f1f5f9; }
.act-body { flex: 1; min-width: 0; font-size: .875rem; }
.act-ex { color: #64748b; }
.act-person { display: flex; align-items: flex-start; gap: .6rem; padding: .5rem 1rem; }
/* The issue pane. The shell's is always in the DOM and slides; here it is empty
   until something is opened, so it is hidden rather than translated off-screen. */
.pane:not(.open) { display: none; }
.pane.open { display: block; }
.pane-head { display: flex; align-items: center; justify-content: space-between; }
.pane-key { color: #64748b; font-size: .8rem; }
.pane-title { margin: .3rem 0 .6rem; font-size: 1.1rem; }
.pane-meta { display: flex; align-items: center; gap: .5rem; margin-bottom: .8rem; }
.pane-comment { border-top: 1px solid #f1f5f9; padding-top: .7rem; margin-top: .7rem; }
/* The published timeline is the shell's own renderer (static/js/views/timeline_view.js)
   styled by timeline.css, so it needs nothing here. An earlier version of this
   page drew its own lanes and defined .tl-sprint / .tl-rel / .tl-row .tl-label
   to style them - those are real classes the shared renderer uses, and the
   overrides would have clobbered it. */
