:root {
  --bg: #080b15;
  --surface: #121829;
  --surface-2: #182036;
  --surface-3: #1f2942;
  --border: rgba(148, 163, 205, 0.14);
  --border-strong: rgba(148, 163, 205, 0.26);
  --text: #e9edf8;
  --muted: #8b93a8;
  --faint: #5b6377;
  --accent: #6d6afc;
  --accent-2: #9a7bff;
  --teal: #2dd4bf;
  --accent-soft: rgba(109, 106, 252, 0.14);
  --ring: rgba(109, 106, 252, 0.5);

  --bug: #ff6b6b;
  --idea: #f5b544;
  --praise: #46d493;
  --question: #58a6ff;
  --other: #94a0b8;

  /* ── spacing scale ──
     Every gap, padding and margin comes from here. Before this the stylesheet
     used gaps of 2/4/6/7/8/9/10/12/14 and paddings of 3/6/7/8/9/10/11/12/16/
     18/20/24/34, so each component kept its own rhythm and nothing lined up
     with anything it hadn't shipped beside. 1–3px values are hairlines and
     optical nudges, not spacing, and stay literal. */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;

  /* ── type scale ──
     Five steps plus the two display sizes. Replaces roughly a dozen ad-hoc
     sizes (10.5, 11.5, 12.5, 13.5, 14.5 …) that differed by half a pixel and
     communicated nothing by differing. */
  --t-xs: 12px;
  --t-sm: 13px;
  --t-base: 14px;
  --t-lg: 16px;
  --t-xl: 21px;
  --t-2xl: 28px;

  --radius: 14px;
  --radius-sm: 9px;
  --radius-lg: 20px;
  --radius-pill: 999px;
  /* The content column every view lives in, and the one vertical rhythm
     between sections. Sidebar (250) + column + gutters fits a 1440 screen. */
  --col: 1160px;
  --sec-gap: var(--s5);
  /* Height of the phone tab bar, so views can reserve room for it without
     each one guessing. Zero on desktop, where the bar isn't rendered. */
  --tabbar: 0px;
  --shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.6);
  --font: "Instrument Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --head: "Bricolage Grotesque", var(--font);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  font-size: 14px;
  line-height: 1.5;
}

/* ── atmosphere: slow-drifting aurora + film grain ── */
body::before {
  content: ""; position: fixed; inset: -22%; z-index: -2; pointer-events: none;
  background:
    radial-gradient(42% 34% at 18% 12%, rgba(109, 106, 252, 0.22), transparent 70%),
    radial-gradient(36% 30% at 84% 6%, rgba(45, 212, 191, 0.13), transparent 70%),
    radial-gradient(32% 28% at 68% 88%, rgba(154, 123, 255, 0.13), transparent 70%);
  animation: aurora 26s ease-in-out infinite alternate;
}
@keyframes aurora {
  0%   { transform: translate3d(-1.5%, -1%, 0) rotate(0deg) scale(1); }
  50%  { transform: translate3d(2%, 2%, 0) rotate(2deg) scale(1.06); }
  100% { transform: translate3d(-1%, 1.5%, 0) rotate(-2deg) scale(1.03); }
}
body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.6'/%3E%3C/svg%3E");
}

/* display voice for headings + big figures */
h1, h2, h3, .app-card .counts .v { font-family: var(--head); }

/* ── scrollbars ── */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 8px; border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: #263048; }
.hidden { display: none !important; }
.muted { color: var(--muted); }
button, input { font-family: inherit; }
.ico { width: 18px; height: 18px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }

/* ── buttons ── */
.btn {
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s4); border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--surface-2);
  color: var(--text); font-size: var(--t-sm); font-weight: 600; cursor: pointer;
  transition: background .16s ease, border-color .16s ease, transform .06s ease;
}
.btn:hover { background: var(--surface-3); border-color: rgba(255,255,255,.22); }
.btn:active { transform: scale(.98); }
.btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--ring); }
.btn-primary {
  background: linear-gradient(135deg, var(--accent), var(--accent-2)); border-color: transparent; color: #fff;
  position: relative; overflow: hidden; box-shadow: 0 4px 14px -6px rgba(109, 106, 252, .55);
}
.btn-primary::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(110deg, transparent 30%, rgba(255, 255, 255, .28) 50%, transparent 70%);
  transform: translateX(-130%); transition: transform .55s ease;
}
.btn-primary:hover { filter: brightness(1.08); }
.btn-primary:hover::after { transform: translateX(130%); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); padding: var(--s2); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }
.btn-block { width: 100%; justify-content: center; padding: var(--s3); }
/* Was 6px/10px. On the scale it lands taller, which is also what makes the
   card actions a real touch target on a phone instead of a 26px sliver. */
.btn-sm { padding: var(--s2) var(--s3); font-size: var(--t-xs); }
.btn-danger:hover { border-color: rgba(255,107,107,.5); color: var(--bug); }

/* ── login ── */
.login { min-height: 100%; display: grid; place-items: center; padding: 24px; }
.login-card {
  width: 100%; max-width: 360px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 20px; padding: 34px 30px;
  box-shadow: var(--shadow); text-align: center;
  animation: loginIn .55s cubic-bezier(.22,1,.36,1) both;
}
@keyframes loginIn { from { opacity: 0; transform: translateY(16px) scale(.97); } to { opacity: 1; transform: none; } }
.login-card > * { animation: riseIn .5s cubic-bezier(.22,1,.36,1) both; }
.login-card > *:nth-child(2) { animation-delay: .07s; }
.login-card > *:nth-child(3) { animation-delay: .12s; }
.login-card > *:nth-child(4) { animation-delay: .17s; }
.login-card > *:nth-child(5) { animation-delay: .22s; }
.brand-mark {
  width: 52px; height: 52px; margin: 0 auto 16px; border-radius: 15px;
  display: grid; place-items: center; color: #fff; position: relative;
  background: linear-gradient(145deg, var(--accent), var(--accent-2));
  box-shadow: 0 8px 22px -6px rgba(109,106,252,.6);
}
/* orbiting accent ring around the login mark */
.login .brand-mark::before {
  content: ""; position: absolute; inset: -9px; border-radius: 50%; padding: 1.5px;
  background: conic-gradient(transparent 12%, var(--accent) 38%, var(--teal) 58%, transparent 82%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  animation: spinRing 3.4s linear infinite;
}
@keyframes spinRing { to { transform: rotate(360deg); } }
.brand-mark.sm { width: 30px; height: 30px; border-radius: 9px; margin: 0; box-shadow: none; }
.login-card h1 { margin: 0 0 4px; font-size: 22px; font-weight: 800; letter-spacing: -.02em; }
.login-card p { margin: 0 0 22px; font-size: 13px; }
.login-card input {
  width: 100%; padding: 12px 14px; margin-bottom: 12px; border-radius: 10px;
  background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--text); font-size: 14px;
  transition: border-color .16s, box-shadow .16s;
}
.login-card input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
.login-error { color: var(--bug); font-size: 13px; margin: 12px 0 0; }

/* ── layout ── */
.layout { display: grid; grid-template-columns: 250px 1fr; min-height: 100%; }
/* Grid/flex children default to min-width:auto and refuse to shrink below
   their content, which held the page at ~590px on phones. */
.layout > * { min-width: 0; }
.sidebar {
  border-right: 1px solid var(--border); padding: 18px 14px; display: flex; flex-direction: column;
  gap: 4px; position: sticky; top: 0; height: 100vh;
  background: linear-gradient(180deg, rgba(255,255,255,.015), transparent);
}
.sidebar-brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 15px; padding: 6px 8px 14px; letter-spacing: -.01em; }
.nav { flex: 1; display: flex; flex-direction: column; gap: 2px; overflow-y: auto; }
.nav-label { font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); padding: 14px 10px 6px; }
.nav-item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 8px 10px; border-radius: 9px; border: none; background: transparent;
  color: var(--muted); font-size: 13.5px; font-weight: 500; cursor: pointer;
  position: relative; transition: background .14s, color .14s;
}
.nav-item::before {
  content: ""; position: absolute; left: -1px; top: 50%; width: 3px; height: 0;
  border-radius: 4px; background: linear-gradient(180deg, var(--accent), var(--teal));
  transform: translateY(-50%); transition: height .2s cubic-bezier(.22,1,.36,1);
}
.nav-item:hover { background: var(--surface-2); color: var(--text); }
.nav-item.active { background: var(--accent-soft); color: #cfceff; }
.nav-item.active::before { height: 62%; }
.nav-item.active .ico { color: var(--accent-2); }
.nav-item .ico { width: 17px; height: 17px; }
.nav-item span:not(.nav-count):not(.dot) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-count { font-size: 11px; font-weight: 700; color: var(--faint); background: var(--surface-3); padding: 1px 7px; border-radius: 20px; min-width: 20px; text-align: center; }
.nav-item.active .nav-count { background: rgba(109,106,252,.25); color: #cfceff; }
.dot { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }
.logout { margin-top: var(--s1); }
.nav-none { padding: var(--s2) var(--s3); color: var(--faint); font-size: var(--t-xs); }

/* ── phone chrome ──
   Hidden on desktop, where the sidebar does both jobs. Declared here rather
   than only inside the media query so the elements have a definition even if
   the query never matches. */
.topbar, .tabbar { display: none; }
.topbar {
  align-items: center; gap: var(--s3); padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--border); background: var(--surface);
  position: sticky; top: 0; z-index: 20;
}
.topbar-title { font-weight: 700; font-size: var(--t-base); letter-spacing: -.01em; flex: 1; }
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
}
.tab {
  position: relative; flex: 1; display: flex; flex-direction: column; align-items: center;
  gap: 3px; padding: var(--s2) var(--s1); min-height: 52px; justify-content: center;
  border: none; background: none; cursor: pointer;
  color: var(--faint); font-size: 10.5px; font-weight: 600; font-family: inherit;
  transition: color .14s;
}
.tab .ico { width: 21px; height: 21px; }
.tab.active { color: #cfceff; }
.tab.active .ico { color: var(--accent-2); }
/* The active marker sits on the top edge, mirroring the sidebar's left rail. */
.tab.active::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 26px; height: 2px; border-radius: 0 0 3px 3px;
  background: linear-gradient(90deg, var(--accent), var(--teal));
}
.tab-dot { display: none; }
.tab-dot.on {
  display: block; position: absolute; top: 9px; left: 50%; margin-left: 7px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent-2);
}
/* Desktop keeps per-app filtering in the sidebar. Needs to beat .chipbar's own
   display:flex, which is declared later in the file. */
.chipbar.app-filter { display: none; }

/* ── main ── */
/* One measured content column for every view. This used to cap at 2000px, i.e.
   never — so content spread to the window edge while the content itself stayed
   narrow, stranding a single inbox card in a half-screen void and running
   ledger rows out to 40% width. A real column is what makes the four views
   read as one product. */
.main {
  padding: var(--s5) var(--s6) calc(var(--s7) + var(--tabbar));
  max-width: var(--col); width: 100%; margin-inline: auto;
}
.page-head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s4);
  margin-bottom: var(--sec-gap); padding-bottom: var(--s4); border-bottom: 1px solid var(--border);
}
.eyebrow { font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--accent-2); margin: 0 0 5px; }
.page-head h2 {
  margin: 0; font-family: var(--head); font-size: 29px; font-weight: 700; letter-spacing: -.025em;
  background: linear-gradient(120deg, #fff 30%, #b9b6ff 90%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* A one-line orientation under the title — what this view is for. */
.page-sub { font-size: 13px; color: var(--muted); margin: 7px 0 0; max-width: 52ch; line-height: 1.55; }

/* ── section primitive ──
   Every band of content on every view is a titled panel. Before this the
   portfolio was a stack of bare bands separated by hairlines and 10px
   letterspaced eyebrows — nothing grouped, so the eye had no anchors. */
.panel {
  background: linear-gradient(165deg, var(--surface-2), var(--surface) 70%);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--s4) var(--s5); margin-bottom: var(--sec-gap);
}
.panel.flush { padding: var(--s2) var(--s5) var(--s1); }
.sec-head {
  display: flex; align-items: baseline; gap: var(--s3); margin: 0 0 var(--s4);
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--muted);
}
.sec-head .n { font-family: var(--mono); font-size: 11px; letter-spacing: .06em; color: var(--faint); font-weight: 500; }
/* The rule fills whatever the title and count leave — the heading reads as a
   band across the panel rather than a floating word. */
.sec-head::after { content: ""; flex: 1; height: 1px; background: var(--border); align-self: center; }
.sec-note { font-size: var(--t-xs); color: var(--faint); margin: 0; line-height: 1.6; }

/* ── stat primitive ──
   A figure with its label, boxed. Three views had their own version of this
   (.app-card .counts > div, .sc-metric, and the portfolio's .mfig), which is
   why the same idea looked like three different things depending on where you
   met it. .mfig keeps its own borderless editorial treatment on purpose; the
   two boxed ones are now the same component. */
.stat {
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: var(--s2) var(--s3); min-width: 0;
}
.stat .v {
  font-family: var(--head); font-size: var(--t-xl); font-weight: 800;
  line-height: 1.1; font-variant-numeric: tabular-nums;
}
.stat .v.empty { color: var(--faint); font-size: var(--t-base); padding: var(--s1) 0; }
.stat .k {
  font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint);
}
/* Either order is used — value-first in the apps grid, label-first in the app
   sheet — so the gap belongs between them, not on one of them. */
.stat .v + .k, .stat .k + .v { margin-top: 2px; }
/* Rows of stats size themselves to the space rather than to a column count,
   so the same markup works in a sheet, a card and a panel. */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: var(--s2); }

/* ── stat strip ──
   Deliberately one thin bar rather than a row of tall tiles: the figures are
   small, two of them are usually empty, and the feedback below is the point. */
/* The summary row and the filters share one container — see .inbox-bar. */
.inbox-bar {
  border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 14px;
  background: linear-gradient(165deg, var(--surface-2), var(--surface) 70%); overflow: hidden;
}
.statbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2) var(--s5);
  padding: var(--s3) var(--s4); position: relative; overflow: hidden;
  border-bottom: 1px solid var(--border);
}
/* cursor-tracking glow (set via --mx/--my from JS) */
.statbar::after {
  content: ""; position: absolute; inset: 0; opacity: 0; pointer-events: none; transition: opacity .25s;
  background: radial-gradient(220px circle at var(--mx, 50%) var(--my, 50%), rgba(109, 106, 252, .13), transparent 65%);
}
.statbar:hover::after { opacity: 1; }
.sfig { display: flex; align-items: baseline; gap: var(--s2); position: relative; }
/* The figures used to be separated by absolutely-positioned dividers, which
   drew as stray verticals at the start of every wrapped row — and needed a
   620px breakpoint to switch off before that happened. A wider column gap
   separates them at every width and needs no breakpoint to know when. */
.sfig .v { font-family: var(--head); font-size: var(--t-xl); font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.sfig .k { font-size: var(--t-xs); color: var(--muted); font-weight: 500; }
.sfig .sub { color: var(--faint); }
.sfig.warn .v { color: var(--idea); }
.sfig.warn .k { color: var(--idea); opacity: .85; animation: warnPulse 2.2s ease-in-out infinite; }
.sfig.grow { margin-left: auto; }
@keyframes warnPulse { 50% { opacity: .45; } }
.spark { display: flex; align-items: flex-end; gap: 3px; height: 18px; width: 78px; margin-left: 4px; }
.spark i {
  flex: 1; border-radius: 2px 2px 0 0; min-height: 2px; display: block;
  background: linear-gradient(180deg, rgba(154, 123, 255, .55), rgba(109, 106, 252, .12));
  transition: height .5s cubic-bezier(.22,1,.36,1);
  animation: sparkUp .7s cubic-bezier(.22,1,.36,1) both; transform-origin: bottom;
}
@keyframes sparkUp { from { transform: scaleY(0); } to { transform: none; } }

/* ── toolbar ──
   One contained control bar rather than four controls floating loose on the
   page background. Search leads; scope/type/sort group to the trailing edge so
   the row has two clear halves instead of a smear of widgets. */
.toolbar { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; padding: var(--s3); }
/* Search grows to fill whatever the controls leave, which pushes them to the
   trailing edge on one line and lets them sit together under it once the bar
   wraps. The previous version used margin-left:auto plus a 900px breakpoint to
   cancel it — the auto margin is what needed the breakpoint, so it's gone. */
.toolbar > :not(.search) { flex: 0 0 auto; }
.search { position: relative; flex: 1 1 240px; min-width: 0; }
.search .ico { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--faint); width: 16px; height: 16px; }
.search input {
  width: 100%; padding: 9px 12px 9px 34px; border-radius: 9px; background: var(--surface); color: var(--text);
  border: 1px solid var(--border-strong); font-size: 13px; transition: border-color .16s, box-shadow .16s;
}
.search input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
.seg { display: inline-flex; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; padding: 3px; gap: 2px; }
.seg button {
  padding: 6px 11px; border: none; background: transparent; color: var(--muted); font-size: 12.5px; font-weight: 600;
  border-radius: 7px; cursor: pointer; transition: background .14s, color .14s;
}
.seg button:hover { color: var(--text); }
.seg button.active { background: var(--surface-3); color: var(--text); box-shadow: 0 1px 2px rgba(0,0,0,.3); }
/* Type and sort are secondary controls — dropdowns so the scope segment stays
   the one obviously-primary filter in the toolbar. */
.sel {
  padding: 8px 30px 8px 11px; border-radius: 9px; background: var(--surface); color: var(--muted);
  border: 1px solid var(--border); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--faint) 50%), linear-gradient(135deg, var(--faint) 50%, transparent 50%);
  background-position: calc(100% - 15px) 52%, calc(100% - 10px) 52%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}
.sel:hover { color: var(--text); border-color: var(--border-strong); }
.sel:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }

/* Says plainly what the list is currently showing — the sidebar and the scope
   control both narrow it, and the heading alone couldn't tell you which. */
.scope-line { font-size: 12.5px; color: var(--faint); margin: -4px 0 16px; }
.scope-line strong { color: var(--muted); font-weight: 700; }
.scope-line .sep { margin: 0 7px; opacity: .5; }
.linkbtn {
  border: none; background: none; padding: 0; margin-left: 4px; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--accent-2);
}
.linkbtn:hover { text-decoration: underline; }

/* ── status groups ── */
.fgroup + .fgroup { margin-top: var(--s5); }
.fgroup-h {
  display: flex; align-items: center; gap: 9px; margin: 0 0 11px;
  font-size: 11.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--muted);
}
.fgroup-h .n {
  font-size: 11px; font-weight: 700; letter-spacing: 0; color: var(--faint);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 1px 7px;
}
.fgroup-h::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* ── feedback list ── */
/* Responsive triage board: fills wide screens with multiple card columns,
   collapses to a single column on phones. min(400px,100%) prevents the tracks
   from overflowing a narrow viewport. */
.feed { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(400px, 100%), 1fr)); gap: var(--s3); align-items: start; }
.card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--s4); animation: rise .45s cubic-bezier(.22,1,.36,1) both;
  transition: border-color .18s, transform .18s cubic-bezier(.22,1,.36,1), box-shadow .18s;
}
.card:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: 0 14px 34px -20px rgba(0, 0, 0, .85); }
/* The left accent is the app's colour, on every card. Status is carried by the
   group heading above, so the card only has to answer "which app is this?" */
.card { border-left: 3px solid var(--accent-app, var(--border-strong)); }
.card-top { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s2); flex-wrap: wrap; }
.app-badge { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 700; }
.app-badge .dot { width: 8px; height: 8px; border-radius: 2px; }
.badge { display: inline-flex; align-items: center; gap: 4px; font-size: 10.5px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; padding: 3px 8px 3px 6px; border-radius: 6px; }
.badge svg { width: 11px; height: 11px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; opacity: .85; }
/* Softer tints — the type glyph carries recognition so colour can recede */
.badge.bug { background: rgba(255,107,107,.10); color: var(--bug); }
.badge.idea { background: rgba(245,181,68,.10); color: var(--idea); }
.badge.praise { background: rgba(70,212,147,.10); color: var(--praise); }
.badge.question { background: rgba(88,166,255,.10); color: var(--question); }
.badge.other { background: rgba(148,160,184,.10); color: var(--other); }
.stars { color: var(--idea); opacity: .9; font-size: 13px; letter-spacing: 1px; }
.card-time { margin-left: auto; font-size: 12px; color: var(--faint); white-space: nowrap; cursor: default; }
/* Clamped by default so one long message can't stretch its whole grid row.
   JS adds .clamped (and the toggle) only to the cards that actually overflow. */
.card-msg {
  font-size: 14px; color: var(--text); white-space: pre-wrap; word-break: break-word;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; line-clamp: 4; overflow: hidden;
}
.card-msg.expanded { -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; }
.msg-more {
  margin-top: 6px; padding: 0; border: none; background: none; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--accent-2); opacity: .85;
}
.msg-more:hover { opacity: 1; text-decoration: underline; }
/* Context, not content — one dim line rather than a row of chips */
.card-meta { margin-top: var(--s3); font-size: var(--t-xs); color: var(--faint); line-height: 1.6; word-break: break-word; }
.card-meta .sep { margin: 0 6px; opacity: .5; }
/* The reporter address is the one piece of metadata that is actionable */
.meta-mail { color: var(--accent-2); text-decoration: none; }
.meta-mail:hover { text-decoration: underline; }
.btn-reply { color: var(--accent-2); border-color: rgba(154, 123, 255, .35); text-decoration: none; }
.btn-reply:hover { border-color: var(--accent-2); background: rgba(109, 106, 252, .12); }

/* Attached screenshots — a thumbnail strip; the full image opens in a new tab.
   Fixed height so a portrait phone shot can't stretch the card's row. */
.shots { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; }
.shot { display: block; line-height: 0; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); transition: border-color .15s, transform .15s; }
.shot:hover { border-color: var(--accent-2); transform: translateY(-1px); }
.shot-thumb { width: 56px; height: 56px; object-fit: cover; display: block; background: var(--surface-2); }

/* Replies already sent, shown on the card so the thread is visible at a glance */
.thread { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.thread-item {
  border-left: 2px solid var(--praise); background: rgba(70, 212, 147, .05);
  border-radius: 0 8px 8px 0; padding: 8px 11px;
}
.thread-item.failed { border-left-color: var(--bug); background: rgba(255, 107, 107, .06); }
.thread-top {
  display: flex; gap: 8px; align-items: baseline; justify-content: space-between;
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--praise);
}
.thread-item.failed .thread-top { color: var(--bug); }
.thread-err { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 60%; }
.thread-body { margin-top: 5px; font-size: 13px; color: var(--muted); white-space: pre-wrap; word-break: break-word; }

/* Saved-reply shortcuts in the composer */
.tpl-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.tpl-row .feat-pill { cursor: pointer; }
.tpl-row .feat-pill:hover { border-color: var(--accent-2); color: var(--text); }
.reply-orig { margin-top: 12px; }

/* Collapsible structured-diagnostic block split out of a message */
.diag { margin-top: 10px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2); overflow: hidden; }
.diag summary { cursor: pointer; padding: 8px 12px; font-size: 12px; font-weight: 600; color: var(--muted); list-style: none; display: flex; align-items: center; gap: 7px; user-select: none; }
.diag summary::before { content: ""; width: 6px; height: 6px; border-right: 1.6px solid var(--faint); border-bottom: 1.6px solid var(--faint); transform: rotate(-45deg); transition: transform .15s; flex: 0 0 auto; }
.diag[open] summary::before { transform: rotate(45deg); }
.diag summary::-webkit-details-marker { display: none; }
.diag pre { margin: 0; padding: 10px 12px; border-top: 1px solid var(--border); font: 11.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text); white-space: pre-wrap; word-break: break-word; max-height: 340px; overflow: auto; }
.chip { font-size: 11.5px; color: var(--muted); background: var(--surface-2); border: 1px solid var(--border); padding: 3px 9px; border-radius: 20px; }
.card-actions { display: flex; gap: var(--s2); margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--border); opacity: 0; transition: opacity .16s; }
.card:hover .card-actions, .card:focus-within .card-actions { opacity: 1; }
/* Status is the heading's job now, so the only per-status treatment left is
   pushing already-handled items visually into the background. */
.card.status-resolved, .card.status-archived { opacity: .62; }
.card.status-resolved:hover, .card.status-archived:hover { opacity: 1; }

/* status-change feedback: fly out when leaving the filter, flash when staying */
.card.exit-out { animation: cardOut .34s cubic-bezier(.4, 0, .9, .5) forwards !important; }
@keyframes cardOut { to { opacity: 0; transform: translateX(70px) scale(.92); } }
.card.flash-resolved { animation: flashResolved .55s ease both !important; }
@keyframes flashResolved { 30% { border-color: var(--praise); box-shadow: 0 0 0 3px rgba(70, 212, 147, .3), 0 0 34px -6px rgba(70, 212, 147, .55); } }
.card.flash-read { animation: flashRead .55s ease both !important; }
@keyframes flashRead { 30% { border-color: var(--question); box-shadow: 0 0 0 3px rgba(88, 166, 255, .28); } }
.card.flash-archived { animation: flashArchived .55s ease both !important; }
@keyframes flashArchived { 30% { border-color: var(--border-strong); box-shadow: 0 0 0 3px rgba(148, 160, 184, .22); } }
.card.flash-new { animation: flashNew .55s ease both !important; }
@keyframes flashNew { 30% { border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring), 0 0 34px -6px rgba(109, 106, 252, .55); } }

/* ── apps grid ──
   .app-card is now .card plus what makes it an app card. It used to restate
   the card's own background, border, radius and padding, which is how the two
   drifted apart. Same for the counts: they are .stat now. */
.apps-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: var(--s3); }
/* Feedback cards carry a left accent in their app's colour; an app card is
   already unambiguous about which app it is. */
.app-card { border-left-width: 1px; border-left-color: var(--border); }
.app-card:hover { transform: none; box-shadow: none; }
.app-card h3 { margin: 0; font-size: var(--t-lg); font-weight: 700; display: flex; align-items: center; gap: var(--s2); }
.app-card .counts { margin: var(--s3) 0; }
/* Two keys per app now, and which is which matters: one ships inside the
   binary, the other must never leave the repo. */
.keylabel {
  display: flex; align-items: baseline; gap: var(--s2); margin: var(--s3) 0 var(--s1);
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--faint);
}
.keylabel span { font-weight: 600; letter-spacing: 0; text-transform: none; font-size: 11px; }
.keylabel .warn { color: var(--idea); }
.keyrow {
  display: flex; align-items: center; gap: var(--s2); background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--s2) var(--s3);
  font-family: var(--mono); font-size: var(--t-xs);
}
.keyrow code { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
.app-card .app-actions { display: flex; gap: var(--s2); margin-top: var(--s3); flex-wrap: wrap; }
details.snippet { margin-top: var(--s3); }
details.snippet summary { cursor: pointer; font-size: var(--t-xs); color: var(--accent-2); font-weight: 600; }
details.snippet pre { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--s3); overflow-x: auto; font-size: 11px; color: var(--muted); margin-top: var(--s2); }

/* ── empty ──
   One empty state for the whole app. The portfolio had its own (.sc-empty),
   which is why "nothing here yet" looked like two different products. The
   dashed box is now a modifier, used where the empty state sits inside a
   populated page rather than replacing it. */
.empty { text-align: center; padding: var(--s7) var(--s5); color: var(--faint); }
.empty svg { width: 40px; height: 40px; margin-bottom: var(--s3); opacity: .5; animation: floaty 3.4s ease-in-out infinite; }
@keyframes floaty { 50% { transform: translateY(-6px); } }
.empty h3 { color: var(--muted); font-weight: 600; margin: 0 0 var(--s1); font-size: var(--t-lg); font-family: var(--head); }
.empty p { margin: 0 auto; font-size: var(--t-sm); max-width: 46ch; line-height: 1.6; }
.empty.boxed {
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
  background: var(--surface); padding: var(--s6) var(--s5);
}

/* ── toast ── */
.toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(10px);
  background: var(--surface-3); border: 1px solid var(--border-strong); color: var(--text);
  padding: 11px 18px; border-radius: 11px; font-size: 13px; font-weight: 500; box-shadow: var(--shadow);
  opacity: 0; transition: opacity .22s, transform .38s cubic-bezier(.34,1.56,.64,1); z-index: 50;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

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

/* ── catch-up (focus-mode triage) ── */
.head-actions { display: flex; align-items: center; gap: 8px; }
.btn .pill { background: rgba(255,255,255,.22); color: #fff; font-size: 11px; font-weight: 800; padding: 0 6px; border-radius: 20px; min-width: 18px; text-align: center; }
.btn .pill:empty { display: none; }

.cu {
  position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 20px;
  background: rgba(6, 8, 13, .78); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  animation: cuFade .2s ease both;
}
@keyframes cuFade { from { opacity: 0; } to { opacity: 1; } }

/* setup + done panels */
.cu-panel {
  position: relative; width: 100%; max-width: 400px; background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 20px; padding: 28px 26px; box-shadow: var(--shadow);
}
.cu-panel h2 { margin: 0 0 4px; font-size: 21px; font-weight: 800; letter-spacing: -.02em; }
.cu-panel > p.muted { margin: 0 0 20px; font-size: 13px; }
.cu-close {
  position: absolute; top: 14px; right: 14px; width: 30px; height: 30px; border-radius: 8px;
  border: none; background: var(--surface-2); color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer;
}
.cu-close:hover { background: var(--surface-3); color: var(--text); }
.cu-field { margin-bottom: 16px; }
.cu-field > label { display: block; font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); margin-bottom: 7px; }
.cu-field select {
  width: 100%; padding: 10px 12px; border-radius: 9px; background: var(--surface-2);
  border: 1px solid var(--border-strong); color: var(--text); font-size: 13.5px;
}
.cu-field select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
.cu-types { display: flex; flex-wrap: wrap; gap: 7px; }
.cu-type {
  display: inline-flex; align-items: center; gap: 6px; padding: 7px 11px; border-radius: 8px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--muted);
  font-size: 12.5px; font-weight: 600; cursor: pointer; user-select: none; text-transform: capitalize;
}
.cu-type svg { width: 13px; height: 13px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.cu-type.on { background: var(--accent-soft); border-color: transparent; color: #cfceff; }
.cu-order-seg { display: inline-flex; }
.cu-start-count { color: var(--faint); font-weight: 600; }

/* card stack */
.cu-stack { width: 100%; max-width: 560px; display: flex; flex-direction: column; gap: 14px; }
.cu-top { display: flex; align-items: center; gap: 12px; }
.cu-progress { flex: 1; height: 5px; background: var(--surface-3); border-radius: 20px; overflow: hidden; }
.cu-bar { height: 100%; width: 0; background: linear-gradient(90deg, var(--accent), var(--accent-2)); border-radius: 20px; transition: width .3s cubic-bezier(.22,1,.36,1); }
.cu-count { font-size: 12.5px; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cu-top .cu-close { position: static; }

.cu-cards { position: relative; height: min(56vh, 460px); }
.cu-card {
  position: absolute; inset: 0; display: flex; flex-direction: column; gap: 12px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius);
  padding: 20px; box-shadow: var(--shadow); touch-action: pan-y; cursor: grab;
  transition: transform .28s cubic-bezier(.22,1,.36,1), opacity .28s ease;
}
.cu-card.behind { transform: scale(.955) translateY(14px); opacity: .5; pointer-events: none; box-shadow: none; }
.cu-card.dragging { transition: none; cursor: grabbing; }
.cu-card.fly-right { transform: translateX(140%) rotate(14deg); opacity: 0; }
.cu-card.fly-left  { transform: translateX(-140%) rotate(-14deg); opacity: 0; }
.cu-card.fly-up    { transform: translateY(-140%); opacity: 0; }
.cu-card.fly-down  { transform: translateY(140%) scale(.9); opacity: 0; }

.cu-card-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cu-card-msg { font-size: 16px; line-height: 1.55; color: var(--text); white-space: pre-wrap; word-break: break-word; }
.cu-meta-json { display: flex; flex-direction: column; gap: 4px; margin-top: auto; padding-top: 12px; border-top: 1px solid var(--border); }
.cu-kv { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; }
.cu-kv span:first-child { color: var(--faint); text-transform: capitalize; }
.cu-kv span:last-child { color: var(--muted); font-family: ui-monospace, "SF Mono", Menlo, monospace; text-align: right; word-break: break-word; }

/* swipe direction hints on the card */
.cu-swipe-badge {
  position: absolute; top: 18px; padding: 6px 12px; border-radius: 9px; font-size: 12px; font-weight: 800;
  text-transform: uppercase; letter-spacing: .05em; border: 2px solid currentColor; opacity: 0; transition: opacity .1s; pointer-events: none;
}
.cu-swipe-badge.right { right: 18px; color: var(--praise); transform: rotate(9deg); }
.cu-swipe-badge.left  { left: 18px; color: var(--muted); transform: rotate(-9deg); }
.cu-swipe-badge.up    { left: 50%; margin-left: -34px; color: var(--question); }
.cu-card[data-dir="right"] .cu-swipe-badge.right,
.cu-card[data-dir="left"]  .cu-swipe-badge.left,
.cu-card[data-dir="up"]    .cu-swipe-badge.up { opacity: 1; }

/* action bar */
.cu-actions { display: flex; justify-content: center; gap: 10px; }
.cu-act {
  display: inline-flex; flex-direction: column; align-items: center; gap: 4px; min-width: 62px;
  padding: 10px 8px; border-radius: 12px; border: 1px solid var(--border); background: var(--surface);
  color: var(--muted); font-size: 11px; font-weight: 600; cursor: pointer; transition: background .14s, color .14s, border-color .14s, transform .06s;
}
.cu-act svg { width: 20px; height: 20px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.cu-act:hover { background: var(--surface-2); color: var(--text); }
.cu-act:active { transform: scale(.94); }
.cu-act .k { font-size: 9.5px; opacity: .6; font-weight: 700; }
.cu-act.resolve:hover { color: var(--praise); border-color: rgba(70,212,147,.4); }
.cu-act.read:hover    { color: var(--accent-2); border-color: var(--ring); }
.cu-act.archive:hover { color: var(--text); }
.cu-act.skip:hover    { color: var(--question); border-color: rgba(88,166,255,.4); }
.cu-act.delete:hover  { color: var(--bug); border-color: rgba(255,107,107,.4); }
.cu-hint { text-align: center; font-size: 12px; color: var(--faint); }

/* done state */
.cu-done-check {
  width: 56px; height: 56px; margin: 0 auto 14px; border-radius: 50%; display: grid; place-items: center;
  background: rgba(70,212,147,.14); color: var(--praise); font-size: 28px; font-weight: 700;
  animation: popIn .5s cubic-bezier(.34,1.56,.64,1) both;
  box-shadow: 0 0 0 0 rgba(70,212,147,.35);
}
@keyframes popIn { from { transform: scale(.3); opacity: 0; } to { transform: none; opacity: 1; } }
.cu-panel.cu-done-panel { text-align: center; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after, .card { animation: none !important; transition: none !important; }
  .cu-card.fly-right, .cu-card.fly-left, .cu-card.fly-up, .cu-card.fly-down { transform: none; }
}

/* re-triggered on view switches for a soft cross-view transition */
.view-enter { animation: viewIn .35s cubic-bezier(.22,1,.36,1) both; }
@keyframes viewIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* Catch up's phone treatment moved into the one phone block below. */

/* ── modal (confirm / add-app) ── */
.modal-overlay {
  position: fixed; inset: 0; z-index: 120; display: grid; place-items: center; padding: 20px;
  background: rgba(6, 8, 13, .7); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  animation: cuFade .18s ease both;
}
.modal-card {
  width: 100%; max-width: 380px; background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 18px; padding: 24px; box-shadow: var(--shadow);
}
.modal-card h3 { margin: 0 0 6px; font-size: 18px; font-weight: 800; letter-spacing: -.01em; }
.modal-card p { margin: 0 0 18px; font-size: 13.5px; color: var(--muted); line-height: 1.5; }
.modal-label { display: block; font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); margin-bottom: 8px; }
.modal-card input[type="text"] {
  width: 100%; padding: 11px 13px; border-radius: 10px; background: var(--surface-2);
  border: 1px solid var(--border-strong); color: var(--text); font-size: 14px; margin-bottom: 16px;
}
.modal-card input[type="text"]:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
.modal-card textarea {
  width: 100%; padding: 11px 13px; border-radius: 10px; background: var(--surface-2);
  border: 1px solid var(--border-strong); color: var(--text); font-size: 14px; margin-bottom: 16px;
  font-family: inherit; resize: vertical; min-height: 90px; line-height: 1.5;
}
.modal-card textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
.modal-swatches { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; }
.modal-swatch { width: 30px; height: 30px; border-radius: 8px; border: 2px solid transparent; cursor: pointer; padding: 0; transition: transform .1s; }
.modal-swatch:hover { transform: scale(1.1); }
.modal-swatch.sel { border-color: #fff; box-shadow: 0 0 0 2px var(--bg); }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
.btn-danger-solid { background: var(--bug); border-color: transparent; color: #2a0d0d; font-weight: 700; }
.btn-danger-solid:hover { background: #ff8080; border-color: transparent; }

/* ── focus-visible rings (keyboard accessibility) ── */
.nav-item:focus-visible, .seg button:focus-visible, .cu-type:focus-visible, .cu-act:focus-visible,
.cu-close:focus-visible, .modal-swatch:focus-visible, .btn-ghost:focus-visible,
.badge:focus-visible, details.snippet summary:focus-visible, .card:focus-visible {
  outline: none; box-shadow: 0 0 0 3px var(--ring);
}

/* ── refresh spin ── */
#refresh.spin svg, #revRefresh.spin svg { animation: spin .6s cubic-bezier(.4,0,.2,1); transform-origin: center; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ── responsive ──
   One shell breakpoint. Everything that used to need its own pin (460, 560,
   620, 820, 900) is now handled by the components sizing themselves. */
@media (max-width: 759px) {
  /* The sidebar is a desktop object: phone chrome replaces it rather than
     folding it sideways into a scrolling row of mixed destinations and
     filters. */
  .layout { grid-template-columns: 1fr; }
  .sidebar { display: none; }
  .topbar { display: flex; }
  .tabbar { display: flex; }
  .chipbar.app-filter { display: flex; }
  :root { --tabbar: 60px; }

  .main { padding: var(--s4) var(--s3) calc(var(--s7) + var(--tabbar)); }
  .page-head { flex-wrap: wrap; align-items: flex-start; padding-bottom: var(--s3); }
  .page-head h2 { font-size: var(--t-xl); }
  /* 24px of panel padding either side is a lot of a 390px screen */
  .panel { padding: var(--s3); }
  .panel.flush { padding: var(--s2) var(--s3) var(--s1); }
  .mast { padding-block: var(--s4); }

  /* The scope segment wraps to a second row rather than scrolling sideways.
     Scrolling clipped "Deleted" mid-word with the scrollbar hidden, so on a
     phone the last scope looked like it didn't exist — for a filter, a hidden
     option is worse than a taller control. */
  .seg { flex-wrap: wrap; max-width: 100%; }
  .seg button { flex: 0 0 auto; }

  /* Card actions get a full-width row each rather than a squeezed line, and
     Delete stops being flushed away from the buttons it belongs with. */
  .card-actions { flex-wrap: wrap; }
  .card-actions .btn { flex: 1 1 auto; justify-content: center; min-height: 40px; }
  .card-actions .btn-danger { margin-left: 0 !important; }
  .app-card .app-actions .btn { flex: 1 1 auto; justify-content: center; }

  /* Catch up goes full-bleed: on a phone it is the whole screen, not a card
     floating on a dimmed one. */
  .cu { padding: 0; place-items: stretch; }
  .cu-stack { max-width: none; height: 100%; padding: var(--s3); justify-content: space-between; }
  .cu-cards { height: auto; flex: 1; }
  .cu-act { min-width: 0; flex: 1; }

  /* The toast has to clear the tab bar. */
  .toast { bottom: calc(var(--tabbar) + var(--s3) + env(safe-area-inset-bottom)); }
}

/* Touch / coarse-pointer devices have no hover: card actions must always show,
   otherwise Mark read / Resolve / Archive / Delete are invisible on iPhone. */
@media (hover: none) {
  .card-actions { opacity: 1; }
}

/* iOS Safari zooms the entire page when a focused form control has a font-size
   below 16px, and that zoom persists after blur. Tapping the login field was
   enough to leave the whole app magnified and clipped at both edges on iPhone
   (seen in the simulator, 2026-07-29). 16px is the threshold that prevents it —
   these must stay >= 16px on touch devices. */
@media (pointer: coarse) {
  .login-card input,
  .search input,
  .sel,
  .modal-card input[type="text"],
  input, select, textarea { font-size: 16px; }
}


/* ═════════════════════════════════════════════ portfolio (editorial) ══ */
/* Display figures use the same face as the rest of the app — the serif made
   this page read as a different product bolted on. Mono stays for the
   technical readouts. Mobile-first — every grid starts single-column. */
:root { --display: var(--head); --mono: "JetBrains Mono", ui-monospace, monospace; }

@keyframes riseIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.rise { animation: riseIn .5s cubic-bezier(.22,1,.36,1) both; }

/* ── masthead ── */
/* Now a panel like every other band; it used to be a bare band closed by a
   hairline, and its own "PORTFOLIO · <month>" rule said what the page title
   says. Only the month stamp survives, up in the page header. */
.mast { padding-block: 24px; }
.mast-stamp {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--faint); white-space: nowrap;
}
.mast-hero { display: grid; grid-template-columns: 1fr; gap: 20px; align-items: end; }
.mast-figure { min-width: 0; }
.mast-num {
  /* line-height was .84, which cropped the top of tall Fraunces numerals
     against the rule above — the figure read as a rendering bug. */
  font-family: var(--display); font-weight: 800; line-height: 1;
  font-size: clamp(54px, 11vw, 96px); letter-spacing: -.04em;
  background: linear-gradient(150deg, #fff 8%, var(--accent-2) 55%, var(--teal) 115%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  font-variant-numeric: tabular-nums;
}
.mast-num .unit { font-size: .38em; letter-spacing: -.01em; }
.mast-cap { font-size: 13px; color: var(--muted); margin-top: 12px; max-width: 46ch; line-height: 1.55; }
.mast-side { display: grid; grid-template-columns: repeat(auto-fit, minmax(105px, 1fr)); gap: 14px; }
.mfig { border-left: 2px solid var(--border-strong); padding-left: 11px; min-width: 0; }
.mfig .v { font-family: var(--display); font-weight: 700; font-size: 26px; line-height: 1.05; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.mfig .k { font-family: var(--mono); font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase; color: var(--faint); margin-top: 5px; }

/* ── share-of-installs bar ── */
.share { margin-bottom: var(--sec-gap); }
.share-bar { display: flex; height: 12px; border-radius: 999px; overflow: hidden; background: var(--surface-2); gap: 2px; }
.share-seg { min-width: 3px; transition: filter .16s; animation: segIn .7s cubic-bezier(.22,1,.36,1) both; transform-origin: left; }
@keyframes segIn { from { transform: scaleX(0); } to { transform: none; } }
.share-seg:hover { filter: brightness(1.35); }
.share-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 11px; font-size: 11.5px; color: var(--muted); }
.share-legend span { display: inline-flex; align-items: center; gap: 6px; }
.share-legend .sw { width: 8px; height: 8px; border-radius: 2px; }
.share-legend b { font-family: var(--mono); font-weight: 500; color: var(--faint); font-size: 11px; }

/* ── ledger rows ── */
.ledger { display: grid; gap: 2px; }
.lrow {
  position: relative; width: 100%; text-align: left; color: var(--text); cursor: pointer;
  background: transparent; border: none; border-top: 1px solid var(--border);
  padding: 16px 6px; display: grid; gap: 12px; border-radius: var(--radius-sm);
  transition: background .16s ease;
}
/* The section rule above is already a line — a row border under it reads as a
   double rule, and the panel edge closes the list at the bottom. */
.ledger .lrow:first-child { border-top: none; }
.lrow:hover { background: linear-gradient(90deg, color-mix(in srgb, var(--app) 12%, transparent), transparent 70%); }
.lrow:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--ring); }

.lrow-head { display: flex; align-items: center; gap: 12px; min-width: 0; }
.lrank { font-family: var(--mono); font-size: 11px; color: var(--faint); width: 22px; flex: 0 0 auto; }
.licon {
  width: 40px; height: 40px; border-radius: 11px; flex: 0 0 auto; display: grid; place-items: center;
  font-size: 19px; font-weight: 700; color: #fff;
  background: linear-gradient(145deg, var(--app), color-mix(in srgb, var(--app) 45%, #000));
  box-shadow: 0 5px 16px -8px var(--app);
  overflow: hidden;
}
/* A real app icon fills the tile — the colour gradient is only the fallback
   backdrop for an emoji or initial. */
.licon img, .sc-icon img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ltitles { min-width: 0; flex: 1; }
.lname { font-family: var(--display); font-size: 20px; font-weight: 700; letter-spacing: -.015em; line-height: 1.15; }
.ltag { font-size: 12.5px; color: var(--muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Version sits next to the name it belongs to. It used to be flushed to the
   far edge of the row, stranded a screen-width away from its app. */
.lnameline { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.lver { font-family: var(--mono); font-size: 11.5px; color: var(--faint); flex: 0 0 auto; }

/* proportional install bar — the comparison at a glance */
.lbar-wrap { display: flex; align-items: center; gap: 12px; }
.lbar { flex: 1; height: 7px; border-radius: 999px; background: var(--surface-2); overflow: hidden; min-width: 0; }
.lbar i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--app) 55%, #000), var(--app));
  animation: barGrow .8s cubic-bezier(.22,1,.36,1) both;
}
@keyframes barGrow { from { transform: scaleX(0); transform-origin: left; } to { transform: none; } }
.lnum { font-family: var(--mono); font-size: 13px; font-weight: 500; white-space: nowrap; flex: 0 0 auto; }
.lnum.none { color: var(--faint); font-size: 11.5px; }
.ldelta { font-family: var(--mono); font-size: 11px; white-space: nowrap; flex: 0 0 auto; }
.ldelta.up { color: var(--praise); } .ldelta.down { color: var(--bug); } .ldelta.flat { color: var(--faint); }

.lmeta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.feat-pill {
  font-size: 11.5px; font-weight: 500; padding: 3px 9px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--muted);
}
.feat-pill.more { color: var(--faint); background: transparent; border-style: dashed; }

.pill-status {
  display: inline-flex; align-items: center; gap: 5px; font-family: var(--mono);
  font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; border: 1px solid transparent; white-space: nowrap;
}
.pill-status .dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.pill-status.live { background: rgba(70,212,147,.12); color: var(--praise); border-color: rgba(70,212,147,.28); }
.pill-status.testflight { background: rgba(45,212,191,.12); color: var(--teal); border-color: rgba(45,212,191,.3); }
.pill-status.beta { background: rgba(245,181,68,.12); color: var(--idea); border-color: rgba(245,181,68,.28); }
.pill-status.development { background: rgba(88,166,255,.12); color: var(--question); border-color: rgba(88,166,255,.28); }
.pill-status.archived { background: rgba(148,160,184,.10); color: var(--other); border-color: rgba(148,160,184,.24); }

.sc-spark { display: block; width: 92px; height: 26px; flex: 0 0 auto; overflow: visible; }
/* draw the sparkline in (polyline carries pathLength="1") */
@media (prefers-reduced-motion: no-preference) {
  .sc-spark polyline { stroke-dasharray: 1; stroke-dashoffset: 1; animation: sparkDraw 1.1s .15s cubic-bezier(.4,0,.2,1) forwards; }
  .sc-spark polygon { opacity: 0; animation: sparkFill .6s .75s ease forwards; }
}
@keyframes sparkDraw { to { stroke-dashoffset: 0; } }
@keyframes sparkFill { to { opacity: 1; } }

/* ── shipping timeline ── */
.tl { display: grid; gap: 0; }
.tl-item { display: grid; grid-template-columns: 74px 1fr; gap: 14px; padding: 12px 6px; border-top: 1px solid var(--border); }
.tl-item:first-child { border-top: none; }
.tl-item:last-child { padding-bottom: 16px; }
.tl-date { font-family: var(--mono); font-size: 11px; color: var(--faint); padding-top: 3px; white-space: nowrap; }
.tl-body { min-width: 0; }
.tl-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.tl-app { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; font-size: 13.5px; }
.tl-app .sw { width: 8px; height: 8px; border-radius: 2px; flex: 0 0 auto; }
.tl-ver { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.tl-notes { font-size: 12.5px; color: var(--muted); margin-top: 3px; line-height: 1.55; }


@media (min-width: 760px) {
  .mast-hero { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 40px; }
  .lrow { grid-template-columns: minmax(0, 1fr) minmax(210px, 300px); align-items: center; column-gap: 28px; }
  .lrow-head { grid-column: 1; }
  .lmeta { grid-column: 1; }
  .lbar-wrap { grid-column: 2; grid-row: 1 / span 2; }
  /* With no install data there is no second column — don't reserve 300px of
     empty space and strand the row's content on the left. */
  .lrow:not(:has(.lbar-wrap)) { grid-template-columns: minmax(0, 1fr); }
}

/* ── earnings (portfolio) ── */
.earn { margin-bottom: var(--sec-gap); }
/* An absence shouldn't get the same visual weight as the real content */
/* Still deliberately lighter than a panel — but a bare line stranded between
   two panels reads as a stray, so it gets a rule to hang off. */
.earn-hint {
  font-size: 12.5px; color: var(--faint); line-height: 1.6;
  margin: calc(var(--sec-gap) * -0.4) 0 var(--sec-gap); padding-left: 13px;
  border-left: 2px solid var(--border-strong);
}
.earn-hint strong { color: var(--muted); font-weight: 600; }
.earn-grid { display: grid; gap: 20px; }
.earn-figs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; align-content: start; }
.earn-chart { display: flex; align-items: stretch; gap: 6px; height: 170px; }
.earn-col { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: flex-end; gap: 6px; }
.earn-bar {
  display: flex; flex-direction: column-reverse; overflow: hidden; min-height: 4px;
  border-radius: 5px 5px 2px 2px; transform-origin: bottom;
  animation: growUp .7s cubic-bezier(.22,1,.36,1) both; transition: filter .16s;
}
.earn-bar i { display: block; min-height: 2px; }
.earn-col:hover .earn-bar { filter: brightness(1.25); }
@keyframes growUp { from { transform: scaleY(0); } to { transform: none; } }
.earn-x { font-family: var(--mono); font-size: 9.5px; color: var(--faint); text-align: center; text-transform: uppercase; letter-spacing: .05em; }
@media (min-width: 760px) {
  .earn-grid { grid-template-columns: 210px minmax(0, 1fr); }
  .earn-figs { grid-template-columns: 1fr; gap: 16px; }
}

/* ════════════════════════════════════════════════════════════════ tags ══ */
/* Deliberately monochrome. A card already carries two colour encodings — the
   app accent on its left edge and the type badge — and a third would turn it
   into a swatch board. Tags are read, not scanned by colour. */
.tags { display: flex; flex-wrap: wrap; gap: var(--s1); margin-top: var(--s3); }
.tag {
  display: inline-flex; align-items: center; gap: var(--s1);
  padding: 2px var(--s2); border-radius: var(--radius-pill);
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--muted); font: inherit; font-size: 11px; font-weight: 600;
  cursor: pointer; transition: color .14s, border-color .14s, background .14s;
}
.tag:hover { color: var(--text); border-color: var(--border-strong); }
.tag .x { color: var(--faint); font-size: 12px; line-height: 1; }
.tag:hover .x { color: var(--bug); }
.tag .n { color: var(--faint); font-weight: 500; }
/* The add button is dashed and dim until hovered — present on every card, so
   it must not read as content. */
.tag.add { border-style: dashed; color: var(--faint); background: transparent; }
.tag.add:hover { color: var(--accent-2); border-color: var(--accent-2); }
.tagpicks { display: flex; flex-wrap: wrap; gap: var(--s1); margin-bottom: var(--s4); }

/* ── saved views in the sidebar ── */
.nav-view .nav-x {
  margin-left: auto; color: var(--faint); font-size: 15px; line-height: 1;
  padding: 0 var(--s1); border-radius: 4px; opacity: 0; transition: opacity .14s, color .14s;
}
.nav-view:hover .nav-x { opacity: 1; }
.nav-view .nav-x:hover { color: var(--bug); }

/* ── the selected card (j/k) ── */
.card.sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 14px 34px -20px rgba(0,0,0,.85); }
/* Selection implies the actions are about to be used, so stop hiding them. */
.card.sel .card-actions { opacity: 1; }

/* ══════════════════════════════════════════════════════ command palette ══ */
.palette {
  /* align-items: flex-start, or the card stretches to the full overlay height
     and one result sits above 500px of empty panel. */
  position: fixed; inset: 0; z-index: 140; display: flex; justify-content: center; align-items: flex-start;
  padding: 12vh var(--s4) var(--s4);
  background: rgba(6, 8, 13, .72); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  animation: cuFade .14s ease both;
}
.palette-card {
  width: 100%; max-width: 560px; max-height: 70vh; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg); box-shadow: var(--shadow); overflow: hidden;
  animation: paletteIn .18s cubic-bezier(.22,1,.36,1) both;
}
@keyframes paletteIn { from { opacity: 0; transform: translateY(-8px) scale(.98); } to { opacity: 1; transform: none; } }
.palette-input { display: flex; align-items: center; gap: var(--s3); padding: var(--s4); border-bottom: 1px solid var(--border); }
.palette-input .ico { color: var(--faint); }
.palette-input input {
  flex: 1; min-width: 0; border: none; background: none; color: var(--text);
  font: inherit; font-size: var(--t-lg); outline: none;
}
.palette-list { overflow-y: auto; padding: var(--s2); }
.palette-group {
  font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); padding: var(--s3) var(--s3) var(--s1);
}
.palette-item {
  display: flex; align-items: center; gap: var(--s3); width: 100%; text-align: left;
  padding: var(--s2) var(--s3); border: none; background: none; border-radius: var(--radius-sm);
  color: var(--muted); font: inherit; font-size: var(--t-sm); cursor: pointer;
}
.palette-item .pl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-item .ph { color: var(--faint); font-size: var(--t-xs); flex: 0 0 auto; }
/* Highlight follows the keyboard cursor, not the mouse — with both, the eye
   loses track of which one Enter will run. */
.palette-item.on { background: var(--accent-soft); color: #cfceff; }
.palette-none { color: var(--faint); font-size: var(--t-sm); padding: var(--s5); text-align: center; margin: 0; }

kbd {
  font: inherit; font-size: 10px; font-weight: 700; font-family: var(--mono);
  background: var(--surface-3); border: 1px solid var(--border); border-bottom-width: 2px;
  border-radius: 4px; padding: 1px 5px; color: var(--muted); white-space: nowrap;
}
.btn-kbd { margin-left: var(--s1); }
/* On a phone there is no ⌘K and the button is one more thing in a tight row. */
@media (max-width: 759px) { #openPalette { display: none; } }

.keys { display: grid; grid-template-columns: auto 1fr; gap: var(--s2) var(--s4); margin: 0 0 var(--s5); align-items: center; }
.keys dt { display: flex; gap: 3px; }
.keys dd { margin: 0; font-size: var(--t-sm); color: var(--muted); }

/* ══════════════════════════════════════════════════════════════ trends ══ */
/* Charts are plain elements, not a library: flex ratios do the scaling, so
   there is nothing to resize on a viewport change and no SVG viewBox to keep
   in sync. Colour comes from the same --bug/--idea/--praise/--question/--other
   tokens the badges use, so a colour means one thing everywhere in the app. */

.trend-figs { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: var(--s4); }
.tfig .v {
  font-family: var(--head); font-size: var(--t-2xl); font-weight: 800;
  letter-spacing: -.03em; line-height: 1; font-variant-numeric: tabular-nums;
}
.tfig .v .unit { font-size: .5em; font-weight: 700; color: var(--muted); margin-left: 1px; }
.tfig .k { font-size: var(--t-xs); color: var(--muted); margin-top: var(--s2); }
.tfig.warn .v { color: var(--idea); }

/* ── volume: stacked column per day ── */
.vchart { display: flex; gap: var(--s3); height: 168px; }
.vgrid {
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: 10px; color: var(--faint); font-variant-numeric: tabular-nums;
  text-align: right; min-width: 16px; padding-block: 1px;
}
/* The grid line is the panel's own edge treatment rather than a drawn rule —
   recessive by construction, so it can't compete with the bars. */
.vcols {
  flex: 1; display: flex; align-items: flex-end; gap: 2px; min-width: 0;
  border-bottom: 1px solid var(--border); border-top: 1px dashed var(--border);
}
.vcol { flex: 1; min-width: 0; height: 100%; display: flex; align-items: flex-end; cursor: default; }
.vcol:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--ring); border-radius: 3px; }
.vbar {
  width: 100%; min-height: 2px; display: flex; flex-direction: column-reverse; gap: 2px;
  border-radius: 4px 4px 0 0; overflow: hidden;
  animation: growUp .55s cubic-bezier(.22,1,.36,1) both; transform-origin: bottom;
}
.vcol:hover .vbar { filter: brightness(1.22); }
/* A 2px surface gap between stacked segments — this is what keeps adjacent
   type colours separable for a colour-blind reader. */
.vbar i { display: block; min-height: 3px; }
.vaxis {
  display: flex; justify-content: space-between; margin-top: var(--s2);
  font-size: 10px; color: var(--faint); font-family: var(--mono);
}

/* ── one legend, serving the column chart and the mix bar ── */
.tlegend { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); margin-top: var(--s4); font-size: var(--t-xs); color: var(--muted); }
.tlegend span { display: inline-flex; align-items: center; gap: 6px; text-transform: capitalize; }
.tlegend i { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; }
.tlegend b { color: var(--faint); font-variant-numeric: tabular-nums; font-weight: 600; }
.tlegend .off { opacity: .4; }

/* ── type mix ── */
.mixbar { display: flex; height: 14px; gap: 2px; border-radius: var(--radius-pill); overflow: hidden; background: var(--surface-2); }
.mixbar i { display: block; min-width: 4px; animation: segIn .7s cubic-bezier(.22,1,.36,1) both; transform-origin: left; }
.mixbar i:focus-visible { outline: none; filter: brightness(1.4); }
.mixlabels { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); margin-top: var(--s3); font-size: var(--t-xs); color: var(--muted); text-transform: capitalize; }
.mixlabels span { display: inline-flex; align-items: center; gap: 6px; }
.mixlabels i { width: 9px; height: 9px; border-radius: 2px; }
.mixlabels b { color: var(--text); font-variant-numeric: tabular-nums; }

/* ── per-app comparison ── */
/* One grid for the whole list, with the rows as `display: contents`. Per-row
   grids sized their own columns, so the track started wherever that app's name
   happened to end — two apps with the same count drew different-length bars,
   which is the one thing a comparison chart must never do. */
.abars {
  display: grid; grid-template-columns: minmax(90px, 150px) minmax(0, 1fr) auto;
  gap: var(--s3); align-items: center;
}
.abar { display: contents; }
.an { display: inline-flex; align-items: center; gap: var(--s2); font-size: var(--t-sm); font-weight: 600; min-width: 0; }
.an i { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; }
.atrack { height: 8px; border-radius: var(--radius-pill); background: var(--surface-2); overflow: hidden; }
.atrack i { display: block; height: 100%; border-radius: var(--radius-pill); animation: barGrow .8s cubic-bezier(.22,1,.36,1) both; }
.av { font-family: var(--mono); font-size: var(--t-sm); white-space: nowrap; }
.av .ab { color: var(--bug); font-size: 10px; margin-left: var(--s2); }

/* ── version table ── */
.vtable td { vertical-align: middle; }
.vtable .vver { font-family: var(--mono); color: var(--text); }
.vtable tr td:last-child { text-align: left; white-space: nowrap; }
.vshare {
  display: inline-block; width: 54px; height: 6px; border-radius: var(--radius-pill);
  background: var(--surface-3); overflow: hidden; margin-right: var(--s2); vertical-align: middle;
}
.vshare i { display: block; height: 100%; background: var(--bug); border-radius: var(--radius-pill); }
/* The build with the highest bug share is the one the table exists to find. */
.vtable tr.worst .vver { color: var(--bug); font-weight: 700; }

/* ── shared chart tooltip ── */
.charttip {
  position: fixed; z-index: 60; transform: translate(-50%, -120%);
  background: var(--surface-3); border: 1px solid var(--border-strong); color: var(--text);
  padding: 6px var(--s3); border-radius: var(--radius-sm); font-size: var(--t-xs);
  white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity .12s;
  box-shadow: var(--shadow); max-width: 90vw; overflow: hidden; text-overflow: ellipsis;
}
.charttip.show { opacity: 1; }

/* ── App Store reviews ── */
/* ── app filter chips ──
   Shared by the Reviews app picker and the Inbox's phone filter: both are the
   same question ("which app am I looking at?") and used to be two components. */
.chipbar { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s4); }
.chip-app {
  display: inline-flex; align-items: center; gap: var(--s2); padding: var(--s2) var(--s3);
  border-radius: var(--radius-pill); background: var(--surface); border: 1px solid var(--border);
  color: var(--muted); font-size: var(--t-sm); font-weight: 600; cursor: pointer;
  transition: background .14s, color .14s, border-color .14s;
}
.chip-app:hover { color: var(--text); background: var(--surface-2); }
.chip-app.on { background: var(--accent-soft); border-color: rgba(109, 106, 252, .4); color: #cfceff; }
.chip-app .dot { width: 8px; height: 8px; border-radius: 2px; }
.chip-app .n {
  font-size: 11px; font-weight: 700; color: var(--faint);
  background: var(--surface-3); padding: 0 6px; border-radius: var(--radius-pill); min-width: 18px; text-align: center;
}
.chip-app.on .n { background: rgba(109,106,252,.25); color: #cfceff; }
.rev-loading { color: var(--faint); padding: var(--s7); text-align: center; font-size: var(--t-sm); }

.rev-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(460px, 100%), 1fr)); gap: var(--s3); align-items: start; }
/* A review card is a .card; the app-coloured left accent belongs to feedback,
   where several apps share one list. Here the whole list is one app. */
.rev-card { border-left-width: 1px; border-left-color: var(--border); }
.rev-card:hover { transform: none; box-shadow: none; }
.rev-top { display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap; }
.rev-stars { color: var(--idea); letter-spacing: 2px; font-size: 14px; flex: 0 0 auto; }
.rev-stars.low { color: var(--bug); }
.rev-title { font-weight: 700; font-size: 14.5px; }
.rev-top .card-time { margin-left: auto; }
.rev-text { margin-top: 8px; font-size: 13.5px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
.rev-meta { margin-top: 8px; font-size: 12px; color: var(--faint); }

.rev-resp { margin-top: 12px; border-left: 3px solid var(--praise); background: rgba(70, 212, 147, .06); border-radius: 0 10px 10px 0; padding: 10px 12px; }
.rev-resp-head { display: flex; align-items: center; gap: 6px; font-size: 10.5px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; color: var(--praise); margin-bottom: 5px; }
.rev-pending { color: var(--idea); font-weight: 700; text-transform: none; letter-spacing: 0; }
.rev-resp-body { font-size: 13px; color: var(--muted); white-space: pre-wrap; line-height: 1.55; }

.rev-reply-zone { margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--border); }
.rev-reply-launch { display: flex; gap: 8px; flex-wrap: wrap; }
.rev-suggest { border-color: rgba(154, 123, 255, .45); color: var(--accent-2); }
.rev-suggest:hover { background: var(--accent-soft); border-color: rgba(154, 123, 255, .6); color: #cfceff; }
.rev-reply { display: grid; gap: 8px; }
.rev-reply select, .rev-reply textarea {
  width: 100%; padding: 9px 11px; border-radius: 9px; background: var(--surface-2);
  border: 1px solid var(--border-strong); color: var(--text); font-size: 13.5px; font-family: inherit;
}
.rev-reply textarea { resize: vertical; min-height: 84px; line-height: 1.5; }
.rev-reply select:focus, .rev-reply textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
.rev-send { display: flex; align-items: center; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
.rev-count { font-size: 11px; margin-right: auto; }

.rev-setup { max-width: 660px; }
.rev-setup h3 { margin: 0 0 var(--s2); font-size: var(--t-lg); font-family: var(--head); }
.rev-setup p { color: var(--muted); font-size: 13.5px; line-height: 1.6; margin: 0 0 6px; }
.rev-setup ol { margin: 12px 0; padding-left: 20px; color: var(--muted); font-size: 13.5px; line-height: 1.7; display: grid; gap: 8px; }
.rev-setup code { background: var(--surface-2); border: 1px solid var(--border); padding: 1px 6px; border-radius: 5px; font-family: var(--mono); font-size: 11.5px; color: var(--text); }

.tpl-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); margin-bottom: var(--s3); }
.tpl-head h3 { margin: 0; font-size: var(--t-lg); font-family: var(--head); }
.tpl-list { display: grid; gap: 10px; }
.tpl-row { display: flex; gap: 12px; align-items: flex-start; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px 14px; }
.tpl-info { flex: 1; min-width: 0; }
.tpl-title { font-weight: 700; font-size: 13.5px; }
.tpl-kws { font-family: var(--mono); font-size: 10.5px; color: var(--accent-2); margin-top: 2px; }
.tpl-body {
  font-size: 12.5px; color: var(--muted); margin-top: 5px; white-space: pre-wrap; line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.tpl-acts { display: flex; gap: 6px; flex: 0 0 auto; flex-wrap: wrap; }

/* ── catch-up confetti ── */
.cu-confetti { position: absolute; inset: 0; overflow: hidden; pointer-events: none; border-radius: inherit; }
.cu-confetti i { position: absolute; top: -12px; width: 7px; height: 11px; border-radius: 2px; opacity: 0; animation: confetti 1.7s ease-in .12s both; }
.cu-confetti i:nth-child(1)  { left: 5%;  background: var(--accent);   animation-delay: .10s; }
.cu-confetti i:nth-child(2)  { left: 14%; background: var(--praise);   animation-delay: .34s; }
.cu-confetti i:nth-child(3)  { left: 23%; background: var(--idea);     animation-delay: .18s; }
.cu-confetti i:nth-child(4)  { left: 32%; background: var(--teal);     animation-delay: .46s; }
.cu-confetti i:nth-child(5)  { left: 41%; background: var(--accent-2); animation-delay: .08s; }
.cu-confetti i:nth-child(6)  { left: 50%; background: var(--question); animation-delay: .30s; }
.cu-confetti i:nth-child(7)  { left: 59%; background: var(--praise);   animation-delay: .22s; }
.cu-confetti i:nth-child(8)  { left: 68%; background: var(--idea);     animation-delay: .42s; }
.cu-confetti i:nth-child(9)  { left: 77%; background: var(--accent);   animation-delay: .14s; }
.cu-confetti i:nth-child(10) { left: 86%; background: var(--teal);     animation-delay: .38s; }
.cu-confetti i:nth-child(11) { left: 93%; background: var(--accent-2); animation-delay: .26s; }
.cu-confetti i:nth-child(12) { left: 47%; background: var(--bug);      animation-delay: .52s; }
@keyframes confetti {
  0% { opacity: 0; transform: translateY(0) rotate(0deg); }
  12% { opacity: 1; }
  100% { opacity: 0; transform: translateY(320px) rotate(560deg); }
}

/* ── detail sheet ── */
.sheet {
  position: fixed; inset: 0; z-index: 130; display: flex; align-items: flex-end; justify-content: center;
  background: rgba(6, 8, 13, .72); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  animation: cuFade .18s ease both;
}
.sheet-card {
  width: 100%; max-height: 92vh; overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: 20px 20px 0 0; padding: 20px 18px calc(20px + env(safe-area-inset-bottom));
  box-shadow: var(--shadow); animation: sheetUp .22s cubic-bezier(.32,.72,0,1) both;
}
@keyframes sheetUp { from { transform: translateY(18px); opacity: .6; } to { transform: none; opacity: 1; } }

.sheet-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 16px; }
.sc-icon {
  width: 46px; height: 46px; border-radius: 13px; flex: 0 0 auto; display: grid; place-items: center;
  font-size: 22px; font-weight: 700; color: #fff;
  background: linear-gradient(145deg, var(--app, var(--accent)), color-mix(in srgb, var(--app, var(--accent)) 45%, #000));
  box-shadow: 0 6px 18px -8px var(--app, var(--accent));
  overflow: hidden;
}
.sc-titles { min-width: 0; flex: 1; }
.sc-name { font-family: var(--display); font-size: 21px; font-weight: 600; letter-spacing: -.015em; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.sc-tagline { font-size: 13px; color: var(--muted); margin-top: 3px; }
/* The sheet's metrics are .stat like everywhere else; only the surface
   differs, because inside a sheet the page background reads as a hole. */
.sc-metrics { margin-bottom: var(--s2); }
.sc-metrics .stat { background: var(--surface-2); }
.sc-metric .ldelta { margin-top: 2px; display: block; }
.sheet-close {
  margin-left: auto; flex: 0 0 auto; width: 34px; height: 34px; border-radius: 9px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--muted);
  font-size: 20px; line-height: 1; display: grid; place-items: center;
}
.sheet-close:hover { color: var(--text); background: var(--surface-3); }
.sheet-section { margin-top: 20px; }
.sheet-section > h4 {
  margin: 0 0 10px; font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--faint);
}
.sheet-desc { font-size: 13.5px; color: var(--muted); line-height: 1.6; white-space: pre-wrap; }

.feat-list { display: grid; gap: 8px; }
.feat-row { display: flex; gap: 10px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; }
.feat-row .tick { color: var(--praise); flex: 0 0 auto; margin-top: 1px; }
.feat-row .ft { font-size: 13px; font-weight: 700; }
.feat-row .fd { font-size: 12.5px; color: var(--muted); margin-top: 2px; line-height: 1.5; }

/* release timeline */
.rel { position: relative; padding-left: 20px; padding-bottom: 16px; }
.rel:last-child { padding-bottom: 0; }
.rel::before { content: ""; position: absolute; left: 4px; top: 5px; width: 9px; height: 9px; border-radius: 50%; background: var(--app, var(--accent)); }
.rel:not(:last-child)::after { content: ""; position: absolute; left: 8px; top: 17px; bottom: 0; width: 1px; background: var(--border-strong); }
.rel-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.rel-ver { font-size: 13.5px; font-weight: 800; font-variant-numeric: tabular-nums; }
.rel-latest { font-size: 9.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--praise); background: rgba(70,212,147,.12); padding: 2px 6px; border-radius: 5px; }
.rel-date { font-size: 11.5px; color: var(--faint); margin-left: auto; }
.rel-notes { font-size: 12.5px; color: var(--muted); margin-top: 4px; white-space: pre-wrap; line-height: 1.55; }
.rel-del { background: none; border: none; color: var(--faint); cursor: pointer; font-size: 11px; padding: 2px 4px; }
.rel-del:hover { color: var(--bug); }

/* metric history table — scrolls inside its own box so narrow phones never
   push the sheet sideways */
.mwrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.mtable { width: 100%; min-width: 260px; border-collapse: collapse; font-size: 12.5px; }
.mtable th { text-align: left; font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); font-weight: 700; padding: 0 8px 7px 0; }
.mtable td { padding: 7px 8px 7px 0; border-top: 1px solid var(--border); font-variant-numeric: tabular-nums; }
.mtable tr td:last-child { text-align: right; }

/* forms inside the sheet */
.sheet-form { display: grid; gap: 10px; }
.sheet-form label { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); }
.sheet-form input, .sheet-form textarea, .sheet-form select {
  width: 100%; padding: 10px 12px; border-radius: 10px; background: var(--surface-2);
  border: 1px solid var(--border-strong); color: var(--text); font-size: 14px; /* 14px+ stops iOS zoom-on-focus */
}
.sheet-form textarea { resize: vertical; min-height: 74px; line-height: 1.5; }
.sheet-form input:focus, .sheet-form textarea:focus, .sheet-form select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring);
}
.field-row { display: grid; grid-template-columns: 1fr; gap: 10px; }
.sheet-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.sheet-actions .btn { flex: 1 1 auto; justify-content: center; min-height: 44px; }

/* Paired-field rows fit two columns whenever the sheet is wide enough to hold
   them; that is a property of the row, not of the viewport. */
.field-row { grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr)); }
.showcase-grid { grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }

@media (min-width: 760px) {
  /* Above the shell breakpoint the sheet stops being a bottom sheet and
     becomes a centred dialog. */
  .sheet { align-items: center; padding: var(--s5); }
  .sheet-card { max-width: 620px; border-radius: var(--radius-lg); padding: var(--s5); }
}

/* Explanatory note under a profile field */
.field-hint { font-size: 11.5px; color: var(--faint); line-height: 1.55; margin: 5px 0 0; }

/* Undo affordance inside the toast */
.toast-undo {
  margin-left: 12px; border: none; background: none; padding: 0; cursor: pointer;
  font: inherit; font-weight: 700; color: var(--accent-2); text-decoration: underline;
}

/* Deleted-scope cards read as a countdown, not a graveyard */
.purge-in { color: var(--bug); opacity: .85; font-weight: 600; }
.card.status-deleted, [data-restore] { }


/* ── measurements ── */
.meastable td { white-space: nowrap; }
.mflag { display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; padding: 1px 6px; border-radius: 999px; background: var(--border); color: var(--faint); margin-right: 4px; }
.mq { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; margin-left: 4px; }
.mq-good { color: var(--praise, #3fb950); }
.mq-ok { color: var(--idea, #d29922); }
.mq-poor { color: var(--bug, #f85149); }
.meas-json { max-height: 360px; overflow: auto; font-size: 11px; line-height: 1.4; margin: 0; padding: 10px; background: var(--bg-2, rgba(127,127,127,.08)); border-radius: 8px; white-space: pre; }
