/* Tokens, reset, and the primitives every screen uses: buttons, inputs, chips, meta text. */

:root{
  /* iOS system colours, shared with the website, so the portal and the page that introduces
     it are one product. The older names below are aliases, kept so every screen's stylesheet
     reads the same tokens it always has. `--accent` is for text and lines; `--accent-fill`
     is for solid fills, which on a light background want the brighter green. */
  --bg:#000000; --bg-elev:#1c1c1e; --surface:#1c1c1e; --surface-2:#2c2c2e;
  --border:rgba(255,255,255,.10); --border-strong:rgba(255,255,255,.16);
  --text:#f5f5f7; --text-muted:#98989d; --text-faint:#636366;
  --accent:#30d158; --accent-fill:#30d158; --accent-strong:#30d158;
  --accent-soft:rgba(48,209,88,.15); --accent-ink:#000000; --accent-dim:rgba(48,209,88,.45);
  --blue:#0a84ff; --amber:#ff9f0a; --red:#ff453a; --violet:#bf5af2;
  --side:#161618; --sel:rgba(255,255,255,.10);
  --hover:rgba(255,255,255,.05); --active:rgba(255,255,255,.10);
  --shadow-sm:none; --shadow:none;
  --shadow-lg:0 20px 48px rgba(0,0,0,.6);
  --ease:cubic-bezier(.2,.8,.2,1);
  --r-sm:6px; --r:10px; --r-lg:14px;
  --scroll:rgba(255,255,255,.18); --scroll-hover:rgba(255,255,255,.3);
  --mono:"SF Mono",ui-monospace,"JetBrains Mono",Menlo,monospace;
  --sans:-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",system-ui,sans-serif;

  --panel:var(--surface); --panel-2:var(--surface-2); --line:var(--border);
  --ink:var(--text); --ink-dim:var(--text-muted); --ink-faint:var(--text-faint);
  --warn:var(--amber); --err:var(--red);
  --ruled:var(--blue); --measured:var(--accent); --derived:var(--violet);
  color-scheme:dark;
}
/* System preference wins unless the toggle has stamped data-theme on the root. */
@media (prefers-color-scheme:light){
  :root:not([data-theme="dark"]){
    --bg:#f5f5f7; --bg-elev:#ffffff; --surface:#ffffff; --surface-2:#f2f2f7;
    --border:rgba(0,0,0,.08); --border-strong:rgba(0,0,0,.14);
    --text:#1d1d1f; --text-muted:#6e6e73; --text-faint:#aeaeb2;
    --accent:#248a3d; --accent-fill:#34c759; --accent-strong:#248a3d;
    --accent-soft:rgba(52,199,89,.12); --accent-ink:#ffffff; --accent-dim:rgba(52,199,89,.45);
    --blue:#007aff; --amber:#c77700; --red:#ff3b30; --violet:#af52de;
    --side:#ebebf0; --sel:rgba(0,0,0,.07);
    --hover:rgba(0,0,0,.04); --active:rgba(0,0,0,.07);
    --shadow-sm:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.04);
    --shadow:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.05);
    --shadow-lg:0 4px 12px rgba(0,0,0,.06),0 20px 48px rgba(0,0,0,.12);
    --scroll:rgba(0,0,0,.18); --scroll-hover:rgba(0,0,0,.3);
    color-scheme:light;
  }
}
:root[data-theme="light"]{
    --bg:#f5f5f7; --bg-elev:#ffffff; --surface:#ffffff; --surface-2:#f2f2f7;
    --border:rgba(0,0,0,.08); --border-strong:rgba(0,0,0,.14);
    --text:#1d1d1f; --text-muted:#6e6e73; --text-faint:#aeaeb2;
    --accent:#248a3d; --accent-fill:#34c759; --accent-strong:#248a3d;
    --accent-soft:rgba(52,199,89,.12); --accent-ink:#ffffff; --accent-dim:rgba(52,199,89,.45);
    --blue:#007aff; --amber:#c77700; --red:#ff3b30; --violet:#af52de;
    --side:#ebebf0; --sel:rgba(0,0,0,.07);
    --hover:rgba(0,0,0,.04); --active:rgba(0,0,0,.07);
    --shadow-sm:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.04);
    --shadow:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.05);
    --shadow-lg:0 4px 12px rgba(0,0,0,.06),0 20px 48px rgba(0,0,0,.12);
    --scroll:rgba(0,0,0,.18); --scroll-hover:rgba(0,0,0,.3);
    color-scheme:light;
}

*{box-sizing:border-box}
/* `hidden` loses to any author rule that sets `display`, which is most of them: a hidden
   `.field` is still `display:grid` and a hidden `.btn` is still `display:block`. Every element
   the page hides would otherwise need its own `[hidden]` rule, and the ones nobody remembered
   just stayed on screen. Said once, here. */
[hidden]{display:none!important}

html{background:var(--bg)}
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.55 var(--sans);
     font-feature-settings:"cv11","ss01","ss03";-webkit-font-smoothing:antialiased;
     -moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}
a{color:inherit}
::selection{background:color-mix(in srgb,var(--accent) 28%,transparent)}
:focus-visible{outline:none}

/* Scrollbars that belong to the page rather than to the operating system of 2009. */
*{scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:10px;
      border:3px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:var(--scroll-hover);background-clip:padding-box;
      border:3px solid transparent}
::-webkit-scrollbar-corner{background:transparent}

h1{font:600 22px/1.25 var(--sans);margin:0 0 6px;letter-spacing:-.022em;color:var(--ink)}
.sub{color:var(--ink-dim);margin:0 0 26px;font-size:13.5px}
.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.meta{color:var(--ink-faint);font:11.5px var(--mono);font-variant-numeric:tabular-nums}
.empty{color:var(--ink-faint);padding:32px 0;text-align:center;font-size:13px}
.err{color:var(--err)} .warn{color:var(--warn)} .ok{color:var(--accent)}

.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);
      padding:16px 18px;margin-bottom:12px;box-shadow:var(--shadow-sm);
      transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
.card h3{margin:0 0 4px;font:600 14px var(--sans);letter-spacing:-.01em}
.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}

/* ---------- controls ---------- */
input[type=search],input[type=time],input[type=text],select{background:var(--bg-elev);
  border:1px solid var(--border-strong);color:var(--ink);
  border-radius:var(--r);padding:7px 11px;font:13px var(--sans);outline:none;
  transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
input[type=search]{width:280px}
input::placeholder,textarea::placeholder{color:var(--ink-faint)}
input[type=search]:hover,input[type=time]:hover,input[type=text]:hover,select:hover,
textarea:hover{border-color:color-mix(in srgb,var(--ink) 20%,transparent)}
input[type=search]:focus,input[type=time]:focus,input[type=text]:focus,select:focus{
  border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
textarea{width:100%;background:var(--bg-elev);border:1px solid var(--border-strong);color:var(--ink);
      border-radius:var(--r);padding:10px 12px;font:13px/1.6 var(--sans);outline:none;resize:vertical;
      transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}

/* One size, whether it is a button or a link. They sit in rows together, and `a` taking its
   line-height from the font shorthand while `button` set its own made every such row a set of
   controls at two different heights. */
.ghbtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:30px;
       padding:5px 12px;border:1px solid transparent;border-radius:8px;
       background:var(--surface-2);
       font:500 12.5px/1.4 var(--sans);color:var(--ink);text-decoration:none;white-space:nowrap;
       transition:background .15s var(--ease),color .15s var(--ease),opacity .15s var(--ease)}
.ghbtn:hover{color:var(--ink);background:color-mix(in srgb,var(--surface-2) 88%,var(--ink) 12%)}
.ghbtn:active{opacity:.8}
.ghbtn:focus-visible,.btn:focus-visible,.iconbtn:focus-visible{
       border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
button.ghbtn{cursor:pointer;font-family:var(--sans);
      -webkit-appearance:none;appearance:none;font-size:12.5px;line-height:1.4}
button.ghbtn:hover{background:color-mix(in srgb,var(--surface-2) 88%,var(--ink) 12%)}
.ghbtn.primary{background:var(--accent);border-color:transparent;color:var(--accent-ink);font-weight:600}
.ghbtn.primary:hover{background:var(--accent);color:var(--accent-ink);opacity:.88}
.ghbtn:disabled{opacity:.45;cursor:default}
.iconbtn{background:var(--surface-2);border:1px solid transparent;color:var(--ink-dim);
       border-radius:8px;width:34px;height:34px;cursor:pointer;font-size:15px;line-height:1;
       transition:background .15s var(--ease),color .15s var(--ease)}
.iconbtn:hover{color:var(--ink);background:color-mix(in srgb,var(--surface-2) 88%,var(--ink) 12%)}
.iconbtn{display:inline-flex;align-items:center;justify-content:center}
.iconbtn .sync{display:inline-flex;--ic:15px}
.iconbtn.spin .sync{animation:spin .9s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---------- icons ----------
   Lucide, vendored in js/icons.js. They take colour from `currentColor` and size from the
   `--ic` custom property, so a component sets both in CSS and never touches the SVG. */
svg.ic{width:var(--ic,15px);height:var(--ic,15px);display:block;flex:none}
/* A slot that holds one, centred, wherever a text glyph used to sit on a baseline. */
.icslot{display:inline-flex;align-items:center;justify-content:center;flex:none}

.chip{display:inline-flex;align-items:center;font:500 10.5px/1.5 var(--sans);letter-spacing:.01em;
      padding:1px 8px;border-radius:999px;border:1px solid transparent;color:var(--ink-dim);
      background:color-mix(in srgb,var(--ink) 7%,transparent);white-space:nowrap}
.chip.hot{color:var(--err);background:color-mix(in srgb,var(--err) 12%,transparent)}
.chip.warm{color:var(--warn);background:color-mix(in srgb,var(--warn) 13%,transparent)}
.chip.cool{color:var(--ruled);background:color-mix(in srgb,var(--ruled) 12%,transparent)}
.chip.ok{color:var(--accent);background:var(--accent-soft)}
.pill{font:11px var(--mono);color:var(--ink-dim);border:1px solid transparent;
      background:color-mix(in srgb,var(--ink) 4%,transparent);
      border-radius:999px;padding:2px 9px;font-variant-numeric:tabular-nums}
kbd{font:500 10.5px var(--mono);border:1px solid var(--border-strong);
    border-radius:5px;padding:1px 5px;color:var(--ink-dim);background:var(--surface-2)}

/* ---------- waiting ----------
   The inbox reads every repo in the org, which is seconds on a cold cache. What used to stand
   there was one line of grey text in an otherwise empty panel beside a second empty panel,
   which reads as a page that has failed rather than one that is working. These are the shape
   of what is coming: rows in the list, a title and paragraphs in the viewer. */
.sk{--skbg:color-mix(in srgb,var(--ink) 7%,transparent);border-radius:6px;
    background:var(--skbg);animation:pulse 1.4s ease-in-out infinite}
.sk-row{background:none;animation:none;padding:11px 10px;border-top:1px solid var(--line)}
.sk-row:first-of-type{border-top:0}
.sk-row .skbar{height:11px;border-radius:6px;
    background:var(--skbg);animation:pulse 1.4s ease-in-out infinite}
.sk-row .skbar.t{width:84%}
.sk-row .skbar.m{width:38%;height:8px;margin-top:8px}
/* Staggered, so the list reads as several things loading rather than one block flashing. */
.sk-row:nth-child(2n) .skbar.t{width:68%}
.sk-row:nth-child(3n) .skbar.t{width:92%}
.sk-row:nth-child(3n) .skbar.m{width:52%}
.sk-head{height:19px;width:56%;margin:6px 0 18px}
.sk-line{height:10px;width:100%;margin:9px 0}
.sk-line:nth-child(2n){width:88%}
.sk-line:nth-child(3n){width:94%}
.sk-line:last-child{width:42%}
/* The word beside them. It is the only part a screen reader gets, so it stays real text. */
.skhead{color:var(--ink-faint);font-size:12.5px;padding:4px 10px 8px}
/* A count that is still being fetched is not zero, and an empty badge reads as zero. */
.nav .n.wait{display:inline-block;width:15px;height:8px;border-radius:5px;
    background:color-mix(in srgb,var(--ink) 10%,transparent);animation:pulse 1.3s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:.35}50%{opacity:.85}}
@media (prefers-reduced-motion:reduce){
  .sk,.sk-row .skbar,.nav .n.wait{animation:none}
}

.notice{background:color-mix(in srgb,var(--warn) 10%,transparent);border:1px solid transparent;color:var(--ink-dim);border-radius:var(--r);
      padding:11px 14px;margin-bottom:16px;font-size:12.5px;line-height:1.6}
.notice b{color:var(--ink)}

/* ---------- a dialog that asks for more than a line ----------
   Native <dialog>, so the backdrop, the focus trap and Escape are the browser's job. */
dialog.ask{border:1px solid var(--border-strong);border-radius:var(--r-lg);background:var(--bg-elev);
      color:var(--ink);padding:22px 24px;width:min(560px,92vw);
      box-shadow:var(--shadow-lg)}
dialog.ask[open]{animation:dlgin .18s var(--ease)}
@keyframes dlgin{from{opacity:0;transform:translateY(6px) scale(.985)}}
dialog.ask::backdrop{background:rgba(0,0,0,.35);backdrop-filter:blur(8px)}
dialog.ask h3{margin:0;font:600 16px var(--sans);letter-spacing:-.018em}
dialog.ask .askhint{margin:6px 0 14px;font:12.5px/1.55 var(--sans);color:var(--ink-dim)}
dialog.ask textarea{font:13.5px/1.6 var(--sans);min-height:96px;max-height:44vh}
/* The attach control, when the dialog is a reply rather than a question. */
dialog.ask .attach{margin-top:9px}
dialog.ask .askrow{margin-top:14px;justify-content:flex-end}
dialog.ask .askrow .meta{margin-right:auto}

/* The lightbox: an image, over the page, that zooms.
 *
 * Not a <dialog>, so the stacking and the backdrop are declared here rather than inherited
 * from ::backdrop. 60 puts it over the `@` mention list (30) and over the dialog, which is
 * correct: a picture opened from inside a reply box should still open. */
.lightbox{position:fixed;inset:0;z-index:60;display:flex;
          background:rgba(0,0,0,.82)}
/* The stage is inset rather than the whole window, and that is not decoration.
   Most images here are screenshots *of this interface*, and one fitted edge to edge reads as
   the app having navigated rather than as a picture of it. The margin of backdrop around it,
   and the border on it, are the only things that say which you are looking at.

   Fit and centring are arithmetic in the module rather than layout: the transform has to be
   the single source of truth, or zooming fights the flexbox. The module measures this box, so
   changing the inset here is all it takes to change what "fitted" means. */
.lightbox .lbstage{position:absolute;inset:46px 56px 52px;overflow:hidden}
.lightbox img{position:absolute;top:0;left:0;transform-origin:0 0;
              max-width:none;max-height:none;border-radius:8px;
              border:1px solid rgba(255,255,255,.14);
              box-shadow:0 18px 50px rgba(0,0,0,.5);
              cursor:zoom-in;user-select:none;-webkit-user-drag:none}
/* Zoomed past fitted it is being read, not looked at, so the frame comes off: a border and a
   drop shadow in the middle of a diff you are inspecting is furniture in the way. */
.lightbox[data-zoomed="true"] img{border-radius:0;border-color:transparent;box-shadow:none}
.lightbox[data-zoomed="true"] img{cursor:grab}
.lightbox[data-zoomed="true"] img:active{cursor:grabbing}

.lightbox .lbx,.lightbox .lbctl{--ic:17px;
               display:inline-flex;align-items:center;justify-content:center;
               width:34px;height:34px;padding:0;border-radius:9px;
               border:1px solid var(--line);background:var(--panel);color:var(--ink-dim);
               cursor:pointer}
.lightbox .lbx{position:absolute;top:14px;right:16px;--ic:18px}
.lightbox .lbx:hover,.lightbox .lbctl:not(:disabled):hover{color:var(--ink);
               border-color:var(--ink-faint)}
/* At a stop the button stays where it is and stops responding. Removing it would move the two
   beside it, which reads as the control having changed rather than as a limit being reached. */
.lightbox .lbctl:disabled{opacity:.35;cursor:default}

/* Bottom right, which is the one corner nothing else uses: close is top right, the hint is top
   centre and the caption is bottom centre. */
.lightbox .lbzoom{position:absolute;bottom:12px;right:16px;display:flex;gap:6px;align-items:center}
/* Wide enough for "1600%" so the group does not resize as the number grows, and tabular so the
   digits do not shuffle while you hold the button down. */
.lightbox .lblevel{width:auto;min-width:60px;padding:0 9px;
                   font:12px var(--mono);font-variant-numeric:tabular-nums;
                   color:var(--ink-dim)}

/* Both of these are over the picture, so they are quiet and they do not take the pointer:
   a click that lands on the caption still means "fit", not "nothing happened". */
.lightbox .lbhint,.lightbox .lbcap{position:absolute;left:0;right:0;margin:0;
                                   pointer-events:none;text-align:center;
                                   font:11.5px var(--sans);color:var(--ink-faint)}
.lightbox .lbhint{top:16px;padding:0 60px}
.lightbox .lbcap{bottom:19px;padding:0 200px;color:var(--ink-dim);font-size:12.5px;
                 white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* The page behind must not scroll under an overlay. A wheel event that misses the image
   would otherwise scroll the document instead of doing nothing. */
body[data-lightbox] {overflow:hidden}

/* And the affordance on the document side: an image you can open should say so. */
.md img,img.zoomable{cursor:zoom-in}

/* ---------- a dialog for a whole task (sheet() in dialog.js) ---------- */
dialog.sheet{border:1px solid var(--border-strong);border-radius:var(--r-lg);background:var(--bg);
      color:var(--ink);padding:0;width:min(860px,calc(100vw - 32px));max-height:calc(100vh - 48px);
      box-shadow:var(--shadow-lg)}
dialog.sheet[open]{animation:dlgin .18s var(--ease)}
/* Pinned to the window. The graph's own `.sheet` panel is position:absolute, and that rule
   reached this dialog too, so one opened on a scrolled page sat at the top of the document,
   half off the screen. */
dialog.sheet{position:fixed}
dialog.sheet::backdrop{background:rgba(0,0,0,.35);backdrop-filter:blur(8px)}
.sheetbody{padding:22px 24px 26px;overflow:auto;max-height:calc(100vh - 50px)}

/* The side sheet: a thread opened from Home, full height on the right. */
dialog.sheet.side{margin:0 0 0 auto;height:100vh;max-height:100vh;width:min(780px,100vw);
      border-radius:0;border-width:0 0 0 1px}
dialog.sheet.side[open]{animation:sidein .22s var(--ease)}
dialog.sheet.side .sheetbody{max-height:100vh;height:100%;box-sizing:border-box;padding:18px 22px 40px}
@keyframes sidein{from{transform:translateX(24px);opacity:0}}
@media (prefers-reduced-motion:reduce){dialog.sheet.side[open]{animation:none}}
.sidebar-x{position:sticky;top:0;display:flex;justify-content:flex-end;margin:-6px -6px 4px;z-index:1}
dialog.sheet.side .viewer{border:0;box-shadow:none;padding:0;min-height:0;background:none}
/* The title and its actions stay in view while a long thread scrolls under them. The close
   button floats over the header's top right, so the lines beside it leave room for it; the
   actions below it run to the edge, so Open in GitHub lines up with the close button. */
dialog.sheet.side .thead{position:sticky;top:-18px;z-index:1;background:var(--bg);
      padding-top:18px;margin-top:-18px}
dialog.sheet.side .thead > :not(.tacts){padding-right:44px}
dialog.sheet.side .sidebar-x{position:sticky;top:0;height:0;margin:0;z-index:2;overflow:visible}
dialog.sheet.side .sidebar-x .iconbtn{margin-top:-4px}
.sheettitle{margin:0 0 14px;font:600 18px var(--sans);letter-spacing:-.02em}
.sheetloading{display:grid;place-items:center;min-height:260px}
.spinner{width:22px;height:22px;border-radius:50%;border:2px solid var(--line);border-top-color:var(--accent);
      animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* The first load, while the org is read from GitHub: a row per repository. */
.loadlist{display:flex;flex-direction:column;gap:2px;max-width:520px}
.loadrow{display:grid;grid-template-columns:18px 140px 1fr;align-items:center;gap:10px;padding:7px 0;
  font-size:13.5px;border-bottom:1px solid var(--line)}
.loadrow b{font-weight:500}
.loadrow .meta{color:var(--ink-dim)}
.loadmark{width:10px;height:10px;border-radius:50%;border:2px solid var(--accent-dim);
  border-top-color:var(--accent);animation:loadspin .8s linear infinite}
.loadrow.done .loadmark{animation:none;border-color:var(--accent);background:var(--accent)}
.loadrow.done .meta{color:var(--ink-faint)}
@keyframes loadspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.loadmark{animation:none}}
