:root{
  --bg:#f6f7f9; --surface:#ffffff; --surface-2:#f2f4f7;
  --text:#16191d; --muted:#69707d; --border:#e2e6eb;
  --accent:#2563eb; --accent-fg:#ffffff; --accent-soft:#eaf0fe;
  --ok:#0f7b3e; --ok-soft:#e6f5ec;
  --warn:#8a6100; --warn-soft:#fdf3e0;
  --err:#c02626; --err-soft:#fdeceb;
  --shadow:0 1px 2px rgba(16,24,40,.06),0 4px 12px rgba(16,24,40,.05);
}
[data-theme="dark"]{
  --bg:#0e1116; --surface:#161a21; --surface-2:#1d222b;
  --text:#e6e9ee; --muted:#98a1b0; --border:#272d38;
  --accent:#5b93ff; --accent-fg:#0b0f16; --accent-soft:#17233d;
  --ok:#5ed49a; --ok-soft:#12291f;
  --warn:#e3b341; --warn-soft:#2a2415;
  --err:#ff8079; --err-soft:#2e1817;
  --shadow:0 1px 2px rgba(0,0,0,.4),0 4px 14px rgba(0,0,0,.3);
}
*{box-sizing:border-box}
body{
  margin:0; background:var(--bg); color:var(--text);
  font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
  transition:background .15s ease,color .15s ease;
}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.86em}

/* header */
header{
  display:flex;align-items:center;gap:1.25rem;
  padding:.85rem 1.5rem;background:var(--surface);
  border-bottom:1px solid var(--border);position:sticky;top:0;z-index:10;
}
.brand{display:flex;align-items:center;gap:.55rem;font-weight:650;letter-spacing:-.01em}
.brand svg{display:block}
nav{display:flex;gap:.35rem;margin-left:.5rem}
nav a{
  padding:.35rem .7rem;border-radius:7px;color:var(--muted);font-weight:500;font-size:.92rem;
}
nav a:hover{background:var(--surface-2);color:var(--text);text-decoration:none}
nav a.on{background:var(--accent-soft);color:var(--accent)}
.spacer{flex:1}
.who{color:var(--muted);font-size:.88rem}

/* buttons */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.4rem;
  padding:.5rem .9rem;border-radius:8px;border:1px solid var(--border);
  background:var(--surface);color:var(--text);font:inherit;font-size:.9rem;
  font-weight:500;cursor:pointer;transition:background .12s ease,border-color .12s ease;
}
.btn:hover{background:var(--surface-2)}
.btn-primary{background:var(--accent);border-color:var(--accent);color:var(--accent-fg);width:100%;padding:.65rem}
.btn-primary:hover{filter:brightness(1.07);background:var(--accent)}
/* line-height:0 is right for a button whose whole content is an <svg>, and
   wrong for one containing text: the text box collapses and the label sits
   off-centre. Row buttons carrying words use .btn-sm, defined with the rest of
   the table styles below. */
.btn-icon{padding:.42rem .5rem;line-height:0}

/* layout */
main{max-width:1180px;margin:0 auto;padding:1.75rem 1.5rem 4rem}
h1{font-size:1.3rem;margin:0 0 .25rem;letter-spacing:-.02em}
.sub{color:var(--muted);font-size:.9rem;margin:0 0 1.5rem}

.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.9rem;margin-bottom:1.75rem}
.card{background:var(--surface);border:1px solid var(--border);border-radius:11px;padding:1rem 1.1rem;box-shadow:var(--shadow)}
.card .n{font-size:1.8rem;font-weight:640;letter-spacing:-.03em;line-height:1.15}
.card .l{color:var(--muted);font-size:.8rem;text-transform:uppercase;letter-spacing:.05em;margin-top:.15rem}
.card.ok .n{color:var(--ok)} .card.err .n{color:var(--err)} .card.warn .n{color:var(--warn)}
/* Figures INSIDE a panel. Not .card: a card is surface on the page, and reusing
   it here nests the same background, border and shadow inside themselves. The
   panel already supplies all three, so these are bare columns and the panel
   body supplies the padding that keeps them off its edge. */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1.2rem}
.stats .n{font-size:1.6rem;font-weight:640;letter-spacing:-.03em;line-height:1.15}
.stats .l{color:var(--muted);font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;margin-top:.15rem}
/* .sub carries a page-level bottom margin that leaves a gap below the last line
   inside a panel. Same look, no trailing space. */
.note{color:var(--muted);font-size:.9rem;margin:1.1rem 0 0}
.muted{color:var(--muted)}
/* The lifecycle editor's rungs. table-layout:fixed so the two input columns
   stay the same width down the table — with auto layout each row sized to its
   own content and the inputs stepped in and out as the branch names varied. */
.rungs{width:100%;border-collapse:collapse;table-layout:fixed}
.rungs th{text-align:left;font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;
  color:var(--muted);font-weight:500;padding:0 .7rem .55rem 0;border-bottom:1px solid var(--border)}
.rungs td{padding:.3rem .7rem .3rem 0;vertical-align:middle}
.rungs th:last-child,.rungs td:last-child{padding-right:0}
.rungs input{width:100%}
/* The position column is a label, not data: narrow, and out of the way. */
.rungs th.n,.rungs td.n{width:2.25rem;color:var(--muted);font-size:.8rem;
  font-variant-numeric:tabular-nums;padding-right:.5rem}
.rungs tr.fixed td{padding-top:.55rem}
.rungs tr.fixed .muted{font-size:.86rem}
/* The spare rows exist to be filled in. Dimming the marker says they are an
   invitation rather than a rung that has lost its name. */
.rungs tr.spare td.n{opacity:.45}

.panel{background:var(--surface);border:1px solid var(--border);border-radius:11px;box-shadow:var(--shadow);overflow:hidden}
.panel-head{display:flex;align-items:center;gap:.6rem;padding:.8rem 1.1rem;border-bottom:1px solid var(--border)}
.panel-head h2{font-size:.95rem;margin:0;font-weight:600}
.filters{display:flex;gap:.3rem;margin-left:auto;align-items:center}
/* Search box in a panel header. Sized to the chips beside it rather than to
   the full-width inputs in a form, which would dominate the heading. */
.find{width:13rem;padding:.3rem .6rem;border-radius:7px;border:1px solid var(--border);
  background:var(--bg);color:var(--text);font:inherit;font-size:.83rem}
.find:focus{outline:none;border-color:var(--accent)}
.find::-webkit-search-cancel-button{cursor:pointer}
.filters a{padding:.25rem .6rem;border-radius:6px;font-size:.83rem;color:var(--muted)}
.filters a.on{background:var(--accent-soft);color:var(--accent)}
.filters a:hover{text-decoration:none;background:var(--surface-2)}

/* A full page is 200 rows, which is several screens. Bounding the height makes
   the table its own scrolling region, which is what lets the header below stay
   pinned — a column of bare timestamps and tags with the headings scrolled off
   is unreadable. Short tables are unaffected: max-height only bites once the
   content exceeds it. */
.scroll{overflow:auto;max-height:72vh}
table{border-collapse:collapse;width:100%;font-size:.88rem}
/* Every column holds a single short value except one, so nowrap is the default
   and .wrap is the exception. The alternative — letting each cell wrap — gives
   rows of three different heights and a ragged left edge down the table. */
th,td{text-align:left;padding:.55rem .7rem;border-bottom:1px solid var(--border);
  vertical-align:middle;white-space:nowrap}
/* The outer columns are padded to 1.1rem so the first cell lines up with the
   panel heading above it and the last stops level with the panel's right edge.
   At .7rem the table looked inset by a few pixels against everything else. */
th:first-child,td:first-child{padding-left:1.1rem}
th:last-child,td:last-child{padding-right:1.1rem}
/* position:sticky needs an opaque background of its own — the rows scroll
   UNDER it, and a transparent header shows them through. box-shadow draws the
   bottom edge because a sticky element's own border does not travel with it. */
th{color:var(--muted);font-weight:600;font-size:.76rem;text-transform:uppercase;letter-spacing:.05em;
  background:var(--surface-2);position:sticky;top:0;z-index:1;
  box-shadow:inset 0 -1px 0 var(--border)}
tr:last-child td{border-bottom:none}
/* Striping carries the eye across a wide row. Kept very close to the surface
   colour: the job is to bind a row together, not to draw attention to every
   second one. Hover still wins, so the row under the pointer is unambiguous. */
tbody tr:nth-child(even){background:color-mix(in srgb,var(--surface-2) 55%,transparent)}
tbody tr:hover{background:var(--surface-2)}
/* Ids and clock times only line up as columns if every digit is the same
   width; proportional digits make a stack of timestamps look bent. */
.mono,.num{font-variant-numeric:tabular-nums}
.num{text-align:right}
/* The one prose column. width:100% makes it absorb the leftover space so the
   fixed-shape columns sit at their natural width instead of being stretched
   apart by the browser's default table algorithm. */
td.wrap{white-space:normal;width:100%;min-width:18rem;color:var(--muted);line-height:1.5}
/* Action column: as narrow as its button, hard against the right edge. */
th.col-act,td.col-act{width:1px;text-align:right}
/* Secondary detail — the event kind, a first-seen date, a dash standing in for
   a value there isn't one of. Present, but not competing with the branch name
   and the result, which are what the eye is actually scanning for. */
.soft{color:var(--muted)}
.empty{padding:2.5rem;text-align:center;color:var(--muted)}
/* The mirror of .panel-head: paging controls and footnotes sit on the same
   1.1rem margin as the heading above and the first column between them. */
.panel-foot{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;
  padding:.7rem 1.1rem;border-top:1px solid var(--border);
  background:var(--surface);color:var(--muted);font-size:.84rem}

.tag{display:inline-block;padding:.1rem .5rem;border-radius:20px;font-size:.75rem;font-weight:600;white-space:nowrap}
.t-github{background:var(--accent-soft);color:var(--accent)}
.t-local{background:var(--warn-soft);color:var(--warn)}
.s-done{background:var(--ok-soft);color:var(--ok)}
.s-failed{background:var(--err-soft);color:var(--err)}
.s-skipped{background:var(--warn-soft);color:var(--warn)}
.s-pending,.s-running{background:var(--surface-2);color:var(--muted)}

/* login */
.login-wrap{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1.5rem}
.login{width:100%;max-width:370px}
.login .logo{display:flex;align-items:center;justify-content:center;gap:.6rem;margin-bottom:.4rem;font-size:1.35rem;font-weight:650;letter-spacing:-.02em}
.login .tag-line{text-align:center;color:var(--muted);font-size:.88rem;margin-bottom:1.6rem}
.login form{background:var(--surface);border:1px solid var(--border);border-radius:13px;padding:1.5rem;box-shadow:var(--shadow)}
label{display:block;font-size:.83rem;font-weight:600;margin-bottom:.35rem}
input[type=text],input[type=password]{
  width:100%;padding:.6rem .7rem;margin-bottom:1rem;border-radius:8px;
  border:1px solid var(--border);background:var(--bg);color:var(--text);font:inherit;font-size:.93rem;
}
input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.error{background:var(--err-soft);color:var(--err);border:1px solid var(--err);
  padding:.55rem .7rem;border-radius:8px;font-size:.86rem;margin-bottom:1rem}
.login-foot{text-align:center;margin-top:1rem;color:var(--muted);font-size:.8rem}
.theme-line{display:flex;justify-content:center;margin-top:1.1rem}

/* forms */
.panel-body{padding:1.25rem}
.grid2{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:0 1.4rem}
.grid3{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:0 1.4rem}
/* A row of filters is the last thing in its panel, so the per-field bottom
   margin that separates stacked form fields is dead space here. */
.filter-row .field{margin-bottom:0}
.field{margin-bottom:1.35rem}
.field label{margin-bottom:.4rem}
/* Spacing here is deliberate: the hint belongs to the control above it, so the
   gap under the input is small and the gap under the hint is large. Reverse
   that and the hint reads as a caption for the NEXT field. */
.field .hint{color:var(--muted);font-size:.79rem;line-height:1.55;margin:.45rem 0 0;max-width:46rem}
input[type=email],textarea,select{
  width:100%;padding:.6rem .7rem;border-radius:8px;
  border:1px solid var(--border);background:var(--bg);color:var(--text);font:inherit;font-size:.93rem;
}
textarea{min-height:5rem;resize:vertical;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.85rem;line-height:1.6}
/* display:block on the textarea removes the inline descender gap that would
   otherwise make its hint sit further away than every other field's. */
.field input[type=text],.field input[type=email],.field textarea{margin-bottom:0;display:block}
::placeholder{color:var(--muted);opacity:.62}
/* The working-days row. Seven toggles that have to read as one control rather
   than as seven unrelated checkboxes, so they share a row and the selected ones
   are filled in. */
.daybar{display:flex;flex-wrap:wrap;gap:.4rem}
.daypick{display:inline-flex;align-items:center;gap:.4rem;margin:0;font-weight:500;
  padding:.4rem .7rem;border:1px solid var(--border);border-radius:8px;
  background:var(--bg);cursor:pointer;user-select:none}
.daypick:hover{background:var(--surface-2)}
.daypick input{width:auto;margin:0;accent-color:var(--accent)}
.daypick:has(input:checked){background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}
input[type=time]{width:100%;padding:.6rem .7rem;border-radius:8px;
  border:1px solid var(--border);background:var(--bg);color:var(--text);font:inherit;font-size:.93rem}
.check{display:flex;align-items:center;gap:.5rem;font-size:.9rem;margin:.2rem 0 1rem}
.check input{width:auto;margin:0}
.actions{display:flex;gap:.5rem;align-items:center}
.actions .btn-primary{width:auto;padding:.55rem 1.1rem}
.btn-danger{color:var(--err);border-color:var(--border)}
.btn-danger:hover{background:var(--err-soft);border-color:var(--err)}
/* line-height is explicit: .btn-sm is used on <a> as well as <button> inside
   table rows, and the two do not agree on a default. Without it the Link and
   Replay buttons in the same column are different heights. */
.btn-sm{padding:.3rem .6rem;font-size:.82rem;line-height:1.35;white-space:nowrap}
a.btn-sm:hover{text-decoration:none}

/* messages */
.flash{background:var(--ok-soft);color:var(--ok);border:1px solid var(--ok);
  padding:.6rem .8rem;border-radius:9px;font-size:.88rem;margin-bottom:1.2rem}
.errs{background:var(--err-soft);color:var(--err);border:1px solid var(--err);
  padding:.6rem .8rem .6rem 1.6rem;border-radius:9px;font-size:.87rem;margin-bottom:1.2rem}
.errs li{margin:.15rem 0}

/* chips */
.chips{display:flex;flex-wrap:wrap;gap:.4rem;margin-bottom:1.5rem}
.chip{display:inline-flex;align-items:center;gap:.4rem;padding:.3rem .7rem;border-radius:20px;
  font-size:.8rem;font-weight:500;border:1px solid var(--border);background:var(--surface)}
.chip b{font-weight:600}
.chip.on{background:var(--ok-soft);color:var(--ok);border-color:transparent}
.chip.off{background:var(--warn-soft);color:var(--warn);border-color:transparent}
.chip-warn{background:var(--warn-soft);color:var(--warn);border-color:transparent}
.dot{width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}

/* trigger cards */
.trig{display:grid;grid-template-columns:repeat(auto-fit,minmax(310px,1fr));gap:.9rem;margin-bottom:1.5rem}
.tcard{background:var(--surface);border:1px solid var(--border);border-radius:11px;
  padding:1rem 1.1rem;box-shadow:var(--shadow)}
.tcard h3{margin:0 0 .1rem;font-size:.93rem;font-weight:620;display:flex;align-items:center;gap:.5rem}
.tcard .src{font-size:.74rem;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);
  margin-bottom:.55rem}
.tcard p{margin:0;color:var(--muted);font-size:.86rem;line-height:1.5}
.tcard .eff{margin-top:.6rem;padding-top:.6rem;border-top:1px dashed var(--border);
  font-size:.83rem;color:var(--text)}

/* promotion path: a pipeline, read left to right.
   Each step carries the branch and the status that branch puts a work package
   into; the connector is a drawn line and arrowhead rather than a text arrow,
   so the sequence survives at small sizes and does not depend on the font. */
/* Never wraps. A wrapped pipeline breaks in the worst possible way: the
   connector is its own element, so it lands at the end of one line or the
   start of the next as an arrow pointing at nothing. The table is already
   inside a scrolling container, so a long path scrolls rather than folds. */
.col-flow{white-space:nowrap;width:99%;padding-top:.4rem;padding-bottom:.4rem}
/* The same pipeline standing on its own, on the lifecycle editor. */
.flowbox{padding:.9rem 1.1rem;overflow-x:auto}
.flow{display:inline-flex;flex-wrap:nowrap;align-items:stretch}
.flow .st{display:flex;flex-direction:column;justify-content:center;gap:.05rem;flex:none;
  padding:.3rem .6rem;border:1px solid var(--border);border-radius:7px;
  background:var(--surface-2);line-height:1.35}
.flow .st b{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.78rem;font-weight:500;white-space:nowrap}
.flow .st em{font-style:normal;color:var(--muted);font-size:.72rem;white-space:nowrap}

/* The connector. A 1px rule with a CSS-drawn head: no glyph, so it cannot
   render as a box in a font that lacks the arrow. */
/* align-self, because the row stretches the steps to a common height and a
   stretched 1px rule becomes a full-height block. */
.flow .lk{position:relative;flex:none;align-self:center;width:1.4rem;height:1px;
  background:var(--border)}
.flow .lk::after{content:"";position:absolute;right:0;top:-3px;width:0;height:0;
  border-top:3.5px solid transparent;border-bottom:3.5px solid transparent;
  border-left:5px solid var(--border)}

/* Stage 0 is not a branch — it is "a feature branch exists". Dashed, so the
   pipeline does not imply a deployment branch somebody could go and configure. */
.flow .st.start{background:transparent;border-style:dashed}

.flow .st.fin{background:var(--ok-soft);border-color:transparent}
.flow .st.fin b{color:var(--ok)}
.flow .st.fin em{color:var(--ok)}
/* A final stage with nothing mapped is not a success. The panel this replaced
   painted "no status mapped" green, which read as configured-and-working. */
.flow .st em.nomap,.flow .st.fin em.nomap{color:var(--warn)}

/* ordered explanation list */
.steps{counter-reset:s;list-style:none;padding:0;margin:0}
.steps li{counter-increment:s;position:relative;padding:.55rem 0 .55rem 2.2rem;
  border-bottom:1px solid var(--border);font-size:.88rem}
.steps li:last-child{border-bottom:none}
.steps li::before{content:counter(s);position:absolute;left:0;top:.5rem;
  width:1.5rem;height:1.5rem;border-radius:50%;background:var(--accent-soft);color:var(--accent);
  font-size:.76rem;font-weight:650;display:flex;align-items:center;justify-content:center}
.steps b{font-weight:600}
.steps .m{color:var(--muted)}
.section-h{font-size:.95rem;font-weight:620;margin:1.9rem 0 .7rem}

/* trigger hero */
.hero{display:flex;align-items:center;gap:1.2rem;flex-wrap:wrap;
  background:var(--surface);border:1px solid var(--border);border-radius:11px;
  padding:1.15rem 1.25rem;box-shadow:var(--shadow);margin-bottom:1.4rem}
.hero .txt{flex:1;min-width:16rem}
.hero h2{margin:0 0 .25rem;font-size:.98rem;font-weight:620}
.hero p{margin:0;color:var(--muted);font-size:.86rem;line-height:1.55}
.hero form{margin:0}
.hero .btn-primary{width:auto;padding:.62rem 1.25rem;white-space:nowrap}

/* sync summary */
.sum{display:flex;flex-wrap:wrap;gap:.55rem;margin-bottom:1.4rem}
.stat{background:var(--surface);border:1px solid var(--border);border-radius:9px;
  padding:.55rem .9rem;min-width:5.8rem;box-shadow:var(--shadow)}
.stat .n{font-size:1.3rem;font-weight:640;line-height:1.2;letter-spacing:-.02em}
.stat .l{color:var(--muted);font-size:.73rem;text-transform:uppercase;letter-spacing:.05em}
.stat.add .n{color:var(--ok)} .stat.warn .n{color:var(--warn)} .stat.err .n{color:var(--err)}

/* copy-out block */
.copywrap{position:relative}
.copywrap textarea{min-height:9.5rem;width:100%;padding:.75rem .8rem;border-radius:8px;
  border:1px solid var(--border);background:var(--bg);color:var(--text);display:block;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.82rem;
  line-height:1.7;resize:vertical}
.copywrap .copybtn{position:absolute;top:.55rem;right:.55rem}

.pill{display:inline-block;padding:.06rem .48rem;border-radius:20px;font-size:.7rem;
  font-weight:650;vertical-align:middle;margin-left:.35rem}
.p-new{background:var(--ok-soft);color:var(--ok)}
.p-arch{background:var(--warn-soft);color:var(--warn)}
.p-gone{background:var(--err-soft);color:var(--err)}
.p-priv{background:var(--surface-2);color:var(--muted)}
tr.off td:not(:last-child){opacity:.5}
