@viberaven/cli 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -11558,7 +11558,7 @@ var VERSION;
11558
11558
  var init_version = __esm({
11559
11559
  "src/version.ts"() {
11560
11560
  "use strict";
11561
- VERSION = "1.4.0";
11561
+ VERSION = "1.4.2";
11562
11562
  }
11563
11563
  });
11564
11564
 
@@ -14872,6 +14872,8 @@ var init_providerLogos = __esm({
14872
14872
  aws: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#FF9900" d="M6.763 10.582c4.95-2.283 10.563-2.283 15.475 0 .532.243.848.741.848 1.256 0 .532-.323 1.026-.85 1.256-4.912 2.283-10.525 2.283-15.474 0-.528-.23-.851-.724-.851-.279 0-.544.098-.754.243-4.95 2.283-10.563 2.283-15.475 0-.532-.243-.848-.741-.848-1.256 0-.532.323-1.026.85-1.256.228-.098.492-.196.754-.196.279 0 .544.098.754.243Z"/><path fill="#FF9900" d="M12 3.65c-2.772 0-5.027 1.147-5.027 2.553S9.228 8.756 12 8.756s5.027-1.147 5.027-2.553S14.772 3.65 12 3.65Z"/></svg>',
14873
14873
  sentry: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 22 20H2L12 3Zm0 5.1L6.8 17h2.1l3.1-5.3 3.1 5.3h2.1L12 8.1Z"/></svg>',
14874
14874
  posthog: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#1D4AFF" d="M5.2 6.1h8.2c3 0 5.3 2 5.3 5.1 0 3.2-2.3 5.2-5.3 5.2H5.2V6.1Z"/><path fill="#F54E00" d="M4.1 4h4.2a2.35 2.35 0 0 1 0 4.7H4.1V4Z"/><circle cx="13.7" cy="11.75" r="1.1" fill="#F9BD2B"/><path fill="#F54E00" d="m16.5 9.35 2-1.15.85 1.3-2.15 1.3-.7-1.45Zm.38 4.78 2.42.86-.78 1.6-2.4-1.02.76-1.44Z"/></svg>',
14875
+ resend: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#FFFFFF" d="M14.679 0c4.648 0 7.413 2.765 7.413 6.434s-2.765 6.434-7.413 6.434H12.33L24 24h-8.245l-8.88-8.44c-.636-.588-.93-1.273-.93-1.86 0-.831.587-1.565 1.713-1.883l4.574-1.224c1.737-.465 2.936-1.81 2.936-3.572 0-2.153-1.761-3.4-3.939-3.4H0V0z"/></svg>',
14876
+ upstash: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#00E9A3" d="M13.8027 0C11.193 0 8.583.9952 6.5918 2.9863c-3.9823 3.9823-3.9823 10.4396 0 14.4219 1.9911 1.9911 5.2198 1.9911 7.211 0 1.991-1.9911 1.991-5.2198 0-7.211L12 12c.9956.9956.9956 2.6098 0 3.6055-.9956.9955-2.6099.9955-3.6055 0-2.9866-2.9868-2.9866-7.8297 0-10.8164 2.9868-2.9868 7.8297-2.9868 10.8164 0l1.8028-1.8028C19.0225.9952 16.4125 0 13.8027 0zM12 12c-.9956-.9956-.9956-2.6098 0-3.6055.9956-.9955 2.6098-.9955 3.6055 0 2.9867 2.9868 2.9867 7.8297 0 10.8164-2.9867 2.9868-7.8297 2.9868-10.8164 0l-1.8028 1.8028c3.9823 3.9822 10.4396 3.9822 14.4219 0 3.9823-3.9824 3.9823-10.4396 0-14.4219-.9956-.9956-2.3006-1.4922-3.6055-1.4922-1.3048 0-2.6099.4966-3.6054 1.4922-1.9912 1.9912-1.9912 5.2198 0 7.211z"/></svg>',
14875
14877
  playwright: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#2EAD33" d="M8.4 8.2 5.4 17.2h2.3l.8-2.8h2.7l.8 2.8h2.3L11.6 8.2H8.4Zm1.1 4.8.8-2.4.8 2.4H9.5Z"/><path fill="#E2574C" d="M15.6 8.2 12.6 17.2h2.3l.8-2.8h2.7l.8 2.8h2.3L18.8 8.2h-3.2Zm1.1 4.8.8-2.4.8 2.4h-1.6Z"/><ellipse fill="#2EAD33" cx="9.5" cy="13.1" rx="1.1" ry="1.4"/><ellipse fill="#E2574C" cx="16.5" cy="13.1" rx="1.1" ry="1.4"/></svg>',
14876
14878
  logrocket: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="5" fill="#764ABC"/><path fill="#FFFFFF" d="M12 7.4c2.7 0 4.9 2.2 4.9 4.9s-2.2 4.9-4.9 4.9-4.9-2.2-4.9-4.9 4.9-4.9 4.9-4.9Zm0 1.9a3 3 0 1 0 0 6 3 3 0 0 0 0-6Zm-3.2 7.4h6.4v1.5H8.8v-1.5Z"/></svg>',
14877
14879
  figma: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 3h3.5v6H8.5a3 3 0 1 1 0-6Zm3.5 6h3.5a3 3 0 1 0 0-6H12v6Zm0 0h3.5a3 3 0 1 1 0 6H12V9Zm-3.5 0H12v6H8.5a3 3 0 1 1 0-6ZM8.5 15H12v2.5A3.5 3.5 0 1 1 8.5 15Z"/></svg>',
@@ -16029,10 +16031,10 @@ const providerMarkAssets = {
16029
16031
  };
16030
16032
 
16031
16033
  const mascotAssets = {
16032
- idle: '/report/assets/viberaven-mascot.png?v=raven-cards-2',
16033
- fixing: '/report/assets/viberaven-mascot.png?v=raven-cards-2',
16034
- verified: '/report/assets/viberaven-mascot.png?v=raven-cards-2',
16035
- alert: '/report/assets/viberaven-mascot.png?v=raven-cards-2',
16034
+ idle: '/manga/raven-mark.png?v=manga1',
16035
+ fixing: '/manga/raven-mark.png?v=manga1',
16036
+ verified: '/manga/raven-mark.png?v=manga1',
16037
+ alert: '/manga/raven-mark.png?v=manga1',
16036
16038
  };
16037
16039
 
16038
16040
  const providerDashboardUrls = {
@@ -16268,10 +16270,22 @@ function providerAssetSrc(provider) {
16268
16270
  return providerMarkAssets[provider.id] || providerAssets[provider.id] || providerAssets.supabase;
16269
16271
  }
16270
16272
 
16273
+ function providerIconMarkup(provider) {
16274
+ const iconHtml = provider && provider.iconHtml ? String(provider.iconHtml) : '';
16275
+ if (iconHtml.trim()) return iconHtml;
16276
+ // Fallback must still be the PROVIDER icon, never the raven card-art. Build the real logo
16277
+ // from the provider id so a cold/offline boot shows Supabase/Vercel/etc, not a generic raven.
16278
+ const id = provider && provider.id ? String(provider.id).toLowerCase().replace(/[^a-z0-9]/g, '') : '';
16279
+ if (id) {
16280
+ const color = String(areaEnergy(provider.area) || '#e8c15a').replace('#', '');
16281
+ return '<img class="provider-logo__img" src="https://cdn.simpleicons.org/' + id + '/' + color + '" alt="" decoding="async" data-provider-logo-key="' + id + '" />';
16282
+ }
16283
+ return '<img src="' + providerAssetSrc(provider) + '" alt="" />';
16284
+ }
16285
+
16271
16286
  function ProviderToken(provider, extraClass) {
16272
16287
  const className = 'vr-provider-token' + (extraClass ? ' ' + extraClass : '');
16273
- const iconHtml = provider && provider.iconHtml ? String(provider.iconHtml) : '';
16274
- return '<span class="' + className + '">' + (iconHtml.trim() ? iconHtml : '<img src="' + providerAssetSrc(provider) + '" alt="" />') + '</span>';
16288
+ return '<span class="' + className + '">' + providerIconMarkup(provider) + '</span>';
16275
16289
  }
16276
16290
 
16277
16291
  function areaEnergy(area) {
@@ -16294,26 +16308,53 @@ function gradeMark(tier) {
16294
16308
  return '&#9679;';
16295
16309
  }
16296
16310
 
16311
+ // Full manga card art (shipped in assets/manga/cards). Providers with baked
16312
+ // art render the image as the whole face + a holographic foil overlay; the
16313
+ // rest fall back to the CSS-built card anatomy below.
16314
+ const MANGA_CARD_ARTS = { supabase: 1, vercel: 1, github: 1, posthog: 1, stripe: 1, resend: 1, sentry: 1, upstash: 1, clerk: 1, authjs: 1 };
16315
+ function cardArtSrc(provider) {
16316
+ const id = provider && provider.id ? String(provider.id).toLowerCase().replace(/[^a-z0-9]/g, '') : '';
16317
+ // ?v bump busts stale browser cache when a card art is redrawn
16318
+ return MANGA_CARD_ARTS[id] ? '/manga/cards/' + id + '.webp?v=5' : '';
16319
+ }
16320
+
16297
16321
  function TradingCard(provider, variant, options) {
16298
16322
  const opts = options || {};
16299
16323
  const size = variant === 'dealt' ? 'is-dealt' : variant === 'map' ? 'is-map' : 'is-hand';
16300
16324
  const grade = provider.grade || { value: null, tier: 'facedown', label: 'FACE DOWN' };
16301
16325
  const foil = escapeHtml(provider.id);
16302
16326
  const placedClass = opts.placed ? ' is-placed' : '';
16327
+ const tagOpen = opts.asDiv ? 'div' : 'button type="button"';
16328
+ const tagClose = opts.asDiv ? 'div' : 'button';
16303
16329
  if (grade.tier === 'facedown' && !opts.forceFace) {
16304
- return '<' + (opts.asDiv ? 'div' : 'button type="button"') + ' class="vr-tcard is-back ' + size + placedClass + '"' +
16330
+ return '<' + tagOpen + ' class="vr-tcard is-back ' + size + placedClass + '"' +
16305
16331
  (opts.asDiv ? '' : ' data-action="deal-provider" data-provider="' + foil + '" aria-label="Flip ' + escapeHtml(provider.name) + ' \u2014 run its first checks" title="' + escapeHtml(provider.name) + ' \xB7 face down \u2014 click to flip"') +
16306
16332
  (opts.style ? ' style="' + opts.style + '"' : '') + '>' +
16307
16333
  '<span class="vr-tcard-backart"><img src="' + mascotAssets.idle + '" alt="" /></span>' +
16308
16334
  '<span class="vr-tcard-backname">' + escapeHtml(provider.name) + '</span>' +
16309
- '</' + (opts.asDiv ? 'div' : 'button') + '>';
16335
+ '</' + tagClose + '>';
16310
16336
  }
16311
16337
  const flawless = grade.tier === 'flawless';
16312
- return '<' + (opts.asDiv ? 'div' : 'button type="button"') + ' class="vr-tcard ' + size + placedClass + (flawless ? ' is-flawless' : '') + '" data-foil="' + foil + '"' +
16338
+ const artSrc = cardArtSrc(provider);
16339
+ if (artSrc) {
16340
+ // full-art face: the image IS the card; holo foil + glare ride on top,
16341
+ // grade chip and RAVEN GRADE strip overlay the corners.
16342
+ return '<' + tagOpen + ' class="vr-tcard has-art ' + size + placedClass + (flawless ? ' is-flawless' : '') + '" data-foil="' + foil + '"' +
16343
+ (opts.asDiv ? '' : ' data-action="deal-provider" data-provider="' + foil + '" aria-label="Deal ' + escapeHtml(provider.name) + ' \u2014 checks run instantly in chat" title="' + escapeHtml(provider.name) + ' \xB7 ' + escapeHtml(grade.label) + '"') +
16344
+ (opts.style ? ' style="' + opts.style + '"' : '') + '>' +
16345
+ '<img class="vr-tcard-artfull" src="' + artSrc + '" alt="' + escapeHtml(provider.name) + '" draggable="false" />' +
16346
+ '<span class="vr-tcard-holofoil" aria-hidden="true"></span>' +
16347
+ '<span class="vr-tcard-glare" aria-hidden="true"></span>' +
16348
+ (opts.placed ? '<span class="vr-tcard-unplace" data-action="unplace-card" data-unplace="' + foil + '" title="Remove from the table" role="button" aria-label="Remove from the table">&#10005;</span>' : '') +
16349
+ '<span class="vr-tcard-hp is-onart"><span class="vr-tcard-energy" style="--vr-energy:' + areaEnergy(provider.area) + '"></span><i>' + (grade.value == null ? '?' : grade.value) + '</i></span>' +
16350
+ '<span class="vr-tcard-gradebar">' + (flawless ? '&#10038; ' : '') + 'RAVEN GRADE &middot; ' + escapeHtml(grade.label) + '</span>' +
16351
+ '</' + tagClose + '>';
16352
+ }
16353
+ return '<' + tagOpen + ' class="vr-tcard ' + size + placedClass + (flawless ? ' is-flawless' : '') + '" data-foil="' + foil + '"' +
16313
16354
  (opts.asDiv ? '' : ' data-action="deal-provider" data-provider="' + foil + '" aria-label="Deal ' + escapeHtml(provider.name) + ' \u2014 checks run instantly in chat" title="' + escapeHtml(provider.name) + ' \xB7 ' + escapeHtml(grade.label) + '"') +
16314
16355
  (opts.style ? ' style="' + opts.style + '"' : '') + '>' +
16315
16356
  '<span class="vr-tcard-sheen" aria-hidden="true"></span>' +
16316
- (opts.placed ? '<span class="vr-tcard-unplace" data-action="unplace-card" data-unplace="' + foil + '" title="Return to your hand" aria-hidden="true">&#10005;</span>' : '') +
16357
+ (opts.placed ? '<span class="vr-tcard-unplace" data-action="unplace-card" data-unplace="' + foil + '" title="Remove from the table" role="button" aria-label="Remove from the table">&#10005;</span>' : '') +
16317
16358
  '<span class="vr-tcard-inner">' +
16318
16359
  '<span class="vr-tcard-head"><b>' + escapeHtml(provider.name) + '</b><span class="vr-tcard-hp"><span class="vr-tcard-energy" style="--vr-energy:' + areaEnergy(provider.area) + '" title="' + escapeHtml(String(provider.area || '')) + '"></span><i>' + (grade.value == null ? '?' : grade.value) + '</i></span></span>' +
16319
16360
  '<span class="vr-tcard-artwin"><span class="vr-tcard-holo" aria-hidden="true"></span>' + ProviderToken(provider, 'is-tcard') + '</span>' +
@@ -16426,6 +16467,15 @@ function dealProviderCard(providerId) {
16426
16467
  chat.messages.push(message);
16427
16468
  demo.notice = provider.name + ' dealt into chat \u2014 checks running.';
16428
16469
  render({ scrollToBottom: chatIndex });
16470
+ const dealtAt = Date.now();
16471
+ window.requestAnimationFrame(animateDealtCardIntoChat);
16472
+ // The local check returns almost instantly; hold the results render until the
16473
+ // deal animation has played, so it is actually visible (otherwise the full
16474
+ // re-render replaces the animating card before you see it).
16475
+ const renderAfterDeal = () => {
16476
+ const wait = Math.max(0, 720 - (Date.now() - dealtAt));
16477
+ window.setTimeout(() => render(), wait);
16478
+ };
16429
16479
  fetch('/api/provider-check', {
16430
16480
  method: 'POST',
16431
16481
  headers: { 'content-type': 'application/json' },
@@ -16441,12 +16491,12 @@ function dealProviderCard(providerId) {
16441
16491
  message.done = true;
16442
16492
  const idx = providers.findIndex((item) => item.id === providerId);
16443
16493
  if (idx >= 0 && payload.provider) providers[idx] = normalizeProvider(payload.provider);
16444
- render();
16494
+ renderAfterDeal();
16445
16495
  })
16446
16496
  .catch(() => {
16447
16497
  message.error = 'Could not reach the local server \u2014 is the Studio still running?';
16448
16498
  message.done = true;
16449
- render();
16499
+ renderAfterDeal();
16450
16500
  });
16451
16501
  }
16452
16502
 
@@ -17501,6 +17551,17 @@ function runChatTask(kind, promptOverride) {
17501
17551
  clearChatTaskTimer();
17502
17552
  const chatIndex = Math.max(0, Math.min(demo.pickerChatIndex || 0, demo.activeChatCount - 1));
17503
17553
  const chat = chatContext(chatIndex);
17554
+ // Product contract: installed is NOT enough \u2014 force Test connection before
17555
+ // real agent control. If the selected CLI has not passed a connection probe,
17556
+ // route to the Terminal setup instead of spawning it.
17557
+ if (chat.cliId && chat.cliId !== 'terminal' && !cliAgentReady(chat.cliId)) {
17558
+ const agentLabel = (cliAgents.find((item) => item.id === chat.cliId)?.label) || chat.cliId;
17559
+ demo.pickerChatIndex = chatIndex;
17560
+ demo.notice = 'Connect ' + agentLabel + ' first \u2014 run Test connection in the Terminal tab, then send your mission.';
17561
+ selectTerminalCli(chat.cliId);
17562
+ render();
17563
+ return;
17564
+ }
17504
17565
  const mission = resolveQuickActionMission(kind, chatIndex, promptOverride);
17505
17566
  const meta = taskMeta(kind, chatIndex);
17506
17567
  const attachedProviderId = chat.providerId || null;
@@ -17531,6 +17592,7 @@ function runChatTask(kind, promptOverride) {
17531
17592
  error: '',
17532
17593
  providerId: taskProviderId,
17533
17594
  releaseId: taskReleaseId,
17595
+ startedAt: Date.now(),
17534
17596
  };
17535
17597
  if (chatIndex === 0) rememberRecentChat(label, provider, release, chat);
17536
17598
  chat.providerId = null;
@@ -17541,6 +17603,7 @@ function runChatTask(kind, promptOverride) {
17541
17603
  demo.activeStepId = kind === 'verify' ? 'verify' : kind === 'provider' ? 'provider' : 'agent';
17542
17604
  demo.notice = meta.notice;
17543
17605
  render({ scrollToBottom: chatIndex });
17606
+ startTaskElapsedTimer();
17544
17607
  const finish = (message, isError, cardsPayload) => {
17545
17608
  const taskContext = chat.activeTask || {};
17546
17609
  const responseText = isError
@@ -18229,10 +18292,20 @@ function QuickstartModal() {
18229
18292
 
18230
18293
  function DiscoveryModal() {
18231
18294
  const found = providers.filter((provider) => provider.state !== 'not_detected');
18232
- const rankFor = (tier) => tier === 'flawless' ? ['legendary', 'LEGENDARY'] : tier === 'clear' ? ['epic', 'EPIC'] : ['rare', 'RARE'];
18233
- const cards = found.map((provider, index) => {
18295
+ const hashRank = (id) => { let h = 0; for (let i = 0; i < id.length; i += 1) h = (h * 31 + id.charCodeAt(i)) >>> 0; return h; };
18296
+ const rankFor = (provider) => {
18234
18297
  const tier = provider.grade ? provider.grade.tier : 'review';
18235
- const rank = rankFor(tier);
18298
+ if (tier === 'flawless') return ['legendary', 'LEGENDARY'];
18299
+ if (tier === 'clear') return ['epic', 'EPIC'];
18300
+ // ungraded providers still deal a real pack spread (common..legendary)
18301
+ const r = hashRank(provider.id) % 12;
18302
+ if (r === 0) return ['legendary', 'LEGENDARY'];
18303
+ if (r < 4) return ['epic', 'EPIC'];
18304
+ if (r < 10) return ['rare', 'RARE'];
18305
+ return ['common', 'COMMON'];
18306
+ };
18307
+ const cards = found.map((provider, index) => {
18308
+ const rank = rankFor(provider);
18236
18309
  return '<div class="vr-discover-card' + (demo.flippedCards['disc:' + provider.id] ? ' is-flipped' : '') + '" data-flip-card data-flip-id="disc:' + escapeHtml(provider.id) + '" data-rank="' + rank[0] + '" style="--vr-i:' + String(index) + '">' +
18237
18310
  '<span class="vr-flip-inner">' +
18238
18311
  '<span class="vr-flip-back vr-tcard is-back is-dealt" data-action="flip-discover" role="button" tabindex="0" aria-label="Flip to reveal ' + escapeHtml(provider.name) + '">' +
@@ -18715,18 +18788,40 @@ function ChatMissionCards(cards, chatIndex, options, usedActions) {
18715
18788
  return html;
18716
18789
  }
18717
18790
 
18791
+ let taskElapsedTimer = null;
18792
+ function formatElapsed(ms) {
18793
+ const s = Math.max(0, Math.floor(ms / 1000));
18794
+ return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
18795
+ }
18796
+ function anyLaneWorking() {
18797
+ return (demo.chatContexts || []).some((c) => c && c.activeTask && c.activeTask.phase === 'working' && c.activeTask.startedAt);
18798
+ }
18799
+ // Live heartbeat: while the agent runs, tick the elapsed clock every second so it never looks frozen.
18800
+ function startTaskElapsedTimer() {
18801
+ if (taskElapsedTimer) return;
18802
+ taskElapsedTimer = window.setInterval(() => {
18803
+ if (!anyLaneWorking()) { window.clearInterval(taskElapsedTimer); taskElapsedTimer = null; return; }
18804
+ document.querySelectorAll('[data-task-elapsed]').forEach((el) => {
18805
+ const start = Number(el.getAttribute('data-task-elapsed')) || 0;
18806
+ if (start) el.textContent = formatElapsed(Date.now() - start);
18807
+ });
18808
+ }, 1000);
18809
+ }
18810
+
18718
18811
  function ChatTaskAnimation(chatIndex, compact) {
18719
18812
  const chat = chatContext(chatIndex);
18720
18813
  const task = chat.activeTask;
18721
18814
  if (!task || task.phase !== 'working') return '';
18722
18815
  const meta = taskMeta(task.kind);
18723
18816
  const statusText = task.kind === 'verify' ? 'Verifying...' : task.kind === 'diff' ? 'Reviewing...' : task.kind === 'explain' ? 'Explaining...' : 'Working...';
18724
- const thoughtText = 'reading launch context';
18817
+ const thoughtText = 'reading your repo';
18818
+ const started = task.startedAt || Date.now();
18819
+ const elapsedHtml = '<span class="vr-task-elapsed" data-task-elapsed="' + started + '">' + formatElapsed(Date.now() - started) + '</span>';
18725
18820
  const compactClass = compact ? ' is-compact' : '';
18726
18821
  const rail = compact ? '' : '<div class="vr-task-rail" aria-hidden="true"><span></span></div>';
18727
- const detail = compact ? '' : '<p>' + escapeHtml(meta.line) + '</p>';
18822
+ const detail = compact ? '' : '<p>' + escapeHtml(meta.line) + ' On a large repo this can take a minute.</p>';
18728
18823
  return '<section class="vr-chat-agent-task is-working' + compactClass + '" data-task="' + escapeHtml(task.kind) + '">' +
18729
- '<header><span class="vr-task-glyph" aria-hidden="true"></span><div><strong>' + escapeHtml(statusText) + '</strong><em>(' + escapeHtml(thoughtText) + ')</em></div><b>' + escapeHtml(meta.title) + '</b></header>' +
18824
+ '<header><span class="vr-task-glyph" aria-hidden="true"></span><div><strong>' + escapeHtml(statusText) + '</strong><em>(' + escapeHtml(thoughtText) + ')</em></div>' + elapsedHtml + '<b>' + escapeHtml(meta.title) + '</b></header>' +
18730
18825
  rail +
18731
18826
  detail +
18732
18827
  '</section>';
@@ -18740,16 +18835,19 @@ function formatAgentOutput(value) {
18740
18835
  .filter((line) => !/^(OpenAI Codex v|workdir:|model:|provider:|approval:|sandbox:|reasoning effort:|reasoning summaries:|session id:|--------$|user$)/.test(line.trim()))
18741
18836
  .join('\\n')
18742
18837
  .trim();
18743
- if (/IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist/i.test(clean)) {
18838
+ // CLI-failure rewrites only apply to SHORT error output. A long real answer that merely
18839
+ // mentions "auth"/"sign in" (e.g. a production auth review) must never be mistaken for one.
18840
+ const compact = clean.length < 800;
18841
+ if (compact && /IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist/i.test(clean)) {
18744
18842
  return 'Gemini CLI is installed, but this account/client is not eligible for the current Gemini Code Assist CLI. Connect a supported Gemini CLI account, then retry this chat.';
18745
18843
  }
18746
- if (/not running in a trusted directory|--skip-trust|GEMINI_CLI_TRUST_WORKSPACE/i.test(clean)) {
18844
+ if (compact && /not running in a trusted directory|--skip-trust|GEMINI_CLI_TRUST_WORKSPACE/i.test(clean)) {
18747
18845
  return 'Gemini CLI needs this project trusted for headless chat. VibeRaven now passes the trust flag for Gemini runs; retry after your Gemini account is eligible.';
18748
18846
  }
18749
- if (/command not found|not recognized as an internal or external command|ENOENT/i.test(clean)) {
18847
+ if (compact && /command not found|not recognized as an internal or external command|ENOENT/i.test(clean)) {
18750
18848
  return 'The selected CLI is not available on PATH. Install it or sign in, then reconnect it from the chat header.';
18751
18849
  }
18752
- if (/authentication|not logged in|login required|sign in/i.test(clean)) {
18850
+ if (compact && /not logged in|login required|not authenticated|authentication failed|unauthorized|please (run|sign|log)|run (codex|gemini|claude)[^\\n]{0,40}login/i.test(clean)) {
18753
18851
  return 'The selected CLI needs sign-in before VibeRaven can use it. Sign in with the CLI, then retry this mission.';
18754
18852
  }
18755
18853
  const missionIndex = clean.indexOf('User mission:');
@@ -19008,6 +19106,21 @@ function agentErrorPresentation(text) {
19008
19106
  return { headline: raw, raw: '' };
19009
19107
  }
19010
19108
 
19109
+ function looksLikeRawTranscript(text) {
19110
+ const nonEmpty = String(text || '').split('\\n').filter((line) => line.trim());
19111
+ if (nonEmpty.length < 24) return false;
19112
+ let grepLike = 0;
19113
+ let structured = 0;
19114
+ nonEmpty.forEach((line) => {
19115
+ const t = line.trim();
19116
+ if (/:\\d+:\\s/.test(t) || /\\.(json|jsonl|ts|tsx|js|md|txt):\\d+/.test(t)) grepLike++;
19117
+ if (/^#{1,3}\\s+/.test(t) || /^\\*\\*[^*]+\\*\\*/.test(t)) structured++;
19118
+ });
19119
+ if (grepLike >= 8) return true;
19120
+ if (nonEmpty.length > 60 && structured <= 1) return true;
19121
+ return false;
19122
+ }
19123
+
19011
19124
  function renderAgentAnswerHtml(rawText, chatIndex, cards, isError, usedActions) {
19012
19125
  const tick = String.fromCharCode(96);
19013
19126
  const fence = tick + tick + tick;
@@ -19022,6 +19135,16 @@ function renderAgentAnswerHtml(rawText, chatIndex, cards, isError, usedActions)
19022
19135
  '</div>';
19023
19136
  }
19024
19137
  }
19138
+ if (looksLikeRawTranscript(text)) {
19139
+ const headline = isError
19140
+ ? 'The agent explored your repo but did not finish with a clean summary.'
19141
+ : 'The agent returned raw work instead of a summary.';
19142
+ return '<div class="vr-agent-answer-body vr-agent-answer-raw">' +
19143
+ '<p class="vr-agent-raw-headline">' + escapeHtml(headline) + '</p>' +
19144
+ '<p class="vr-agent-raw-sub">Open the agent work below for the raw detail, or use Retry for a clean answer.</p>' +
19145
+ agentWorkLogHtml(text) +
19146
+ '</div>';
19147
+ }
19025
19148
  const split = splitAgentTelemetry(text);
19026
19149
  const workLog = split.work ? agentWorkLogHtml(split.work) : '';
19027
19150
  let visibleText = dedupeAgentMarkdownBody(split.answer, cards);
@@ -19218,8 +19341,9 @@ function ChatAgentResponse(meta, responseText, isError, providerId, releaseId, k
19218
19341
  : '';
19219
19342
  return '<section class="vr-chat-message is-agent vr-chat-agent-response vr-hud-card is-' + statusTone + ' ' + (isError ? 'is-error' : 'is-ready') + '">' +
19220
19343
  '<header class="vr-hud-strip">' +
19221
- '<span class="vr-hud-avatar"><img src="' + mascotUrl() + '" alt="" /></span>' +
19344
+ '<span class="vr-hud-avatar is-raven"><img src="/manga/emblem.png" alt="" /></span>' +
19222
19345
  '<strong>VibeRaven</strong>' +
19346
+ '<span class="vr-hud-aibadge">AI AGENT</span>' +
19223
19347
  '<span class="vr-hud-status is-' + statusTone + '"><i></i>' + statusWord + '</span>' +
19224
19348
  via +
19225
19349
  '<b>Just now</b>' +
@@ -19273,7 +19397,7 @@ function ProviderCardMessage(message) {
19273
19397
  const provider = providers.find((item) => item.id === message.providerId);
19274
19398
  if (!provider) return '';
19275
19399
  const grade = message.grade || provider.grade || { value: null, tier: 'facedown', label: 'FACE DOWN' };
19276
- const rows = message.rows === null
19400
+ const rows = !Array.isArray(message.rows)
19277
19401
  ? '<div class="vr-card-row is-running"><span>&#9676;</span> running checks&hellip;</div>'
19278
19402
  : message.rows.map((row) => {
19279
19403
  const ready = row.state === 'ready';
@@ -19362,8 +19486,8 @@ function renderChatMessages(chat, index) {
19362
19486
  }
19363
19487
  const meta = message.kind ? taskMeta(message.kind) : { title: 'VibeRaven Agent' };
19364
19488
  const messageId = String(index) + '-' + String(messageIndex);
19365
- return storyNode('is-agent' + (message.isError ? ' is-error' : ''),
19366
- Icon(storyIconFor(message.kind)),
19489
+ return storyNode('is-agent has-raven' + (message.isError ? ' is-error' : ''),
19490
+ '<img class="vr-raven-coin" src="/manga/emblem.png" alt="" />',
19367
19491
  ChatAgentResponse(meta, message.text, Boolean(message.isError), message.providerId, message.releaseId, message.kind, index, messageId, message.cards, { showPlan: demo.fixPlanExpandedChatIndex === index }));
19368
19492
  }).join('');
19369
19493
  return html;
@@ -19563,7 +19687,7 @@ function StudioChatLane(index) {
19563
19687
  (gateNotClear && introBlocker ? '<button class="vr-start-here-cta" type="button" data-action="start-agent-fix" data-chat-index="' + String(index) + '">' + Icon('bolt') + '<span>Fix it with the connected agent</span></button>' : '') +
19564
19688
  ChatEnvironmentConnectCard();
19565
19689
  const staticIntro = fresh
19566
- ? storyNode('is-agent is-intro', Icon('spark'), '<section class="vr-chat-message is-agent vr-chat-intro-message"><div><span><img src="' + mascotUrl() + '" alt="" /></span><p><strong>VibeRaven Agent</strong><b>Agent</b><em class="is-ready">Ready</em></p></div>' + introBody + '</section>')
19690
+ ? storyNode('is-agent is-intro has-raven', '<img class="vr-raven-coin" src="/manga/emblem.png" alt="" />', '<section class="vr-chat-message is-agent vr-chat-intro-message"><div><span class="vr-intro-raven"><img src="/manga/emblem.png" alt="" /></span><p><strong>VibeRaven Agent</strong><b>Agent</b><em class="is-ready">Ready</em></p></div>' + introBody + '</section>')
19567
19691
  : '';
19568
19692
  return '<article class="vr-chat-lane ' + (secondary ? 'is-secondary' : 'is-primary') + '" data-chat-index="' + String(index) + '">' +
19569
19693
  (secondary ? '<button class="vr-chat-close" type="button" data-action="close-chat" data-chat-index="' + String(index) + '" aria-label="Close chat">' + Icon('x') + '</button>' : '') +
@@ -19735,23 +19859,32 @@ function worstProviderId() {
19735
19859
  return worst ? worst.id : null;
19736
19860
  }
19737
19861
 
19862
+ // Top-left plaque = the live table STATS (real repo/provider data), replacing
19863
+ // the old crimson ribbon. Compact stat cells so it fits the header row.
19738
19864
  function TablePlaque() {
19739
19865
  const gate = (currentState && currentState.gate) || {};
19740
- const stamp = gate.status === 'clear'
19866
+ const gateClear = gate.status === 'clear';
19867
+ const stamp = gateClear
19741
19868
  ? '<span class="vr-stamp is-mini is-clear">RAVEN &middot; CLEAR</span>'
19742
19869
  : gate.status === 'not_clear'
19743
19870
  ? '<span class="vr-stamp is-mini is-notyet">NOT YET</span>'
19744
19871
  : '<span class="vr-stamp is-mini is-idle">NOT SCANNED</span>';
19745
19872
  const detected = providers.filter((item) => item.state !== 'not_detected');
19746
- const flawless = detected.filter((item) => item.grade && item.grade.tier === 'flawless').length;
19747
19873
  const review = detected.filter((item) => item.grade && item.grade.tier === 'review').length;
19748
19874
  const graded = detected.filter((item) => item.grade && typeof item.grade.value === 'number');
19749
19875
  const avg = graded.length ? graded.reduce((sum, item) => sum + item.grade.value, 0) / graded.length : 0;
19750
- return '<div class="vr-table-ribbon" aria-hidden="true">' +
19751
- '<strong>' + escapeHtml(projectName()).toUpperCase() + '</strong>' + stamp +
19752
- '<span class="vr-ribbon-meter" title="Table grade \u2014 the average raven grade of your cards"><i style="width:' + Math.max(3, Math.round(avg * 10)) + '%"></i></span>' +
19753
- '<b>' + (graded.length ? avg.toFixed(1) : '&#8212;') + '</b>' +
19754
- '<em>' + String(detected.length) + ' in play &middot; ' + String(flawless) + ' flawless &middot; ' + String(review) + ' to review</em>' +
19876
+ const dirty = (demo.worktree && Number(demo.worktree.dirtyCount)) || 0;
19877
+ const cells = [
19878
+ { label: 'Providers', value: String(detected.length), tone: 'gold' },
19879
+ { label: 'Grade', value: graded.length ? avg.toFixed(1) : '&#8212;', tone: 'gold' },
19880
+ { label: 'Review', value: String(review), tone: review > 0 ? 'orange' : 'muted' },
19881
+ { label: 'Uncommitted', value: String(dirty), tone: dirty > 0 ? 'orange' : 'green' },
19882
+ ];
19883
+ return '<div class="vr-table-ribbon">' +
19884
+ '<div class="vr-ribbon-brand"><strong>' + escapeHtml(projectName()).toUpperCase() + '</strong>' + stamp + '</div>' +
19885
+ '<div class="vr-ribbon-stats">' +
19886
+ cells.map((c) => '<span class="vr-ribbon-stat is-' + c.tone + '"><b>' + c.value + '</b><i>' + escapeHtml(c.label) + '</i></span>').join('') +
19887
+ '</div>' +
19755
19888
  '</div>';
19756
19889
  }
19757
19890
 
@@ -20567,7 +20700,7 @@ function TableRail() {
20567
20700
  ['worktree', 'repo', 'Worktree'],
20568
20701
  ];
20569
20702
  return '<aside class="vr-left-context-rail is-table-rail" aria-label="Studio navigation">' +
20570
- '<span class="vr-rail-raven" title="VibeRaven"><img src="' + mascotAssets.idle + '" alt="VibeRaven" /></span>' +
20703
+ '<span class="vr-rail-raven is-emblem" title="VibeRaven"><img src="/manga/emblem.png" alt="VibeRaven" /></span>' +
20571
20704
  '<button class="vr-rail-icon is-new" type="button" data-action="new-chat" title="New chat">' + Icon('plus') + '</button>' +
20572
20705
  '<button class="vr-rail-icon' + (demo.chatsFlyoutOpen ? ' is-active' : '') + '" type="button" data-action="toggle-chats-flyout" title="Recent chats">' + Icon('prompt') + '</button>' +
20573
20706
  '<span class="vr-rail-sep" aria-hidden="true"></span>' +
@@ -20664,12 +20797,118 @@ function closeComposerPopovers() {
20664
20797
  demo.dropActive = false;
20665
20798
  }
20666
20799
 
20800
+ // anime.js entrance for the region map: stagger the nodes in when the view opens.
20801
+ // The nodes are positioned with inline transform:translate(x,y), so we animate
20802
+ // OPACITY ONLY (never transform) and clear the inline value on complete. Gated by
20803
+ // a flag that resets when the view leaves, so it plays once per open, not on every
20804
+ // full re-render.
20805
+ let regionMapAnimated = false;
20806
+ function animateRegionMapOnce() {
20807
+ const nodes = app.querySelectorAll('.vr-studio-architecture.is-region-map .vr-arch-node');
20808
+ if (!nodes.length) { regionMapAnimated = false; return; }
20809
+ if (regionMapAnimated || !window.anime || typeof window.anime.animate !== 'function') return;
20810
+ regionMapAnimated = true;
20811
+ const clear = () => nodes.forEach((n) => { n.style.opacity = ''; });
20812
+ try {
20813
+ window.anime.animate(nodes, {
20814
+ opacity: [0, 1],
20815
+ duration: 460,
20816
+ delay: window.anime.stagger(34),
20817
+ ease: 'out(2)',
20818
+ onComplete: clear,
20819
+ });
20820
+ } catch (err) { clear(); }
20821
+ }
20822
+
20823
+ // Release timeline (Versions view): stagger the version cards in when it opens.
20824
+ // The cards carry a --vr-rot rotation transform, so we animate OPACITY ONLY
20825
+ // (translate/scale would clobber the rotation) and clear it on complete. Gated
20826
+ // once per open like the region map.
20827
+ let releaseTimelineAnimated = false;
20828
+ function animateReleaseTimelineOnce() {
20829
+ const spread = app.querySelector('.vr-version-spread');
20830
+ if (!spread) { releaseTimelineAnimated = false; return; }
20831
+ if (releaseTimelineAnimated || !window.anime || typeof window.anime.animate !== 'function') return;
20832
+ const cards = app.querySelectorAll('.vr-version-spread .vr-vcard');
20833
+ if (!cards.length) return;
20834
+ releaseTimelineAnimated = true;
20835
+ const clear = () => cards.forEach((c) => { c.style.opacity = ''; });
20836
+ try {
20837
+ window.anime.animate(cards, {
20838
+ opacity: [0, 1],
20839
+ duration: 440,
20840
+ delay: window.anime.stagger(50),
20841
+ ease: 'out(2)',
20842
+ onComplete: clear,
20843
+ });
20844
+ } catch (err) { clear(); }
20845
+ }
20846
+
20847
+ // Chat view: stagger the existing message rows in when the chat first shows, so
20848
+ // there is visible motion on the default view (opening / refreshing / switching
20849
+ // to chat). Story nodes have no positioning transform, so translateY is safe;
20850
+ // cleared on complete. Gated once per chat-view open (a new deal animates its own
20851
+ // card separately). Reset when leaving chat or when the transcript is empty.
20852
+ let chatEntranceAnimated = false;
20853
+ function animateChatEntranceOnce() {
20854
+ if (demo.activeStudioTab !== 'chat') { chatEntranceAnimated = false; return; }
20855
+ const nodes = app.querySelectorAll('.vr-chat-transcript .vr-story-node');
20856
+ if (!nodes.length) { chatEntranceAnimated = false; return; }
20857
+ if (chatEntranceAnimated || !window.anime || typeof window.anime.animate !== 'function') return;
20858
+ chatEntranceAnimated = true;
20859
+ const clear = () => nodes.forEach((n) => { n.style.opacity = ''; n.style.transform = ''; });
20860
+ try {
20861
+ window.anime.animate(nodes, {
20862
+ opacity: [0, 1],
20863
+ translateY: [14, 0],
20864
+ duration: 440,
20865
+ delay: window.anime.stagger(60),
20866
+ ease: 'out(2)',
20867
+ onComplete: clear,
20868
+ });
20869
+ } catch (err) { clear(); }
20870
+ }
20871
+
20872
+ // Card dealt into chat: fade + scale + lift the newest provider-card message in.
20873
+ // Called only from dealProviderCard (not the generic render) so it plays once on
20874
+ // deal, not on the later check-complete re-render.
20875
+ function animateDealtCardIntoChat() {
20876
+ if (!window.anime || typeof window.anime.animate !== 'function') return;
20877
+ const cards = app.querySelectorAll('.vr-card-message');
20878
+ const el = cards[cards.length - 1];
20879
+ if (!el) return;
20880
+ const clear = () => {
20881
+ el.style.opacity = '';
20882
+ el.style.transform = '';
20883
+ el.style.scale = '';
20884
+ el.style.rotate = '';
20885
+ el.style.translate = '';
20886
+ el.style.transformOrigin = '';
20887
+ };
20888
+ try {
20889
+ el.style.transformOrigin = '18% 60%';
20890
+ window.anime.animate(el, {
20891
+ opacity: [0, 1],
20892
+ scale: [0.55, 1],
20893
+ translateY: [46, 0],
20894
+ translateX: [-30, 0],
20895
+ rotate: [-7, 0],
20896
+ duration: 720,
20897
+ ease: 'out(3)',
20898
+ onComplete: clear,
20899
+ });
20900
+ } catch (err) { clear(); }
20901
+ }
20902
+
20667
20903
  function render(options) {
20668
20904
  const opts = options || {};
20669
20905
  const scrollState = opts.preserveScroll === false ? null : readChatScrollState();
20670
20906
  stableModalRender = Boolean(demo.activeModal && demo.activeModal === lastRenderedModal);
20671
20907
  app.innerHTML = VibeShell();
20672
20908
  window.requestAnimationFrame(() => {
20909
+ animateRegionMapOnce();
20910
+ animateReleaseTimelineOnce();
20911
+ animateChatEntranceOnce();
20673
20912
  if (opts.scrollToBottom != null) {
20674
20913
  restoreChatScrollState({}, { scrollToBottom: opts.scrollToBottom });
20675
20914
  } else if (scrollState) {
@@ -22085,6 +22324,7 @@ app.addEventListener('pointerdown', (event) => {
22085
22324
  offsetX: event.clientX - rect.left,
22086
22325
  offsetY: event.clientY - rect.top,
22087
22326
  width: rect.width,
22327
+ height: rect.height,
22088
22328
  moved: false,
22089
22329
  };
22090
22330
  });
@@ -22098,7 +22338,9 @@ app.addEventListener('pointermove', (event) => {
22098
22338
  tableDragState.moved = true;
22099
22339
  const ghost = tableDragState.el.cloneNode(true);
22100
22340
  ghost.classList.add('vr-drag-ghost');
22341
+ ghost.classList.remove('is-placed', 'is-hand');
22101
22342
  ghost.style.width = tableDragState.width + 'px';
22343
+ ghost.style.height = tableDragState.height + 'px';
22102
22344
  document.body.appendChild(ghost);
22103
22345
  tableDragState.ghost = ghost;
22104
22346
  tableDragState.el.classList.add('is-drag-source');
@@ -22438,8 +22680,18 @@ app.addEventListener('scroll', (event) => {
22438
22680
  }, true);
22439
22681
 
22440
22682
  loadRecentChats();
22683
+ // Persistence: on refresh/reopen, restore the most recent chat so work is never lost.
22684
+ try {
22685
+ const lastChat = recentChats[0];
22686
+ if (lastChat && Array.isArray(lastChat.messages) && lastChat.messages.length) {
22687
+ demo.activeRecentChatId = lastChat.id;
22688
+ demo.activeChatCount = 1;
22689
+ demo.chatContexts = [restoreRecentChat(lastChat)];
22690
+ syncDemoFromChatContext(demo.chatContexts[0]);
22691
+ }
22692
+ } catch { /* restore is best-effort */ }
22441
22693
  try {
22442
- if (!window.localStorage.getItem('vrOnboarded') && !currentState.empty && !demo.activeModal && window.location.search.indexOf('shot') < 0) {
22694
+ if (!window.localStorage.getItem('vrOnboarded') && !demo.activeRecentChatId && !currentState.empty && !demo.activeModal && window.location.search.indexOf('shot') < 0) {
22443
22695
  demo.activeModal = 'quickstart';
22444
22696
  } else if (
22445
22697
  !window.localStorage.getItem('vrPackOpened') && !currentState.empty && !demo.activeModal &&
@@ -22497,28 +22749,30 @@ var init_appCss = __esm({
22497
22749
  "src/local-ui/static/appCss.ts"() {
22498
22750
  "use strict";
22499
22751
  localUiCss = `
22752
+ @import url('https://fonts.googleapis.com/css2?family=Anton&display=swap');
22500
22753
  :root {
22501
- --vr-bg: #170709;
22502
- --vr-bg-2: #241012;
22503
- --vr-panel: rgba(40, 16, 18, 0.78);
22504
- --vr-panel-2: rgba(52, 22, 24, 0.7);
22754
+ --vr-bg: #0b0b0d;
22755
+ --vr-bg-2: #141418;
22756
+ --vr-panel: rgba(20, 20, 24, 0.8);
22757
+ --vr-panel-2: rgba(30, 30, 35, 0.72);
22505
22758
  --vr-border: rgba(232, 193, 90, 0.26);
22506
22759
  --vr-border-strong: rgba(232, 193, 90, 0.55);
22507
- --vr-text: #f7f2e9;
22508
- --vr-muted: #c49585;
22760
+ --vr-text: #f4f1e8;
22761
+ --vr-muted: #a89a86;
22509
22762
  --vr-purple: #e8c15a;
22510
22763
  --vr-purple-2: #ffd76e;
22511
22764
  --vr-blue: #8fa7ff;
22512
22765
  --vr-green: #22c55e;
22513
22766
  --vr-red: #ef4444;
22514
22767
  --vr-orange: #ffb84d;
22515
- --vr-glow-purple: 0 12px 32px rgba(0, 0, 0, 0.38);
22516
- --vr-glow-green: 0 12px 32px rgba(0, 0, 0, 0.38);
22768
+ --vr-glow-purple: 0 12px 32px rgba(0, 0, 0, 0.5);
22769
+ --vr-glow-green: 0 12px 32px rgba(0, 0, 0, 0.5);
22517
22770
  --vr-accent: #e8c15a;
22518
22771
  --vr-gold: #e8c15a;
22519
22772
  --vr-gold-bright: #ffe9ad;
22520
- --vr-plate: rgba(12, 6, 5, 0.85);
22773
+ --vr-plate: rgba(10, 10, 12, 0.85);
22521
22774
  --vr-foil: linear-gradient(135deg, #fff3c4, #c98f2e 35%, #ffe9a8 55%, #8a5a1a 82%, #ffd76e);
22775
+ --mg-display: 'Anton', 'Arial Narrow', sans-serif;
22522
22776
  }
22523
22777
 
22524
22778
  * { box-sizing: border-box; }
@@ -22527,6 +22781,8 @@ body {
22527
22781
  margin: 0;
22528
22782
  overflow: hidden;
22529
22783
  background: var(--vr-bg);
22784
+ background-image: radial-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1.4px);
22785
+ background-size: 6px 6px;
22530
22786
  color: var(--vr-text);
22531
22787
  font-family: Geist, Inter, "Segoe UI", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
22532
22788
  font-size: 14px;
@@ -22581,7 +22837,7 @@ a i[data-icon] {
22581
22837
  place-items: center;
22582
22838
  border: 1px solid rgba(148, 163, 184, 0.16);
22583
22839
  border-radius: 8px;
22584
- background: rgba(40, 14, 16, 0.72);
22840
+ background: rgba(24, 22, 28, 0.72);
22585
22841
  color: #ffe9ad;
22586
22842
  }
22587
22843
  .vr-context-icon-chip.is-input {
@@ -22618,7 +22874,7 @@ a i[data-icon] {
22618
22874
  .vr-bottom-tip-bar {
22619
22875
  border: 1px solid var(--vr-border);
22620
22876
  background:
22621
- linear-gradient(180deg, rgba(40, 14, 16, 0.68), rgba(22, 7, 9, 0.7)),
22877
+ linear-gradient(180deg, rgba(24, 22, 28, 0.68), rgba(13, 12, 16, 0.7)),
22622
22878
  var(--vr-panel);
22623
22879
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.025);
22624
22880
  }
@@ -22659,7 +22915,7 @@ a i[data-icon] {
22659
22915
  background:
22660
22916
  radial-gradient(circle at 42% 26%, rgba(34, 197, 94, 0.16), transparent 36%),
22661
22917
  radial-gradient(circle at 68% 76%, rgba(232, 193, 90, 0.5), transparent 54%),
22662
- linear-gradient(145deg, rgba(40, 14, 16, 0.98), rgba(22, 7, 9, 0.8));
22918
+ linear-gradient(145deg, rgba(24, 22, 28, 0.98), rgba(13, 12, 16, 0.8));
22663
22919
  box-shadow:
22664
22920
  inset 0 1px 0 rgba(255, 255, 255, 0.08),
22665
22921
  0 0 28px rgba(0, 0, 0, 0);
@@ -22857,7 +23113,7 @@ a i[data-icon] {
22857
23113
  border-radius: 7px;
22858
23114
  background:
22859
23115
  radial-gradient(circle at 34% 20%, rgba(255, 255, 255, 0.16), transparent 34%),
22860
- rgba(40, 14, 16, 0.78);
23116
+ rgba(24, 22, 28, 0.78);
22861
23117
  }
22862
23118
 
22863
23119
  .vr-inline-brand-mark img {
@@ -22870,7 +23126,7 @@ a i[data-icon] {
22870
23126
  .vr-workspace-switcher > span {
22871
23127
  width: 28px;
22872
23128
  height: 28px;
22873
- border: 2px solid rgba(22, 7, 9, 0.9);
23129
+ border: 2px solid rgba(13, 12, 16, 0.9);
22874
23130
  border-radius: 999px;
22875
23131
  background:
22876
23132
  radial-gradient(circle at 35% 28%, #f8fafc, transparent 10%),
@@ -22899,7 +23155,7 @@ a i[data-icon] {
22899
23155
  border-radius: 9px;
22900
23156
  background:
22901
23157
  linear-gradient(180deg, rgba(148, 163, 184, 0.050), rgba(14, 165, 233, 0.04)),
22902
- rgba(22, 7, 9, 0.34);
23158
+ rgba(13, 12, 16, 0.34);
22903
23159
  padding: 7px 8px;
22904
23160
  }
22905
23161
 
@@ -23051,7 +23307,7 @@ a i[data-icon] {
23051
23307
  border-width: 0 0 1px;
23052
23308
  border-radius: 0;
23053
23309
  padding: 0 14px;
23054
- background: rgba(22, 7, 9, 0.52);
23310
+ background: rgba(13, 12, 16, 0.52);
23055
23311
  }
23056
23312
  .vr-topbar-brand {
23057
23313
  display: inline-grid;
@@ -23366,7 +23622,7 @@ a i[data-icon] {
23366
23622
  border-width: 0 0 1px;
23367
23623
  border-radius: 0;
23368
23624
  padding: 0 28px;
23369
- background: rgba(22, 7, 9, 0.5);
23625
+ background: rgba(13, 12, 16, 0.5);
23370
23626
  }
23371
23627
 
23372
23628
  .vr-project-id {
@@ -23762,7 +24018,7 @@ body:has(.vr-app.is-dragging) {
23762
24018
  .vr-new-chat-button kbd {
23763
24019
  border: 1px solid rgba(226, 232, 240, 0.12);
23764
24020
  border-radius: 6px;
23765
- background: rgba(22, 7, 9, 0.5);
24021
+ background: rgba(13, 12, 16, 0.5);
23766
24022
  color: #cbd5e1;
23767
24023
  padding: 2px 6px;
23768
24024
  font: 10px/1.2 "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
@@ -24115,7 +24371,7 @@ body:has(.vr-app.is-dragging) {
24115
24371
  margin: 8px 0 0;
24116
24372
  padding: 8px 10px;
24117
24373
  border-radius: 8px;
24118
- background: rgba(22, 7, 9, 0.72);
24374
+ background: rgba(13, 12, 16, 0.72);
24119
24375
  overflow: auto;
24120
24376
  }
24121
24377
  .vr-cli-setup-step pre code {
@@ -24653,10 +24909,26 @@ body:has(.vr-app.is-dragging) {
24653
24909
  min-height: 32px;
24654
24910
  padding: 0 12px;
24655
24911
  }
24656
- .vr-studio-chat:not(.is-split) .vr-chat-command-bar {
24657
- padding: 6px 0 0;
24912
+ /* Align the composer column with the chat messages. The transcript is a centered
24913
+ max-width column (min(1440px, 100% - 64px)) with 54px side padding, and messages
24914
+ are inset a further ~40px by the agent-avatar gutter. Match all of that here so
24915
+ the composer lines up with the message cards at every viewport width. Higher
24916
+ specificity (.vr-chat-lane) than the responsive command-bar padding passes. */
24917
+ .vr-studio-chat:not(.is-split) .vr-chat-lane .vr-chat-command-bar {
24918
+ width: min(1440px, calc(100% - 64px));
24919
+ max-width: min(1440px, calc(100% - 64px));
24920
+ margin-inline: auto;
24921
+ justify-self: center;
24922
+ padding: 6px 54px 10px 94px;
24658
24923
  align-self: end;
24659
24924
  }
24925
+ /* Size the composer to the same width as the chat messages and left-align it, so
24926
+ both edges line up with the cards (which are min(1120px, 100%)). */
24927
+ .vr-studio-chat:not(.is-split) .vr-chat-lane .vr-chat-command-bar > .vr-chat-composer {
24928
+ width: min(1120px, 100%);
24929
+ max-width: min(1120px, 100%);
24930
+ justify-self: start;
24931
+ }
24660
24932
  .vr-studio-chat:not(.is-split) .vr-chat-command-bar > .vr-chat-composer {
24661
24933
  min-height: 92px;
24662
24934
  border-color: rgba(226, 232, 240, 0.16);
@@ -24711,7 +24983,7 @@ body:has(.vr-app.is-dragging) {
24711
24983
  height: 39px;
24712
24984
  object-fit: contain;
24713
24985
  transform: translateY(4px);
24714
- filter: drop-shadow(0 0 10px rgba(40, 14, 16, 0.75));
24986
+ filter: drop-shadow(0 0 10px rgba(24, 22, 28, 0.75));
24715
24987
  }
24716
24988
  .vr-chat-transcript > header strong,
24717
24989
  .vr-chat-transcript > header em {
@@ -25093,7 +25365,7 @@ body:has(.vr-app.is-dragging) {
25093
25365
  margin: 0;
25094
25366
  padding: 8px 10px;
25095
25367
  border-radius: 8px;
25096
- background: rgba(22, 7, 9, 0.72);
25368
+ background: rgba(13, 12, 16, 0.72);
25097
25369
  overflow: auto;
25098
25370
  }
25099
25371
  .vr-cli-connect-expand pre code {
@@ -25161,7 +25433,7 @@ body:has(.vr-app.is-dragging) {
25161
25433
  margin: 0;
25162
25434
  padding: 14px 16px;
25163
25435
  border-radius: 12px;
25164
- background: rgba(22, 7, 9, 0.82);
25436
+ background: rgba(13, 12, 16, 0.82);
25165
25437
  border: 1px solid rgba(226, 232, 240, 0.1);
25166
25438
  }
25167
25439
  .vr-studio-terminal-log code {
@@ -25186,7 +25458,7 @@ body:has(.vr-app.is-dragging) {
25186
25458
  padding: 6px 12px;
25187
25459
  border-radius: 999px;
25188
25460
  border: 1px solid rgba(226, 232, 240, 0.14);
25189
- background: rgba(40, 14, 16, 0.72);
25461
+ background: rgba(24, 22, 28, 0.72);
25190
25462
  color: #e2e8f0;
25191
25463
  font-size: 12px;
25192
25464
  cursor: pointer;
@@ -25336,10 +25608,17 @@ body:has(.vr-app.is-dragging) {
25336
25608
  position: relative;
25337
25609
  z-index: 1;
25338
25610
  display: grid;
25339
- grid-template-columns: 18px auto minmax(0, 1fr);
25611
+ grid-template-columns: 18px auto auto minmax(0, 1fr);
25340
25612
  align-items: center;
25341
25613
  gap: 8px;
25342
25614
  }
25615
+ .vr-task-elapsed {
25616
+ font-family: var(--vr-hud-mono);
25617
+ font-size: 12px;
25618
+ font-variant-numeric: tabular-nums;
25619
+ color: var(--vr-gold);
25620
+ letter-spacing: 0.04em;
25621
+ }
25343
25622
  .vr-task-glyph {
25344
25623
  position: relative;
25345
25624
  width: 14px;
@@ -25453,6 +25732,11 @@ body:has(.vr-app.is-dragging) {
25453
25732
  display: inline;
25454
25733
  margin-left: 4px;
25455
25734
  }
25735
+ /* compact header only has 3 columns; the redundant title (<b>) would wrap to a
25736
+ clipped second row ("...nges"), so drop it \u2014 the status word already says it. */
25737
+ .vr-chat-command-bar > .vr-chat-agent-task.is-compact header b {
25738
+ display: none;
25739
+ }
25456
25740
  .vr-chat-agent-response {
25457
25741
  max-width: min(920px, 82%);
25458
25742
  display: grid;
@@ -25519,7 +25803,7 @@ body:has(.vr-app.is-dragging) {
25519
25803
  border-radius: 12px;
25520
25804
  background:
25521
25805
  radial-gradient(circle at 0% 0%, rgba(148, 163, 184, 0.050), transparent 42%),
25522
- rgba(40, 14, 16, 0.62);
25806
+ rgba(24, 22, 28, 0.62);
25523
25807
  padding: 10px 12px;
25524
25808
  min-width: 0;
25525
25809
  }
@@ -25551,7 +25835,7 @@ body:has(.vr-app.is-dragging) {
25551
25835
  border-color: rgba(34, 197, 94, 0.28);
25552
25836
  background:
25553
25837
  radial-gradient(circle at 0% 0%, rgba(34, 197, 94, 0.12), transparent 42%),
25554
- rgba(40, 14, 16, 0.62);
25838
+ rgba(24, 22, 28, 0.62);
25555
25839
  }
25556
25840
  .vr-agent-gate-strip[data-tone="clear"] svg {
25557
25841
  color: #86efac;
@@ -25560,7 +25844,7 @@ body:has(.vr-app.is-dragging) {
25560
25844
  border-color: rgba(248, 113, 113, 0.28);
25561
25845
  background:
25562
25846
  radial-gradient(circle at 0% 0%, rgba(248, 113, 113, 0.1), transparent 42%),
25563
- rgba(40, 14, 16, 0.62);
25847
+ rgba(24, 22, 28, 0.62);
25564
25848
  }
25565
25849
  .vr-agent-gate-strip[data-tone="blocked"] svg {
25566
25850
  color: #fca5a5;
@@ -25570,7 +25854,7 @@ body:has(.vr-app.is-dragging) {
25570
25854
  border-radius: 12px;
25571
25855
  background:
25572
25856
  radial-gradient(circle at 0% 0%, rgba(148, 163, 184, 0.050), transparent 42%),
25573
- rgba(40, 14, 16, 0.55);
25857
+ rgba(24, 22, 28, 0.55);
25574
25858
  padding: 12px 14px;
25575
25859
  display: grid;
25576
25860
  gap: 8px;
@@ -25656,7 +25940,7 @@ body:has(.vr-app.is-dragging) {
25656
25940
  .vr-agent-answer-body code {
25657
25941
  border: 1px solid rgba(148, 163, 184, 0.18);
25658
25942
  border-radius: 6px;
25659
- background: rgba(40, 14, 16, 0.72);
25943
+ background: rgba(24, 22, 28, 0.72);
25660
25944
  padding: 1px 6px;
25661
25945
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
25662
25946
  font-size: 12px;
@@ -26030,7 +26314,7 @@ body:has(.vr-app.is-dragging) {
26030
26314
  width: fit-content;
26031
26315
  border: 1px solid rgba(148, 163, 184, 0.22);
26032
26316
  border-radius: 999px;
26033
- background: rgba(40, 14, 16, 0.86);
26317
+ background: rgba(24, 22, 28, 0.86);
26034
26318
  color: #e2e8f0;
26035
26319
  padding: 7px 12px;
26036
26320
  }
@@ -26141,7 +26425,7 @@ body:has(.vr-app.is-dragging) {
26141
26425
  .vr-provider-drop-zone > .vr-provider-drop-label {
26142
26426
  border: 1px dashed rgba(148, 163, 184, 0.22);
26143
26427
  border-radius: 999px;
26144
- background: rgba(22, 7, 9, 0.2);
26428
+ background: rgba(13, 12, 16, 0.2);
26145
26429
  padding: 5px 10px;
26146
26430
  }
26147
26431
  .vr-provider-drop-zone.is-active > .vr-provider-drop-label {
@@ -26879,7 +27163,7 @@ body:has(.vr-app.is-dragging) {
26879
27163
  place-items: center;
26880
27164
  border: 1px solid rgba(120, 145, 190, 0.18);
26881
27165
  border-radius: 10px;
26882
- background: rgba(22, 7, 9, 0.44);
27166
+ background: rgba(13, 12, 16, 0.44);
26883
27167
  color: var(--vr-text);
26884
27168
  transition:
26885
27169
  transform 180ms cubic-bezier(0.23, 1, 0.32, 1),
@@ -27076,7 +27360,7 @@ body:has(.vr-app.is-dragging) {
27076
27360
  .vr-provider-card:hover .vr-provider-proof-chip,
27077
27361
  .vr-provider-card.is-selected .vr-provider-proof-chip {
27078
27362
  border-color: rgba(248, 250, 252, 0.22);
27079
- background: rgba(22, 7, 9, 0.72);
27363
+ background: rgba(13, 12, 16, 0.72);
27080
27364
  }
27081
27365
  .vr-provider-proof-chip svg {
27082
27366
  width: 13px;
@@ -27405,7 +27689,7 @@ body:has(.vr-app.is-dragging) {
27405
27689
  border: 1px solid rgba(120, 145, 190, 0.15);
27406
27690
  border-radius: 9px;
27407
27691
  background:
27408
- linear-gradient(180deg, rgba(40, 14, 16, 0.64), rgba(8, 13, 24, 0.7));
27692
+ linear-gradient(180deg, rgba(24, 22, 28, 0.64), rgba(8, 13, 24, 0.7));
27409
27693
  color: var(--vr-text);
27410
27694
  padding: 8px 10px;
27411
27695
  text-align: left;
@@ -27652,7 +27936,7 @@ body:has(.vr-app.is-dragging) {
27652
27936
  border-radius: 999px;
27653
27937
  background:
27654
27938
  radial-gradient(circle at 50% 50%, rgba(34, 197, 94, 0.22), transparent 46%),
27655
- rgba(40, 14, 16, 0.78);
27939
+ rgba(24, 22, 28, 0.78);
27656
27940
  box-shadow: 0 0 14px rgba(34, 197, 94, 0.2);
27657
27941
  }
27658
27942
  .vr-agent-will span i::before {
@@ -27801,7 +28085,7 @@ body:has(.vr-app.is-dragging) {
27801
28085
  gap: 7px;
27802
28086
  border: 1px solid rgba(120, 145, 190, 0.2);
27803
28087
  border-radius: 9px;
27804
- background: rgba(22, 7, 9, 0.38);
28088
+ background: rgba(13, 12, 16, 0.38);
27805
28089
  color: #dbeafe;
27806
28090
  padding: 0 10px;
27807
28091
  text-decoration: none;
@@ -27912,9 +28196,13 @@ body:has(.vr-app.is-dragging) {
27912
28196
  radial-gradient(circle at 72% 16%, rgba(34, 197, 94, 0.12), transparent 24%),
27913
28197
  linear-gradient(rgba(148, 163, 184, 0.03) 1px, transparent 1px),
27914
28198
  linear-gradient(90deg, rgba(148, 163, 184, 0.03) 1px, transparent 1px),
27915
- rgba(1, 4, 13, 0.78);
28199
+ rgba(1, 4, 13, 0.86);
27916
28200
  background-size: auto, auto, 42px 42px, 42px 42px, auto;
27917
- backdrop-filter: blur(18px);
28201
+ /* NOTE: no backdrop-filter here. A full-viewport backdrop-filter: blur()
28202
+ renders as SOLID BLACK on many real Windows GPU/driver combos (Intel/ANGLE),
28203
+ blacking out the whole Studio whenever a modal (e.g. the welcome modal) is
28204
+ open. Headless/software raster hides the bug. The dark base layer above
28205
+ gives the same focus without the GPU hazard. Do not re-add blur here. */
27918
28206
  animation: vrModalFade 180ms ease-out both;
27919
28207
  }
27920
28208
 
@@ -27933,7 +28221,7 @@ body:has(.vr-app.is-dragging) {
27933
28221
  radial-gradient(circle at 80% 15%, rgba(148, 163, 184, 0.050), transparent 27%),
27934
28222
  radial-gradient(circle at 19% 8%, rgba(56, 189, 248, 0.12), transparent 24%),
27935
28223
  radial-gradient(circle at 74% 70%, rgba(34, 197, 94, 0.08), transparent 28%),
27936
- linear-gradient(180deg, rgba(13, 20, 43, 0.98), rgba(22, 7, 9, 0.995));
28224
+ linear-gradient(180deg, rgba(13, 20, 43, 0.98), rgba(13, 12, 16, 0.995));
27937
28225
  box-shadow:
27938
28226
  0 0 0 1px rgba(255, 233, 173, 0.12),
27939
28227
  0 0 70px rgba(0, 0, 0, 0),
@@ -27959,7 +28247,7 @@ body:has(.vr-app.is-dragging) {
27959
28247
  }
27960
28248
 
27961
28249
  .vr-modal-card::-webkit-scrollbar-thumb {
27962
- border: 3px solid rgba(22, 7, 9, 0.92);
28250
+ border: 3px solid rgba(13, 12, 16, 0.92);
27963
28251
  border-radius: 999px;
27964
28252
  background: rgba(148, 163, 184, 0.4);
27965
28253
  }
@@ -28043,7 +28331,7 @@ body:has(.vr-app.is-dragging) {
28043
28331
  border: 1px solid rgba(96, 122, 177, 0.24);
28044
28332
  border-radius: 18px;
28045
28333
  background:
28046
- linear-gradient(90deg, rgba(148, 163, 184, 0.050), rgba(8, 17, 34, 0.48) 46%, rgba(40, 14, 16, 0.08)),
28334
+ linear-gradient(90deg, rgba(148, 163, 184, 0.050), rgba(8, 17, 34, 0.48) 46%, rgba(24, 22, 28, 0.08)),
28047
28335
  radial-gradient(circle at 86% 42%, rgba(148, 163, 184, 0.050), transparent 32%);
28048
28336
  padding: 22px 34px 22px 22px;
28049
28337
  padding-right: 34px;
@@ -28318,7 +28606,7 @@ body:has(.vr-app.is-dragging) {
28318
28606
  .vr-modal-checks li.pending > span,
28319
28607
  .vr-modal-tasklist li.pending > span {
28320
28608
  border-color: rgba(96, 122, 177, 0.7);
28321
- background: rgba(40, 14, 16, 0.5);
28609
+ background: rgba(24, 22, 28, 0.5);
28322
28610
  box-shadow: none;
28323
28611
  }
28324
28612
 
@@ -28411,7 +28699,7 @@ body:has(.vr-app.is-dragging) {
28411
28699
  border: 1px solid rgba(96, 122, 177, 0.28);
28412
28700
  border-radius: 16px;
28413
28701
  background:
28414
- linear-gradient(180deg, rgba(40, 14, 16, 0.86), rgba(7, 16, 33, 0.94));
28702
+ linear-gradient(180deg, rgba(24, 22, 28, 0.86), rgba(7, 16, 33, 0.94));
28415
28703
  padding: 18px;
28416
28704
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
28417
28705
  }
@@ -28621,7 +28909,7 @@ body:has(.vr-app.is-dragging) {
28621
28909
  margin: 0 0 10px;
28622
28910
  border: 1px solid rgba(148, 163, 184, 0.14);
28623
28911
  border-radius: 12px;
28624
- background: rgba(40, 14, 16, 0.38);
28912
+ background: rgba(24, 22, 28, 0.38);
28625
28913
  padding: 10px 12px;
28626
28914
  }
28627
28915
 
@@ -28653,7 +28941,7 @@ body:has(.vr-app.is-dragging) {
28653
28941
  align-items: start;
28654
28942
  padding: 9px 11px;
28655
28943
  border-radius: 10px;
28656
- background: rgba(40, 14, 16, 0.38);
28944
+ background: rgba(24, 22, 28, 0.38);
28657
28945
  border: 1px solid rgba(148, 163, 184, 0.13);
28658
28946
  color: #e2e8f0;
28659
28947
  line-height: 1.35;
@@ -28859,7 +29147,7 @@ body:has(.vr-app.is-dragging) {
28859
29147
  border-radius: 12px;
28860
29148
  background:
28861
29149
  radial-gradient(circle at 0% 50%, rgba(148, 163, 184, 0.050), transparent 36%),
28862
- rgba(22, 7, 9, 0.5);
29150
+ rgba(13, 12, 16, 0.5);
28863
29151
  color: var(--vr-text);
28864
29152
  padding: 0 16px;
28865
29153
  text-align: left;
@@ -28901,7 +29189,7 @@ body:has(.vr-app.is-dragging) {
28901
29189
  border-radius: 14px;
28902
29190
  background:
28903
29191
  radial-gradient(circle at 86% 0%, rgba(148, 163, 184, 0.050), transparent 34%),
28904
- linear-gradient(180deg, rgba(40, 14, 16, 0.96), rgba(22, 7, 9, 0.96));
29192
+ linear-gradient(180deg, rgba(24, 22, 28, 0.96), rgba(13, 12, 16, 0.96));
28905
29193
  padding: 18px;
28906
29194
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
28907
29195
  }
@@ -30346,7 +30634,7 @@ body:has(.vr-app.is-dragging) {
30346
30634
  border-color: rgba(239, 68, 68, 0.38);
30347
30635
  background:
30348
30636
  radial-gradient(circle at 100% 0%, rgba(239, 68, 68, 0.12), transparent 210px),
30349
- linear-gradient(135deg, rgba(31, 14, 18, 0.9), rgba(13, 16, 24, 0.94));
30637
+ linear-gradient(135deg, rgba(20, 19, 24, 0.9), rgba(13, 16, 24, 0.94));
30350
30638
  }
30351
30639
 
30352
30640
  .vr-chat-agent-response > div:first-child {
@@ -31812,13 +32100,19 @@ div.vr-branch-row {
31812
32100
  painted once as body background layers (no extra composited layers).
31813
32101
  PERF: tiles stay >= 22px and no backdrop-filter, ever. */
31814
32102
  body {
31815
- background-color: var(--vr-bg);
32103
+ /* manga skin: clean ink base. The full-bleed speed-line art was removed \u2014
32104
+ it read as disconnected wallpaper behind the chat. Just a fine screentone
32105
+ dot grid + a faint gold glow at the top edge. */
32106
+ background-color: #0a0a0d;
31816
32107
  background-image:
31817
- radial-gradient(circle, rgba(255, 210, 110, 0.05) 1.2px, transparent 1.5px),
31818
- radial-gradient(ellipse 65% 50% at 22% 0%, rgba(255, 80, 70, 0.2), transparent 65%),
31819
- radial-gradient(ellipse 50% 45% at 90% 38%, rgba(255, 170, 60, 0.14), transparent 65%),
31820
- radial-gradient(ellipse 55% 40% at 55% 108%, rgba(200, 40, 50, 0.14), transparent 65%);
31821
- background-size: 24px 24px, 100% 100%, 100% 100%, 100% 100%;
32108
+ radial-gradient(circle, rgba(255, 255, 255, 0.035) 1.2px, transparent 1.5px),
32109
+ radial-gradient(ellipse 72% 46% at 50% -6%, rgba(232, 193, 90, 0.05), transparent 62%);
32110
+ background-size: 24px 24px, 100% 100%;
32111
+ background-position: 0 0, center;
32112
+ background-repeat: repeat, no-repeat;
32113
+ /* scroll, not fixed: body never scrolls (overflow:hidden) so it looks
32114
+ identical, and fixed-attachment on the large art stalls headless capture. */
32115
+ background-attachment: scroll, scroll, scroll;
31822
32116
  }
31823
32117
 
31824
32118
  /* Glow buttons become solid, quiet primaries. */
@@ -33795,7 +34089,7 @@ body {
33795
34089
  ========================================================================== */
33796
34090
  :root {
33797
34091
  --vr-hud-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Consolas, monospace;
33798
- --vr-hud-surface: rgba(23, 7, 9, 0.92);
34092
+ --vr-hud-surface: rgba(13, 12, 16, 0.92);
33799
34093
  --vr-hud-line: rgba(232, 193, 90, 0.16);
33800
34094
  }
33801
34095
 
@@ -33911,6 +34205,20 @@ body {
33911
34205
  word-break: break-word;
33912
34206
  }
33913
34207
 
34208
+ /* Raw transcript guard: agent dumped grep/tool output instead of a clean answer.
34209
+ Show a themed headline + collapse the raw lines into the work log. */
34210
+ .vr-agent-answer-raw { display: flex; flex-direction: column; gap: 6px; }
34211
+ .vr-agent-raw-headline {
34212
+ margin: 0;
34213
+ font-weight: 700;
34214
+ color: var(--vr-gold-bright);
34215
+ }
34216
+ .vr-agent-raw-sub {
34217
+ margin: 0 0 4px;
34218
+ font-size: 12px;
34219
+ color: var(--vr-muted);
34220
+ }
34221
+
33914
34222
  /* Retire legacy state styling: the frame never changes with state.
33915
34223
  Only the three non-rail sides \u2014 border-left stays the signal color. */
33916
34224
  .vr-chat-agent-response.is-ready,
@@ -34824,7 +35132,7 @@ button.vr-chat-gate-chip.vr-stamp i { display: none; }
34824
35132
  Provider dots and version chips are untouched by design.
34825
35133
  ========================================================================== */
34826
35134
  :root {
34827
- --vr-display: "Bahnschrift SemiCondensed", "Bahnschrift", "Avenir Next Condensed", "Arial Narrow", "Segoe UI", sans-serif;
35135
+ --vr-display: "Anton", "Bahnschrift SemiCondensed", "Bahnschrift", "Avenir Next Condensed", "Arial Narrow", "Segoe UI", sans-serif;
34828
35136
  --vr-ink: rgba(232, 193, 90, 0.6);
34829
35137
  }
34830
35138
 
@@ -34903,13 +35211,15 @@ button.vr-chat-gate-chip.vr-stamp i { display: none; }
34903
35211
  HUD layer: full conversion \u2014 the display voice and flat tags everywhere.
34904
35212
  ========================================================================== */
34905
35213
 
34906
- /* Every button in the shell speaks the display voice (stamps stay mono). */
35214
+ /* Buttons default to the READABLE sans (was condensed display caps \u2014 the #1
35215
+ readability complaint). Marquee buttons (tabs, big CTAs) opt back into Anton
35216
+ via their own higher-specificity rules in the readability pass. */
34907
35217
  .vr-composed-shell button:not(.vr-stamp),
34908
35218
  .vr-modal-card button:not(.vr-stamp),
34909
35219
  .vr-composed-shell .vr-agent-inline-action {
34910
- font-family: var(--vr-display);
34911
- text-transform: uppercase;
34912
- letter-spacing: 0.05em;
35220
+ font-family: var(--vr-hud-sans, 'Geist', 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif);
35221
+ text-transform: none;
35222
+ letter-spacing: 0.01em;
34913
35223
  }
34914
35224
 
34915
35225
  /* Kill the pill: every badge/chip/tag becomes a flat mono tag.
@@ -35288,7 +35598,7 @@ button.vr-tcard { appearance: none; }
35288
35598
  border: 2.5px solid #a8743a;
35289
35599
  background:
35290
35600
  radial-gradient(circle at 50% 42%, rgba(232, 193, 90, 0.12), transparent 60%),
35291
- repeating-linear-gradient(45deg, #241012, #241012 7px, #2e1517 7px, #2e1517 14px);
35601
+ repeating-linear-gradient(45deg, #141418, #141418 7px, #1b1b22 7px, #1b1b22 14px);
35292
35602
  align-items: center;
35293
35603
  justify-content: center;
35294
35604
  gap: 6px;
@@ -35343,10 +35653,10 @@ button.vr-tcard { appearance: none; }
35343
35653
  }
35344
35654
 
35345
35655
  .vr-tcard[data-foil='sentry'] {
35346
- background: linear-gradient(160deg, #ffe3ea, #c2405e 30%, #ffd4de 52%, #7a1c30 78%, #ffd76e);
35656
+ background: linear-gradient(160deg, #ffe3ea, #c2405e 30%, #ffd4de 52%, #343040 78%, #ffd76e);
35347
35657
  }
35348
35658
  .vr-tcard[data-foil='sentry'] .vr-tcard-artwin {
35349
- background: radial-gradient(circle at 50% 42%, #5a2033, #170a10 78%);
35659
+ background: radial-gradient(circle at 50% 42%, #2b2732, #170a10 78%);
35350
35660
  }
35351
35661
  .vr-tcard[data-foil='sentry'] .vr-tcard-artwin .vr-provider-token.is-tcard {
35352
35662
  filter: drop-shadow(0 0 9px #ff8fa8);
@@ -35494,7 +35804,7 @@ button.vr-tcard { appearance: none; }
35494
35804
  place-items: center;
35495
35805
  border: 1.5px solid var(--vr-border);
35496
35806
  border-radius: 10px;
35497
- background: rgba(23, 7, 9, 0.55);
35807
+ background: rgba(13, 12, 16, 0.55);
35498
35808
  color: var(--vr-muted);
35499
35809
  cursor: pointer;
35500
35810
  transition: border-color 100ms ease, color 100ms ease, transform 100ms ease;
@@ -35557,7 +35867,7 @@ button.vr-tcard { appearance: none; }
35557
35867
  padding: 12px;
35558
35868
  border: 2.5px solid var(--vr-gold);
35559
35869
  border-radius: 50%;
35560
- background: radial-gradient(circle at 40% 35%, #3a1216, #170709);
35870
+ background: radial-gradient(circle at 40% 35%, #17171c, #0b0b0d);
35561
35871
  box-shadow: 0 0 16px rgba(232, 193, 90, 0.3);
35562
35872
  transition: transform 120ms ease, box-shadow 120ms ease;
35563
35873
  }
@@ -35583,7 +35893,7 @@ button.vr-tcard { appearance: none; }
35583
35893
  border: none;
35584
35894
  border-radius: 0;
35585
35895
  background:
35586
- radial-gradient(ellipse 65% 50% at 25% 0%, rgba(255, 80, 70, 0.2), transparent 65%),
35896
+ radial-gradient(ellipse 65% 50% at 25% 0%, rgba(232, 193, 90, 0.2), transparent 65%),
35587
35897
  radial-gradient(ellipse 50% 45% at 88% 75%, rgba(255, 170, 60, 0.14), transparent 65%),
35588
35898
  var(--vr-bg);
35589
35899
  }
@@ -35663,11 +35973,11 @@ button.vr-tcard { appearance: none; }
35663
35973
  inset: 8px 12px 6px 4px;
35664
35974
  border-radius: 30px;
35665
35975
  background:
35666
- radial-gradient(ellipse 85% 75% at 50% 42%, rgba(66, 20, 24, 0.92), rgba(34, 10, 12, 0.96) 78%),
35667
- #1c080a;
35976
+ radial-gradient(ellipse 85% 75% at 50% 42%, rgba(24, 24, 30, 0.92), rgba(12, 12, 16, 0.96) 78%),
35977
+ #0e0e12;
35668
35978
  border: 2px solid rgba(232, 193, 90, 0.32);
35669
35979
  box-shadow:
35670
- inset 0 0 0 7px rgba(52, 24, 14, 0.95),
35980
+ inset 0 0 0 7px rgba(26, 24, 30, 0.95),
35671
35981
  inset 0 0 0 9px rgba(232, 193, 90, 0.14),
35672
35982
  inset 0 0 90px rgba(0, 0, 0, 0.55),
35673
35983
  0 24px 60px rgba(0, 0, 0, 0.55);
@@ -35871,7 +36181,7 @@ button.vr-tcard { appearance: none; }
35871
36181
  padding: 10px 14px;
35872
36182
  border: 1.5px solid rgba(255, 184, 77, 0.55);
35873
36183
  border-radius: 10px;
35874
- background: rgba(60, 34, 8, 0.5);
36184
+ background: rgba(34, 30, 24, 0.5);
35875
36185
  color: var(--vr-text);
35876
36186
  font-size: 12.5px;
35877
36187
  }
@@ -36059,7 +36369,7 @@ button.vr-tcard { appearance: none; }
36059
36369
  /* chats flyout: opaque, above the table views, clicks land on it */
36060
36370
  .vr-left-context-rail.is-table-rail { z-index: 60; }
36061
36371
  .vr-chats-flyout {
36062
- background: #1c080a;
36372
+ background: #0e0e12;
36063
36373
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.75), 0 0 0 1px rgba(232, 193, 90, 0.12);
36064
36374
  z-index: 90;
36065
36375
  }
@@ -36107,7 +36417,7 @@ button.vr-tcard { appearance: none; }
36107
36417
  background:
36108
36418
  linear-gradient(160deg, rgba(232, 193, 90, 0.12), transparent 45%),
36109
36419
  var(--vr-plate);
36110
- box-shadow: 0 10px 22px rgba(0, 0, 0, 0.5), inset 0 0 0 1.5px rgba(20, 8, 6, 0.9), inset 0 0 0 2.5px rgba(232, 193, 90, 0.2);
36420
+ box-shadow: 0 10px 22px rgba(0, 0, 0, 0.5), inset 0 0 0 1.5px rgba(15, 14, 18, 0.9), inset 0 0 0 2.5px rgba(232, 193, 90, 0.2);
36111
36421
  padding: 10px 12px;
36112
36422
  }
36113
36423
  .vr-studio-architecture.is-region-map .vr-arch-node:not(.is-card) strong {
@@ -36117,7 +36427,7 @@ button.vr-tcard { appearance: none; }
36117
36427
  }
36118
36428
  .vr-studio-architecture.is-region-map .vr-arch-node:not(.is-card):hover {
36119
36429
  border-color: var(--vr-gold);
36120
- box-shadow: 0 12px 26px rgba(0, 0, 0, 0.55), 0 0 16px rgba(232, 193, 90, 0.25), inset 0 0 0 1.5px rgba(20, 8, 6, 0.9), inset 0 0 0 2.5px rgba(232, 193, 90, 0.3);
36430
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.55), 0 0 16px rgba(232, 193, 90, 0.25), inset 0 0 0 1.5px rgba(15, 14, 18, 0.9), inset 0 0 0 2.5px rgba(232, 193, 90, 0.3);
36121
36431
  }
36122
36432
  /* chat messages wear the card chrome */
36123
36433
  .vr-chat-message.is-agent.vr-hud-card,
@@ -36253,7 +36563,7 @@ button.vr-tcard { appearance: none; }
36253
36563
  background-size: 7px 7px;
36254
36564
  }
36255
36565
  .vr-qs-card[data-step='1'] .vr-qs-art { background: radial-gradient(circle at 50% 42%, #1f5a35, #08170e 78%); }
36256
- .vr-qs-card[data-step='3'] .vr-qs-art { background: radial-gradient(circle at 50% 42%, #5a1c22, #170608 78%); }
36566
+ .vr-qs-card[data-step='3'] .vr-qs-art { background: radial-gradient(circle at 50% 42%, #2b2732, #170608 78%); }
36257
36567
  .vr-qs-card[data-step='4'] .vr-qs-art { background: radial-gradient(circle at 50% 42%, #1c3a5a, #060e17 78%); }
36258
36568
  .vr-qs-glyph { position: relative; }
36259
36569
  .vr-qs-glyph i[data-icon] { width: 30px; height: 30px; color: #ffe9ad; filter: drop-shadow(0 0 9px rgba(255, 233, 173, 0.8)); }
@@ -36440,10 +36750,10 @@ button.vr-tcard { appearance: none; }
36440
36750
  ========================================================================== */
36441
36751
  .vr-studio-architecture.is-region-map {
36442
36752
  background:
36443
- radial-gradient(ellipse 85% 75% at 50% 42%, rgba(66, 20, 24, 0.92), rgba(34, 10, 12, 0.96) 78%),
36444
- #1c080a;
36753
+ radial-gradient(ellipse 85% 75% at 50% 42%, rgba(24, 24, 30, 0.92), rgba(12, 12, 16, 0.96) 78%),
36754
+ #0e0e12;
36445
36755
  box-shadow:
36446
- inset 0 0 0 7px rgba(52, 24, 14, 0.95),
36756
+ inset 0 0 0 7px rgba(26, 24, 30, 0.95),
36447
36757
  inset 0 0 0 9px rgba(232, 193, 90, 0.14),
36448
36758
  inset 0 0 90px rgba(0, 0, 0, 0.55);
36449
36759
  border-radius: 22px;
@@ -36649,7 +36959,7 @@ button.vr-tcard { appearance: none; }
36649
36959
  width: min(460px, 92%);
36650
36960
  border: 2px solid var(--vr-border-strong);
36651
36961
  border-radius: 14px;
36652
- background: #1c080a;
36962
+ background: #0e0e12;
36653
36963
  box-shadow: 0 22px 48px rgba(0, 0, 0, 0.7), 0 0 24px rgba(232, 193, 90, 0.15);
36654
36964
  overflow: hidden;
36655
36965
  }
@@ -36790,32 +37100,54 @@ button.vr-tcard { appearance: none; }
36790
37100
  z-index: 25;
36791
37101
  display: flex;
36792
37102
  align-items: center;
36793
- gap: 10px;
37103
+ gap: 14px;
36794
37104
  margin: 0;
36795
- padding: 4px 14px;
36796
- border: 1.5px solid rgba(232, 193, 90, 0.24);
36797
- border-radius: 999px;
36798
- background: rgba(20, 7, 8, 0.62);
36799
- pointer-events: none;
37105
+ padding: 5px 8px 5px 4px;
37106
+ border: 0;
37107
+ background: none;
36800
37108
  white-space: nowrap;
36801
37109
  max-width: 100%;
36802
37110
  overflow: hidden;
36803
37111
  }
37112
+ .vr-ribbon-brand { display: flex; align-items: center; gap: 8px; }
36804
37113
  .vr-table-ribbon strong {
36805
- font-family: var(--vr-display);
36806
- font-size: 11px;
36807
- font-weight: 800;
36808
- letter-spacing: 2px;
36809
- color: var(--vr-gold-bright);
37114
+ font-family: var(--mg-display);
37115
+ font-size: 13px;
37116
+ font-weight: 400;
37117
+ letter-spacing: 1.5px;
37118
+ color: var(--vr-text);
37119
+ }
37120
+ /* the stats row that replaced the old crimson ribbon text */
37121
+ .vr-ribbon-stats { display: flex; align-items: stretch; gap: 0; }
37122
+ .vr-ribbon-stat {
37123
+ display: flex;
37124
+ flex-direction: column;
37125
+ justify-content: center;
37126
+ padding: 2px 13px;
37127
+ border-left: 1px solid var(--vr-border);
37128
+ }
37129
+ .vr-ribbon-stat:first-child { border-left: 0; }
37130
+ .vr-ribbon-stat b {
37131
+ font-family: var(--mg-display);
37132
+ font-weight: 400;
37133
+ font-size: 16px;
37134
+ line-height: 1;
37135
+ color: var(--vr-text);
36810
37136
  }
36811
- .vr-table-ribbon em {
37137
+ .vr-ribbon-stat i {
36812
37138
  font-style: normal;
37139
+ margin-top: 3px;
36813
37140
  font-family: var(--vr-hud-mono);
36814
- font-size: 8px;
36815
- letter-spacing: 1.4px;
36816
- color: var(--vr-muted);
37141
+ font-size: 8.5px;
37142
+ font-weight: 600;
37143
+ letter-spacing: 0.08em;
36817
37144
  text-transform: uppercase;
37145
+ color: var(--vr-muted);
36818
37146
  }
37147
+ .vr-ribbon-stat.is-gold b { color: var(--vr-gold-bright); }
37148
+ .vr-ribbon-stat.is-green b { color: var(--vr-green); }
37149
+ .vr-ribbon-stat.is-orange b { color: var(--vr-orange); }
37150
+ .vr-ribbon-stat.is-muted b { color: rgba(244, 241, 232, 0.55); }
36819
37151
  .vr-chat-lane.is-primary .vr-chat-transcript { padding-top: 16px; }
36820
37152
 
36821
37153
  /* -- the card dock: a SLIM strip on the table rim, like a real card game.
@@ -37396,6 +37728,29 @@ button.vr-tcard { appearance: none; }
37396
37728
  .vr-discover-card.is-flipped[data-rank='legendary'] .vr-flip-face .vr-tcard { animation: vr-reveal-legendary 1100ms ease-out 240ms both; }
37397
37729
  .vr-discover-card.is-flipped[data-rank='epic'] .vr-flip-face .vr-tcard { animation: vr-reveal-epic 1100ms ease-out 240ms both; }
37398
37730
  .vr-discover-card.is-flipped[data-rank='rare'] .vr-flip-face .vr-tcard { animation: vr-reveal-rare 1100ms ease-out 240ms both; }
37731
+ /* --- pack-opening polish --- */
37732
+ /* contain the collector raven so it never overlaps the first card + separate the header */
37733
+ .vr-modal-discover .vr-qs-head {
37734
+ align-items: center;
37735
+ gap: 16px;
37736
+ margin: 0 0 16px;
37737
+ padding-bottom: 14px;
37738
+ border-bottom: 1px solid rgba(255, 215, 110, 0.14);
37739
+ }
37740
+ .vr-modal-discover .vr-qs-raven { width: 72px; height: 72px; }
37741
+ /* COMMON tier (silver) so the pack reads like a real spread, not all-RARE */
37742
+ .vr-discover-card[data-rank='common'] .vr-discover-rank { color: #cbd5e1; text-shadow: 0 0 6px rgba(148, 163, 184, 0.55); }
37743
+ @keyframes vr-reveal-common {
37744
+ 0% { box-shadow: 0 0 0 rgba(148, 163, 184, 0); }
37745
+ 35% { box-shadow: 0 0 24px rgba(203, 213, 225, 0.55), 0 0 48px rgba(148, 163, 184, 0.28); }
37746
+ 100% { box-shadow: 0 0 8px rgba(148, 163, 184, 0.22); }
37747
+ }
37748
+ .vr-discover-card.is-flipped[data-rank='common'] .vr-flip-face .vr-tcard { animation: vr-reveal-common 1000ms ease-out 240ms both; }
37749
+ /* Hearthstone-style: a rare pull jumps forward as it flips (not on stable re-renders) */
37750
+ .vr-modal-card:not(.is-stable-render) .vr-discover-card.is-flipped[data-rank='legendary'] { animation: vr-pop-hi 720ms cubic-bezier(0.2, 1.5, 0.3, 1) 180ms both; z-index: 5; }
37751
+ .vr-modal-card:not(.is-stable-render) .vr-discover-card.is-flipped[data-rank='epic'] { animation: vr-pop-mid 660ms cubic-bezier(0.2, 1.45, 0.3, 1) 180ms both; z-index: 4; }
37752
+ @keyframes vr-pop-hi { 0%, 100% { transform: scale(1); } 45% { transform: scale(1.16); } }
37753
+ @keyframes vr-pop-mid { 0%, 100% { transform: scale(1); } 45% { transform: scale(1.1); } }
37399
37754
  .vr-discover-take {
37400
37755
  border-color: var(--vr-gold-bright) !important;
37401
37756
  background: var(--vr-gold-bright) !important;
@@ -37413,7 +37768,7 @@ button.vr-tcard { appearance: none; }
37413
37768
  background:
37414
37769
  radial-gradient(circle at 50% 42%, rgba(232, 193, 90, 0.35), rgba(232, 193, 90, 0.08) 32%, transparent 44%),
37415
37770
  repeating-radial-gradient(circle at 50% 42%, rgba(232, 193, 90, 0.1) 0 2px, transparent 2px 10px),
37416
- radial-gradient(ellipse at 50% 44%, #3a1216, #1c080a 82%);
37771
+ radial-gradient(ellipse at 50% 44%, #17171c, #0e0e12 82%);
37417
37772
  }
37418
37773
  .vr-qs-back img { width: 54px; height: 54px; opacity: 0.9; }
37419
37774
  .vr-qs-back b {
@@ -37514,8 +37869,8 @@ button.vr-tcard { appearance: none; }
37514
37869
  background:
37515
37870
  radial-gradient(circle at 50% 44%, rgba(232, 193, 90, 0.42), rgba(232, 193, 90, 0.1) 36%, transparent 50%),
37516
37871
  repeating-radial-gradient(circle at 50% 44%, rgba(232, 193, 90, 0.1) 0 2px, transparent 2px 11px),
37517
- conic-gradient(from 210deg at 50% 46%, #240c0f, #451519 22%, #2a0d10 48%, #3f1317 74%, #240c0f),
37518
- #1c080a;
37872
+ conic-gradient(from 210deg at 50% 46%, #17171d, #232329 22%, #18181f 48%, #20202a 74%, #17171d),
37873
+ #0e0e12;
37519
37874
  }
37520
37875
  .vr-tcard.is-back::after {
37521
37876
  content: '';
@@ -37572,117 +37927,2295 @@ button.vr-tcard { appearance: none; }
37572
37927
  margin: 0;
37573
37928
  }
37574
37929
  .vr-discover-grid { gap: 40px 14px; padding-bottom: 30px; }
37575
- `;
37576
- }
37577
- });
37578
37930
 
37579
- // src/local-ui/static/appHtml.ts
37580
- function escapeJsonForHtml(value) {
37581
- return JSON.stringify(value).replace(/</g, "\\u003c");
37931
+ /* ==========================================================================
37932
+ manga ink pass \u2014 the display voice becomes Anton. The HUD layer above set
37933
+ headings to Bahnschrift (a generic condensed); this final override swaps the
37934
+ marquee surfaces to the real loaded poster face so the Studio reads manga,
37935
+ not "condensed sans." Buttons and small labels stay Bahnschrift for
37936
+ legibility \u2014 Anton at 11px all-caps turns to mud.
37937
+ ========================================================================== */
37938
+ .vr-composed-shell h1,
37939
+ .vr-composed-shell h2,
37940
+ .vr-composed-shell h3,
37941
+ .vr-modal-card h2,
37942
+ .vr-modal-card h3,
37943
+ .vr-quickstrike header,
37944
+ .vr-rail-launcher strong,
37945
+ .vr-arch-node > div strong,
37946
+ .vr-studio-architecture.is-region-map .vr-arch-node:not(.is-card) strong,
37947
+ .vr-card-portrait strong {
37948
+ font-family: var(--mg-display);
37949
+ text-transform: uppercase;
37950
+ letter-spacing: 0.03em;
37951
+ font-weight: 400;
37952
+ /* faint gold ink bleed so the poster type sits on the felt, not above it */
37953
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.55);
37582
37954
  }
37583
- function renderLocalUiHtml(state) {
37584
- return `<!doctype html>
37585
- <html lang="en">
37586
- <head>
37587
- <meta charset="utf-8" />
37588
- <meta name="viewport" content="width=device-width, initial-scale=1" />
37589
- <meta name="theme-color" content="#050816" />
37590
- <title>VibeRaven</title>
37591
- <link rel="icon" type="image/png" href="/report/assets/viberaven-favicon.png?v=raven-cards-3" />
37592
- <link rel="shortcut icon" type="image/png" href="/report/assets/viberaven-favicon.png?v=raven-cards-3" />
37593
- <link rel="apple-touch-icon" href="/report/assets/viberaven-favicon.png?v=raven-cards-3" />
37594
- <link rel="preload" as="image" href="/report/assets/viberaven-mascot.png?v=raven-cards-2" />
37595
- <style>${localUiCss}</style>
37596
- </head>
37597
- <body>
37598
- <div id="vr-app" class="vr-app" aria-label="VibeRaven"></div>
37599
- <script id="vr-state" type="application/json">${escapeJsonForHtml(state)}</script>
37600
- <script>${localUiClientJs}</script>
37601
- </body>
37602
- </html>`;
37955
+ /* Anton has no bold \u2014 its native weight already reads heavy. Give the big
37956
+ section headers a touch more air so the caps don't crowd. */
37957
+ .vr-composed-shell h1 { letter-spacing: 0.02em; }
37958
+
37959
+ /* ==========================================================================
37960
+ manga chat: the VibeRaven voice wears the fierce raven emblem (refs 1 & 3
37961
+ put the raven bust beside the agent output). The coin frames the raven's
37962
+ face out of the transparent emblem; a gold ring seats it as an avatar.
37963
+ ========================================================================== */
37964
+ .vr-story-node.has-raven .vr-story-dot {
37965
+ width: 34px;
37966
+ height: 34px;
37967
+ padding: 0;
37968
+ overflow: hidden;
37969
+ border: 1.5px solid var(--vr-border-strong);
37970
+ background: radial-gradient(circle at 50% 40%, #1a1a1f, #0a0a0d);
37971
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.35), 0 0 12px rgba(232, 193, 90, 0.18);
37972
+ }
37973
+ .vr-raven-coin {
37974
+ width: 100%;
37975
+ height: 100%;
37976
+ object-fit: cover;
37977
+ /* frame the raven's face (right side of the emblem art), not the sign */
37978
+ object-position: 74% 40%;
37979
+ transform: scale(1.55);
37980
+ }
37981
+ .vr-story-node.is-error.has-raven .vr-story-dot { border-color: rgba(239, 68, 68, 0.6); }
37982
+ /* the intro card's inline raven badge */
37983
+ .vr-chat-intro-message .vr-intro-raven {
37984
+ display: inline-grid;
37985
+ place-items: center;
37986
+ width: 30px;
37987
+ height: 30px;
37988
+ border-radius: 50%;
37989
+ overflow: hidden;
37990
+ border: 1.5px solid var(--vr-border-strong);
37991
+ background: radial-gradient(circle at 50% 40%, #1a1a1f, #0a0a0d);
37992
+ }
37993
+ .vr-chat-intro-message .vr-intro-raven img {
37994
+ width: 100%;
37995
+ height: 100%;
37996
+ object-fit: cover;
37997
+ object-position: 74% 40%;
37998
+ transform: scale(1.55);
37603
37999
  }
37604
- var init_appHtml = __esm({
37605
- "src/local-ui/static/appHtml.ts"() {
37606
- "use strict";
37607
- init_appClient();
37608
- init_appCss();
37609
- }
37610
- });
37611
38000
 
37612
- // src/local-ui/server.ts
37613
- function normalizeShellCommand(value) {
37614
- return value.trim().replace(/\s+/g, " ");
38001
+ /* ==========================================================================
38002
+ manga card frame \u2014 match the gold-on-black provider-card asset (refs 3 & 4).
38003
+ Additive over the round-6/7 trading-card anatomy: ink-black inner (kills the
38004
+ last crimson tint at #140a0c), a gold halftone burst behind the glyph, a
38005
+ gold inset double-frame line, and Anton on the card name. Brand-colored
38006
+ glyphs stay (the binder mockup keeps provider colors on the gold frame).
38007
+ ========================================================================== */
38008
+ .vr-tcard:not(.is-back) .vr-tcard-inner {
38009
+ background: #0c0c10;
38010
+ /* the second, inner gold line \u2014 reads as the ornate double frame */
38011
+ box-shadow: inset 0 0 0 1px rgba(232, 193, 90, 0.28);
38012
+ }
38013
+ .vr-tcard:not(.is-back) .vr-tcard-artwin {
38014
+ border-color: rgba(232, 193, 90, 0.32);
38015
+ /* gold burst radiating from behind the provider glyph */
38016
+ background:
38017
+ radial-gradient(circle at 50% 46%, rgba(232, 193, 90, 0.22), rgba(232, 193, 90, 0.05) 42%, transparent 68%),
38018
+ #08080b;
37615
38019
  }
37616
- function resolveApprovedShellCommand(command, requestedCliId) {
37617
- const normalized = normalizeShellCommand(command);
37618
- const candidateIds = requestedCliId ? [requestedCliId] : ["codex", "claude", "gemini"];
37619
- for (const cliId of candidateIds) {
37620
- const setup = CLI_AGENT_SETUP[cliId];
37621
- const install = normalizeShellCommand(setup.installCommand);
37622
- if (normalized === install) {
37623
- return { cliId, step: "install", command: setup.installCommand };
37624
- }
37625
- const signIn = normalizeShellCommand(setup.signInCommand);
37626
- if (normalized === signIn) {
37627
- return { cliId, step: "sign-in", command: setup.signInCommand };
37628
- }
37629
- }
37630
- return null;
38020
+ /* fine gold sunburst rays over the halftone \u2014 the asset's signature shimmer */
38021
+ .vr-tcard:not(.is-back) .vr-tcard-artwin::after {
38022
+ content: '';
38023
+ position: absolute;
38024
+ inset: 0;
38025
+ background:
38026
+ repeating-conic-gradient(from 0deg at 50% 46%,
38027
+ rgba(232, 193, 90, 0.10) 0deg 2deg, transparent 2deg 10deg);
38028
+ -webkit-mask-image: radial-gradient(circle at 50% 46%, #000 0, transparent 70%);
38029
+ mask-image: radial-gradient(circle at 50% 46%, #000 0, transparent 70%);
38030
+ pointer-events: none;
38031
+ z-index: 0;
37631
38032
  }
37632
- async function connectProviderMcp(cwd, providerId) {
37633
- const recipe = MCP_SERVER_RECIPES[providerId];
37634
- if (!recipe) {
37635
- return { ok: false, message: `No known MCP server for ${providerId} yet. Add one manually to .mcp.json.` };
37636
- }
37637
- const configPath = (0, import_node_path30.join)(cwd, ".mcp.json");
37638
- let config = {};
37639
- try {
37640
- const raw = await (0, import_promises23.readFile)(configPath, "utf8");
37641
- const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
37642
- if (isRecord4(parsed)) config = parsed;
37643
- } catch {
37644
- }
37645
- const servers = isRecord4(config.mcpServers) ? { ...config.mcpServers } : {};
37646
- if (servers[providerId]) {
37647
- return { ok: true, message: `${providerId} MCP server already configured in .mcp.json.`, path: ".mcp.json" };
37648
- }
37649
- servers[providerId] = recipe.entry;
37650
- const next = { ...config, mcpServers: servers };
37651
- await (0, import_promises23.writeFile)(configPath, `${JSON.stringify(next, null, 2)}
37652
- `, "utf8");
37653
- const suffix = recipe.note ? ` ${recipe.note}` : "";
37654
- return { ok: true, message: `Added ${providerId} MCP server to .mcp.json.${suffix}`, path: ".mcp.json" };
38033
+ .vr-tcard:not(.is-back) .vr-tcard-artwin .vr-provider-token.is-tcard { z-index: 1; }
38034
+ /* card name speaks the poster face */
38035
+ .vr-tcard-head b {
38036
+ font-family: var(--mg-display);
38037
+ font-weight: 400;
38038
+ letter-spacing: 0.03em;
38039
+ color: var(--vr-gold-bright);
37655
38040
  }
37656
- async function defaultScanRunner(flags, positional) {
37657
- const cli = await Promise.resolve().then(() => (init_cli(), cli_exports));
37658
- return cli.runScanCommand(flags, positional);
38041
+ /* corner ticks: four gold L-brackets tucked into the frame corners */
38042
+ .vr-tcard:not(.is-back) .vr-tcard-inner::before {
38043
+ content: '';
38044
+ position: absolute;
38045
+ inset: 3px;
38046
+ pointer-events: none;
38047
+ z-index: 3;
38048
+ background:
38049
+ linear-gradient(var(--vr-gold-bright), var(--vr-gold-bright)) left top,
38050
+ linear-gradient(var(--vr-gold-bright), var(--vr-gold-bright)) left top,
38051
+ linear-gradient(var(--vr-gold-bright), var(--vr-gold-bright)) right top,
38052
+ linear-gradient(var(--vr-gold-bright), var(--vr-gold-bright)) right top,
38053
+ linear-gradient(var(--vr-gold-bright), var(--vr-gold-bright)) left bottom,
38054
+ linear-gradient(var(--vr-gold-bright), var(--vr-gold-bright)) left bottom,
38055
+ linear-gradient(var(--vr-gold-bright), var(--vr-gold-bright)) right bottom,
38056
+ linear-gradient(var(--vr-gold-bright), var(--vr-gold-bright)) right bottom;
38057
+ background-repeat: no-repeat;
38058
+ background-size: 10px 2px, 2px 10px;
38059
+ opacity: 0.9;
38060
+ }
38061
+
38062
+ /* THE collector redesign: the frame is always GOLD FOIL (like the binder
38063
+ mockup + the gold-on-black provider-card asset). Provider colour lives only
38064
+ in the art-window burst and the glyph glow \u2014 not the frame. This is what
38065
+ turns the cards from "casino colored rectangle" into "premium graded slab". */
38066
+ .vr-tcard[data-foil]:not(.is-back) {
38067
+ background: var(--vr-foil);
37659
38068
  }
37660
- async function readPackageJson(cwd) {
37661
- try {
37662
- const raw = await (0, import_promises23.readFile)((0, import_node_path30.join)(cwd, "package.json"), "utf8");
37663
- const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
37664
- return isRecord4(parsed) ? parsed : {};
37665
- } catch {
37666
- return {};
37667
- }
38069
+ .vr-tcard:not(.is-back) {
38070
+ border-radius: 12px;
38071
+ box-shadow:
38072
+ 0 10px 24px rgba(0, 0, 0, 0.6),
38073
+ inset 0 0 0 1px rgba(0, 0, 0, 0.4),
38074
+ inset 0 0 0 2px rgba(255, 233, 173, 0.35);
37668
38075
  }
37669
- function dependencyNames(packageJson) {
37670
- const names = /* @__PURE__ */ new Set();
37671
- for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
37672
- const value = packageJson[key];
37673
- if (!isRecord4(value)) continue;
37674
- Object.keys(value).forEach((name) => names.add(name.toLowerCase()));
37675
- }
37676
- return names;
38076
+ .vr-tcard:not(.is-back):hover {
38077
+ box-shadow:
38078
+ 0 16px 32px rgba(0, 0, 0, 0.65),
38079
+ 0 0 20px rgba(232, 193, 90, 0.4),
38080
+ inset 0 0 0 1px rgba(0, 0, 0, 0.4),
38081
+ inset 0 0 0 2px rgba(255, 233, 173, 0.5);
37677
38082
  }
37678
- async function listProjectFiles(cwd, maxFiles = 1800) {
37679
- const skipped = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", "coverage", ".turbo", ".viberaven"]);
37680
- const files = [];
37681
- async function walk(dir, prefix = "") {
37682
- if (files.length >= maxFiles) return;
37683
- let entries;
37684
- try {
37685
- entries = await (0, import_promises23.readdir)(dir, { withFileTypes: true });
38083
+ /* the RAVEN GRADE line reads like a graded-slab label bar */
38084
+ .vr-tcard-grade {
38085
+ color: #241207;
38086
+ background: linear-gradient(180deg, #f6eed9, #e2d0a8);
38087
+ border-radius: 3px;
38088
+ padding: 2px 6px;
38089
+ font-weight: 700;
38090
+ }
38091
+
38092
+ /* ==========================================================================
38093
+ manga hero watermark \u2014 the empty chat void gets a faint raven emblem
38094
+ bleeding in from the right, like the faded raven behind the mockup chat.
38095
+ Fills dead space and carries the manga presence without hurting readability.
38096
+ ========================================================================== */
38097
+ .vr-chat-lane.is-primary { position: relative; }
38098
+ /* keep the messages above any lane background */
38099
+ .vr-chat-lane.is-primary > * { position: relative; z-index: 1; }
38100
+
38101
+ /* ==========================================================================
38102
+ manga agent message \u2014 the screenshot-5 styling on REAL answers: a fierce
38103
+ raven avatar, an "AI AGENT" badge, and a faded raven bleeding behind the
38104
+ message (like the eye behind the mockup chat). No fake data.
38105
+ ========================================================================== */
38106
+ .vr-chat-agent-response { position: relative; overflow: hidden; }
38107
+ .vr-chat-agent-response::before {
38108
+ content: '';
38109
+ position: absolute;
38110
+ right: -70px;
38111
+ top: 50%;
38112
+ transform: translateY(-50%);
38113
+ width: 340px;
38114
+ height: 340px;
38115
+ background: url('/manga/emblem.png') no-repeat center / contain;
38116
+ opacity: 0.06;
38117
+ pointer-events: none;
38118
+ z-index: 0;
38119
+ }
38120
+ .vr-chat-agent-response > * { position: relative; z-index: 1; }
38121
+ .vr-hud-avatar.is-raven {
38122
+ width: 34px;
38123
+ height: 34px;
38124
+ border-radius: 50%;
38125
+ overflow: hidden;
38126
+ border: 1.5px solid var(--vr-border-strong);
38127
+ background: radial-gradient(circle at 50% 40%, #1a1a1f, #0a0a0d);
38128
+ box-shadow: 0 0 12px rgba(232, 193, 90, 0.18);
38129
+ flex: none;
38130
+ }
38131
+ .vr-hud-avatar.is-raven img {
38132
+ width: 100%;
38133
+ height: 100%;
38134
+ object-fit: cover;
38135
+ object-position: 74% 40%;
38136
+ transform: scale(1.55);
38137
+ }
38138
+ .vr-hud-aibadge {
38139
+ font-family: var(--vr-hud-mono);
38140
+ font-size: 8.5px;
38141
+ font-weight: 700;
38142
+ letter-spacing: 0.12em;
38143
+ text-transform: uppercase;
38144
+ color: var(--vr-gold-bright);
38145
+ padding: 2px 7px;
38146
+ border-radius: 5px;
38147
+ background: rgba(232, 193, 90, 0.12);
38148
+ box-shadow: inset 0 0 0 1px rgba(232, 193, 90, 0.4);
38149
+ }
38150
+
38151
+ /* ==========================================================================
38152
+ full-art provider cards + holographic foil (the "shiny Pok\xE9mon" effect).
38153
+ The card art (assets/manga/cards/<id>.webp) is the whole face; an
38154
+ iridescent foil + a specular glare sweep ride on top and light up on hover.
38155
+ ========================================================================== */
38156
+ .vr-tcard.has-art {
38157
+ padding: 0;
38158
+ border: none;
38159
+ overflow: hidden;
38160
+ border-radius: 11px;
38161
+ background: #000;
38162
+ box-shadow:
38163
+ 0 10px 24px rgba(0, 0, 0, 0.6),
38164
+ inset 0 0 0 1px rgba(255, 233, 173, 0.3);
38165
+ }
38166
+ .vr-tcard.has-art .vr-tcard-artfull {
38167
+ display: block;
38168
+ width: 100%;
38169
+ height: 100%;
38170
+ object-fit: cover;
38171
+ border-radius: 11px;
38172
+ }
38173
+ /* iridescent holo foil \u2014 always faintly present, blooms on hover */
38174
+ .vr-tcard-holofoil {
38175
+ position: absolute;
38176
+ inset: 0;
38177
+ border-radius: 11px;
38178
+ pointer-events: none;
38179
+ z-index: 2;
38180
+ background: repeating-linear-gradient(115deg,
38181
+ rgba(255, 110, 150, 0.16) 0 7px,
38182
+ rgba(120, 200, 255, 0.16) 7px 14px,
38183
+ rgba(150, 255, 190, 0.16) 14px 21px,
38184
+ rgba(255, 240, 150, 0.16) 21px 28px);
38185
+ background-size: 220% 220%;
38186
+ mix-blend-mode: color-dodge;
38187
+ opacity: 0.16;
38188
+ transition: opacity 220ms ease;
38189
+ }
38190
+ /* gold sparkle glints over the foil */
38191
+ .vr-tcard-holofoil::after {
38192
+ content: '';
38193
+ position: absolute;
38194
+ inset: 0;
38195
+ border-radius: 11px;
38196
+ background:
38197
+ radial-gradient(circle at 28% 22%, rgba(255, 255, 255, 0.55), transparent 9%),
38198
+ radial-gradient(circle at 72% 58%, rgba(255, 233, 173, 0.5), transparent 8%),
38199
+ radial-gradient(circle at 52% 86%, rgba(255, 255, 255, 0.4), transparent 7%);
38200
+ mix-blend-mode: screen;
38201
+ opacity: 0.28;
38202
+ }
38203
+ /* specular glare bar that sweeps across on hover */
38204
+ .vr-tcard-glare {
38205
+ position: absolute;
38206
+ inset: 0;
38207
+ border-radius: 11px;
38208
+ pointer-events: none;
38209
+ z-index: 3;
38210
+ background: linear-gradient(115deg, transparent 36%, rgba(255, 255, 255, 0.3) 48%, rgba(255, 240, 200, 0.22) 54%, transparent 64%);
38211
+ background-size: 250% 250%;
38212
+ background-position: 120% 0%;
38213
+ opacity: 0;
38214
+ transition: opacity 200ms ease;
38215
+ }
38216
+ .vr-tcard.has-art:hover { transform: translateY(-6px) scale(1.04); }
38217
+ .vr-tcard.has-art:hover .vr-tcard-holofoil { opacity: 0.5; animation: vr-holo-shift 2.6s linear infinite; }
38218
+ .vr-tcard.has-art:hover .vr-tcard-glare { opacity: 1; animation: vr-glare-sweep 1.1s ease; }
38219
+ .vr-tcard.has-art.is-flawless { box-shadow: 0 12px 26px rgba(0, 0, 0, 0.6), 0 0 22px rgba(232, 193, 90, 0.5), inset 0 0 0 1px rgba(255, 233, 173, 0.55); }
38220
+ .vr-tcard.has-art.is-flawless .vr-tcard-holofoil { opacity: 0.34; }
38221
+ @keyframes vr-holo-shift { 0% { background-position: 0% 0%; } 100% { background-position: 220% 220%; } }
38222
+ @keyframes vr-glare-sweep { 0% { background-position: 120% 0%; } 100% { background-position: -40% 0%; } }
38223
+ /* grade medallion \u2014 a clean gold-ringed ink coin that suits the ornate art
38224
+ (was a pill with a colored energy dot that looked tacked-on). */
38225
+ .vr-tcard.has-art .vr-tcard-hp.is-onart {
38226
+ position: absolute;
38227
+ top: 7px;
38228
+ right: 7px;
38229
+ z-index: 4;
38230
+ display: grid;
38231
+ place-items: center;
38232
+ width: 21px;
38233
+ height: 21px;
38234
+ padding: 0;
38235
+ border-radius: 50%;
38236
+ background: radial-gradient(circle at 50% 34%, #2b2721, #0b0a0e);
38237
+ box-shadow: inset 0 0 0 1.5px rgba(255, 233, 173, 0.7), 0 1px 3px rgba(0, 0, 0, 0.6);
38238
+ }
38239
+ .vr-tcard.has-art .vr-tcard-hp.is-onart .vr-tcard-energy { display: none; }
38240
+ .vr-tcard.has-art .vr-tcard-hp.is-onart i {
38241
+ font-family: var(--mg-display);
38242
+ font-style: normal;
38243
+ font-size: 12px;
38244
+ line-height: 1;
38245
+ color: var(--vr-gold-bright);
38246
+ }
38247
+ .vr-tcard.is-hand.has-art .vr-tcard-hp.is-onart { width: 17px; height: 17px; top: 5px; right: 5px; }
38248
+ .vr-tcard.is-hand.has-art .vr-tcard-hp.is-onart i { font-size: 9.5px; }
38249
+ /* the baked art already carries the RAVEN GRADE band; hide the overlay strip */
38250
+ .vr-tcard.has-art .vr-tcard-gradebar { display: none; }
38251
+
38252
+ /* the agent message already carries the raven in its header (.vr-hud-avatar)
38253
+ and the intro carries .vr-intro-raven \u2014 so the timeline story-dot drops back
38254
+ to a small plain node instead of a second, redundant raven coin. */
38255
+ .vr-story-node.has-raven .vr-story-dot {
38256
+ width: 12px;
38257
+ height: 12px;
38258
+ margin-top: 16px;
38259
+ border-color: rgba(232, 193, 90, 0.5);
38260
+ background: radial-gradient(circle, var(--vr-gold) 0 2px, rgba(12, 13, 16, 0.96) 3px);
38261
+ box-shadow: 0 0 8px rgba(232, 193, 90, 0.25);
38262
+ }
38263
+ .vr-story-node.has-raven .vr-story-dot .vr-raven-coin { display: none; }
38264
+
38265
+ /* ==========================================================================
38266
+ manga comic panels \u2014 the real restructure. The chat panels were generic
38267
+ rounded-dark rectangles (why it "looked the same"). Now: hard notched
38268
+ corners (comic-panel cut), a bold gold spine on the left, faint gold
38269
+ halftone screentone inside, and a gold tab behind the header label.
38270
+ Applies to the MISSION cards and the VibeRaven agent answers.
38271
+ ========================================================================== */
38272
+ .vr-chat-message.is-user.is-mission,
38273
+ .vr-chat-agent-response {
38274
+ position: relative;
38275
+ border: 1.5px solid var(--vr-border-strong) !important;
38276
+ border-left-width: 3px !important;
38277
+ border-radius: 4px !important;
38278
+ /* two opposite corners cut = comic panel */
38279
+ clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 20px 100%, 0 calc(100% - 20px)) !important;
38280
+ background-color: rgba(13, 12, 16, 0.94) !important;
38281
+ background-image:
38282
+ radial-gradient(rgba(232, 193, 90, 0.055) 1px, transparent 1.4px),
38283
+ linear-gradient(160deg, rgba(232, 193, 90, 0.05), transparent 34%) !important;
38284
+ background-size: 9px 9px, auto !important;
38285
+ }
38286
+ /* MISSION panels lean cool-gold, agent panels get the warmer gold spine */
38287
+ .vr-chat-message.is-user.is-mission { border-left-color: var(--vr-gold) !important; }
38288
+ .vr-chat-agent-response.is-blocked { border-left-color: var(--vr-red) !important; }
38289
+ .vr-chat-agent-response.is-nominal { border-left-color: var(--vr-green) !important; }
38290
+
38291
+ /* header labels become inked tabs: MISSION / VIBERAVEN sit on a gold underline */
38292
+ .vr-chat-message.is-user.is-mission .vr-hud-strip strong,
38293
+ .vr-chat-message.is-user.is-mission > header strong,
38294
+ .vr-chat-agent-response .vr-hud-strip strong {
38295
+ position: relative;
38296
+ }
38297
+ .vr-chat-agent-response .vr-hud-strip {
38298
+ border-bottom: 1px solid rgba(232, 193, 90, 0.16);
38299
+ padding-bottom: 7px;
38300
+ margin-bottom: 2px;
38301
+ }
38302
+ /* a small gold corner bracket on the top-left of each panel */
38303
+ .vr-chat-message.is-user.is-mission::after,
38304
+ .vr-chat-agent-response::after {
38305
+ content: '';
38306
+ position: absolute;
38307
+ left: 7px;
38308
+ top: 7px;
38309
+ width: 12px;
38310
+ height: 12px;
38311
+ border-left: 2px solid rgba(232, 193, 90, 0.55);
38312
+ border-top: 2px solid rgba(232, 193, 90, 0.55);
38313
+ pointer-events: none;
38314
+ z-index: 2;
38315
+ }
38316
+
38317
+ /* ==========================================================================
38318
+ composer = a comic panel too (was a soft rounded "old-school" box). Same
38319
+ notched frame + halftone as the chat messages so the column is of-a-piece.
38320
+ ========================================================================== */
38321
+ .vr-chat-lane .vr-chat-command-bar > .vr-chat-composer,
38322
+ .vr-studio-chat:not(.is-split) .vr-chat-lane.is-primary .vr-chat-command-bar > .vr-chat-composer {
38323
+ border: 1.5px solid var(--vr-border-strong) !important;
38324
+ border-radius: 5px !important;
38325
+ /* NO clip-path: the model/command/access menus are descendants and a notch
38326
+ here clipped them when they popped up (looked "corrupted"). */
38327
+ background-color: rgba(13, 12, 16, 0.96) !important;
38328
+ background-image: radial-gradient(rgba(232, 193, 90, 0.045) 1px, transparent 1.4px) !important;
38329
+ background-size: 9px 9px !important;
38330
+ box-shadow: 0 0 24px rgba(232, 193, 90, 0.07) !important;
38331
+ overflow: visible !important;
38332
+ }
38333
+ .vr-chat-lane .vr-chat-command-bar > .vr-chat-composer:focus-within {
38334
+ border-color: var(--vr-gold) !important;
38335
+ box-shadow: 0 0 26px rgba(232, 193, 90, 0.16) !important;
38336
+ }
38337
+
38338
+ /* ==========================================================================
38339
+ the OTHER views join the manga theme: worktree branch cards, the commit
38340
+ banner, and the region-map nodes all become comic panels (notched corners,
38341
+ gold frame, halftone). A faded raven bleeds in from the right of the table
38342
+ views like the warrior in the reference mockup.
38343
+ ========================================================================== */
38344
+ .vr-bcard {
38345
+ position: relative;
38346
+ border: 1.5px solid var(--vr-border-strong) !important;
38347
+ border-radius: 4px !important;
38348
+ clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 14px 100%, 0 calc(100% - 14px));
38349
+ background-color: rgba(13, 12, 16, 0.95) !important;
38350
+ background-image: radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.4px) !important;
38351
+ background-size: 9px 9px !important;
38352
+ }
38353
+ .vr-bcard.is-current {
38354
+ border-color: var(--vr-gold) !important;
38355
+ box-shadow: 0 0 18px rgba(232, 193, 90, 0.18);
38356
+ }
38357
+ .vr-table-warning {
38358
+ position: relative;
38359
+ border: 1.5px solid var(--vr-border-strong) !important;
38360
+ border-left: 3px solid var(--vr-gold) !important;
38361
+ border-radius: 4px !important;
38362
+ clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%);
38363
+ background-color: rgba(13, 12, 16, 0.95) !important;
38364
+ background-image:
38365
+ radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.4px),
38366
+ linear-gradient(160deg, rgba(232, 193, 90, 0.06), transparent 40%) !important;
38367
+ background-size: 9px 9px, auto !important;
38368
+ }
38369
+ /* region-map + architecture nodes get the comic-panel notch + halftone */
38370
+ .vr-arch-node:not(.is-card) {
38371
+ clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));
38372
+ background-image: radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.4px) !important;
38373
+ background-size: 8px 8px !important;
38374
+ }
38375
+ /* the raven bleeds in from the right of the worktree/versions/region views */
38376
+ .vr-table-view,
38377
+ .vr-studio-architecture { position: relative; }
38378
+ .vr-table-view::after,
38379
+ .vr-studio-architecture.is-region-map::after {
38380
+ content: '';
38381
+ position: absolute;
38382
+ right: -40px;
38383
+ bottom: -20px;
38384
+ width: 460px;
38385
+ height: 460px;
38386
+ background: url('/manga/emblem.png') no-repeat right bottom / contain;
38387
+ opacity: 0.06;
38388
+ pointer-events: none;
38389
+ z-index: 0;
38390
+ }
38391
+ /* rail logo is the fierce raven, framed to its face */
38392
+ .vr-rail-raven.is-emblem {
38393
+ border-radius: 9px;
38394
+ overflow: hidden;
38395
+ border: 1px solid var(--vr-border-strong);
38396
+ background: radial-gradient(circle at 50% 40%, #1a1a1f, #0a0a0d);
38397
+ }
38398
+ .vr-rail-raven.is-emblem img {
38399
+ width: 100%;
38400
+ height: 100%;
38401
+ object-fit: cover;
38402
+ object-position: 74% 40%;
38403
+ transform: scale(1.5);
38404
+ }
38405
+
38406
+ /* ==========================================================================
38407
+ MANGA THEME PASS \u2014 dropdowns/menus, readability, world background, top bar,
38408
+ modal, chrome. One append block; wins by source order. No !important.
38409
+ ========================================================================== */
38410
+
38411
+ /* ----- Area 1: dropdowns & popup menus (kill the white selected row) ----- */
38412
+ .vr-model-picker > button {
38413
+ border: 1px solid var(--vr-border);
38414
+ border-radius: 5px;
38415
+ background-color: rgba(13, 12, 16, 0.95);
38416
+ color: var(--vr-text);
38417
+ }
38418
+ .vr-model-picker > button span { color: var(--vr-muted); }
38419
+ .vr-model-picker > button strong { color: var(--vr-gold-bright); }
38420
+ .vr-model-picker > button i { border-color: var(--vr-gold); }
38421
+ .vr-model-picker > button:hover { border-color: var(--vr-border-strong); background-color: rgba(20, 18, 12, 0.96); }
38422
+
38423
+ .vr-model-menu,
38424
+ .vr-scope-menu,
38425
+ .vr-chat-command-popover {
38426
+ border: 1px solid var(--vr-border-strong);
38427
+ border-left: 3px solid var(--vr-gold);
38428
+ border-radius: 5px;
38429
+ background-color: rgba(10, 10, 13, 0.99);
38430
+ background-image: radial-gradient(rgba(232, 193, 90, 0.045) 1px, transparent 1.4px);
38431
+ background-size: 9px 9px;
38432
+ box-shadow: 4px 6px 0 rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.5);
38433
+ color: var(--vr-text);
38434
+ overflow: hidden;
38435
+ }
38436
+ .vr-model-menu p,
38437
+ .vr-scope-menu p {
38438
+ font-family: var(--mg-display);
38439
+ text-transform: uppercase;
38440
+ letter-spacing: 0.08em;
38441
+ color: var(--vr-gold);
38442
+ }
38443
+ .vr-model-menu button,
38444
+ .vr-scope-menu button,
38445
+ .vr-chat-command-popover button { border-radius: 3px; color: rgba(244, 241, 232, 0.9); }
38446
+ .vr-model-menu button strong { color: rgba(244, 241, 232, 0.95); }
38447
+ .vr-model-menu button em { color: var(--vr-muted); font-size: 11.5px; }
38448
+ .vr-chat-command-popover button.is-primary { color: var(--vr-gold-bright); }
38449
+ .vr-chat-command-popover button.is-muted { color: var(--vr-muted); }
38450
+ .vr-model-menu button:hover,
38451
+ .vr-scope-menu button:hover,
38452
+ .vr-chat-command-popover button:hover {
38453
+ background: rgba(232, 193, 90, 0.12);
38454
+ box-shadow: inset 3px 0 0 var(--vr-gold);
38455
+ color: var(--vr-gold-bright);
38456
+ }
38457
+ /* selected row: gold-tinted ink + spine (JS marks the active row with a <b>) */
38458
+ .vr-model-menu button[aria-selected="true"],
38459
+ .vr-model-menu button.is-active,
38460
+ .vr-model-menu button:has(> b),
38461
+ .vr-scope-menu button:has(> b) {
38462
+ background: rgba(232, 193, 90, 0.16);
38463
+ box-shadow: inset 3px 0 0 var(--vr-gold);
38464
+ }
38465
+ .vr-model-menu button[aria-selected="true"] strong,
38466
+ .vr-model-menu button:has(> b) strong { color: var(--vr-gold-bright); }
38467
+ .vr-model-menu button b,
38468
+ .vr-scope-menu button b { border-color: var(--vr-gold-bright); }
38469
+ /* themed menu scrollbars */
38470
+ .vr-model-menu::-webkit-scrollbar,
38471
+ .vr-scope-menu::-webkit-scrollbar,
38472
+ .vr-chat-command-popover::-webkit-scrollbar { width: 8px; }
38473
+ .vr-model-menu::-webkit-scrollbar-thumb,
38474
+ .vr-scope-menu::-webkit-scrollbar-thumb,
38475
+ .vr-chat-command-popover::-webkit-scrollbar-thumb { background: var(--vr-border-strong); border: 2px solid rgba(13, 12, 16, 0.97); }
38476
+
38477
+ .vr-chat-inline-actions > button,
38478
+ .vr-chat-command-trigger > button {
38479
+ border: 1px solid var(--vr-border);
38480
+ border-radius: 5px;
38481
+ background: rgba(13, 12, 16, 0.7);
38482
+ color: rgba(244, 241, 232, 0.82);
38483
+ }
38484
+ .vr-chat-inline-actions > button:hover,
38485
+ .vr-chat-command-trigger > button:hover {
38486
+ border-color: var(--vr-border-strong);
38487
+ background: rgba(20, 18, 12, 0.85);
38488
+ color: var(--vr-gold-bright);
38489
+ }
38490
+
38491
+ /* ----- Area 2: readability ----- */
38492
+ .vr-chat-command-bar .vr-chat-input { color: var(--vr-text); }
38493
+ .vr-chat-command-bar .vr-chat-input::placeholder { color: var(--vr-muted); }
38494
+ .vr-chat-inline-actions button.is-primary {
38495
+ background: var(--vr-gold);
38496
+ color: #241207;
38497
+ border-color: var(--vr-gold-bright);
38498
+ font-weight: 720;
38499
+ }
38500
+ .vr-chat-inline-actions button.is-muted { opacity: 1; color: var(--vr-muted); font-weight: 650; }
38501
+ .vr-cli-os-fallback { color: var(--vr-muted); text-decoration-color: var(--vr-border-strong); }
38502
+ .vr-cli-os-fallback:hover { color: var(--vr-gold-bright); }
38503
+
38504
+ /* ----- Area 4b: primary CTAs -> gold (after Area 1 so filled-gold wins) ----- */
38505
+ .vr-agent-followups button.is-primary,
38506
+ .vr-agent-inline-action.is-primary,
38507
+ .vr-chat-command-popover button.is-primary,
38508
+ .vr-cli-action-button.is-primary,
38509
+ .vr-studio-architecture > footer button.is-primary,
38510
+ .vr-arch-drawer footer button.is-primary,
38511
+ .vr-wt-actions button.is-primary,
38512
+ .vr-approve-all,
38513
+ .vr-new-chat-button,
38514
+ .vr-provider-ask-agent {
38515
+ background: var(--vr-gold);
38516
+ border-color: var(--vr-gold-bright);
38517
+ color: #0a0b0e;
38518
+ font-family: var(--mg-display);
38519
+ text-transform: uppercase;
38520
+ letter-spacing: 0.04em;
38521
+ }
38522
+ .vr-agent-followups button.is-primary:hover,
38523
+ .vr-agent-inline-action.is-primary:hover,
38524
+ .vr-chat-command-popover button.is-primary:hover,
38525
+ .vr-approve-all:hover,
38526
+ .vr-new-chat-button:hover,
38527
+ .vr-provider-ask-agent:hover { background: var(--vr-gold-bright); border-color: var(--vr-gold-bright); color: #0a0b0e; }
38528
+ .vr-composer-controls .vr-chat-send:not(.is-stop),
38529
+ .vr-chat-command-bar > .vr-chat-composer > .vr-chat-send:not(.is-stop) {
38530
+ background: var(--vr-gold);
38531
+ border-color: var(--vr-gold-bright);
38532
+ color: #0a0b0e;
38533
+ }
38534
+ .vr-composer-controls .vr-chat-send:not(.is-stop):hover,
38535
+ .vr-chat-command-bar > .vr-chat-composer > .vr-chat-send:not(.is-stop):hover { background: var(--vr-gold-bright); border-color: var(--vr-gold-bright); }
38536
+
38537
+ /* ----- Area 3: world background (inked drafting table, lit from above) ----- */
38538
+ body {
38539
+ background-color: #08080a;
38540
+ background-image:
38541
+ radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.6px),
38542
+ repeating-linear-gradient(52deg, rgba(232, 193, 90, 0.045) 0 1px, transparent 1px 8px),
38543
+ repeating-linear-gradient(-52deg, rgba(232, 193, 90, 0.04) 0 1px, transparent 1px 9px),
38544
+ radial-gradient(ellipse 70% 42% at 50% -8%, rgba(232, 193, 90, 0.07), transparent 60%),
38545
+ radial-gradient(120% 120% at 50% 40%, transparent 58%, rgba(0, 0, 0, 0.55) 100%),
38546
+ linear-gradient(180deg, #101015 0%, #0b0b0e 46%, #08080a 100%);
38547
+ background-size: 9px 9px, 900px 900px, 900px 900px, 100% 100%, 100% 100%, 100% 100%;
38548
+ background-position: 0 0, top right, bottom left, center top, center, center;
38549
+ background-repeat: repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
38550
+ background-attachment: scroll;
38551
+ }
38552
+ .vr-composed-shell {
38553
+ background:
38554
+ radial-gradient(ellipse 60% 30% at 50% -4%, rgba(232, 193, 90, 0.06), transparent 60%),
38555
+ radial-gradient(circle at 45% 9%, rgba(232, 193, 90, 0.035), transparent 30%),
38556
+ rgba(11, 12, 15, 0.965);
38557
+ box-shadow:
38558
+ inset 0 1px 0 rgba(255, 233, 173, 0.06),
38559
+ inset 0 0 0 1px rgba(232, 193, 90, 0.05),
38560
+ 0 24px 60px rgba(0, 0, 0, 0.55);
38561
+ }
38562
+ .vr-studio-topbar {
38563
+ background:
38564
+ linear-gradient(180deg, rgba(232, 193, 90, 0.05), transparent 70%),
38565
+ rgba(13, 12, 16, 0.62);
38566
+ }
38567
+
38568
+ /* ----- Area 4a: top tab bar (CHAT/ARCHITECTURE/TERMINAL/DIFF) ----- */
38569
+ .vr-studio-tabs {
38570
+ border: 1px solid var(--vr-border);
38571
+ border-radius: 4px;
38572
+ clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 9px, 100% 100%, 9px 100%, 0 calc(100% - 9px));
38573
+ background: rgba(13, 12, 16, 0.85);
38574
+ overflow: hidden;
38575
+ }
38576
+ .vr-studio-tabs button {
38577
+ font-family: var(--mg-display);
38578
+ text-transform: uppercase;
38579
+ letter-spacing: 0.05em;
38580
+ color: rgba(244, 241, 232, 0.7);
38581
+ }
38582
+ .vr-studio-tabs button:hover { color: var(--vr-gold-bright); background: rgba(232, 193, 90, 0.08); }
38583
+ .vr-studio-tabs button.is-active {
38584
+ background: var(--vr-gold);
38585
+ color: #0a0b0e;
38586
+ border-radius: 3px;
38587
+ box-shadow: inset 0 0 0 1px rgba(255, 233, 173, 0.5);
38588
+ }
38589
+
38590
+ /* ----- Area 4c: modal (ink+gold, not navy/cyan) ----- */
38591
+ .vr-modal-card {
38592
+ border-radius: 4px;
38593
+ clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 20px 100%, 0 calc(100% - 20px));
38594
+ background:
38595
+ radial-gradient(ellipse 70% 40% at 50% -4%, rgba(232, 193, 90, 0.06), transparent 60%),
38596
+ radial-gradient(rgba(232, 193, 90, 0.04) 1px, transparent 1.4px),
38597
+ linear-gradient(180deg, rgba(16, 15, 12, 0.99), rgba(13, 12, 16, 0.995));
38598
+ background-size: 100% 100%, 11px 11px, 100%;
38599
+ }
38600
+ .vr-modal-card::before { background: linear-gradient(135deg, rgba(232, 193, 90, 0.08), transparent 28%); }
38601
+ .vr-modal-card::after { background: linear-gradient(90deg, transparent, rgba(232, 193, 90, 0.05), rgba(232, 193, 90, 0.2), transparent); opacity: 0.3; }
38602
+ .vr-modal-backdrop { background: radial-gradient(ellipse 60% 40% at 50% 8%, rgba(232, 193, 90, 0.07), transparent 45%), rgba(4, 5, 8, 0.8); }
38603
+ .vr-modal-card::-webkit-scrollbar-thumb { border: 3px solid rgba(13, 12, 16, 0.92); background: rgba(232, 193, 90, 0.4); }
38604
+
38605
+ /* ----- Area 4d: chrome buttons, terminal input, connect rows ----- */
38606
+ .vr-studio-add-chat,
38607
+ .vr-studio-actions > button:not(.vr-glow-button) {
38608
+ border: 1px solid var(--vr-border);
38609
+ border-radius: 5px;
38610
+ background: rgba(13, 12, 16, 0.7);
38611
+ color: rgba(244, 241, 232, 0.88);
38612
+ }
38613
+ .vr-studio-add-chat:hover,
38614
+ .vr-studio-actions > button:not(.vr-glow-button):hover { border-color: var(--vr-border-strong); background: rgba(232, 193, 90, 0.08); color: var(--vr-gold-bright); }
38615
+ .vr-terminal-compose textarea {
38616
+ border: 1px solid var(--vr-border);
38617
+ border-radius: 4px;
38618
+ clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 8px 100%, 0 calc(100% - 8px));
38619
+ background: rgba(13, 12, 16, 0.72);
38620
+ color: rgba(244, 241, 232, 0.92);
38621
+ font-family: var(--vr-hud-mono);
38622
+ }
38623
+ .vr-terminal-compose textarea::placeholder { color: var(--vr-muted); }
38624
+ .vr-terminal-compose textarea:focus { border-color: var(--vr-gold); box-shadow: 0 0 18px rgba(232, 193, 90, 0.14); }
38625
+ .vr-env-connect-option {
38626
+ border: 1px solid var(--vr-border);
38627
+ border-radius: 4px;
38628
+ clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
38629
+ background: rgba(13, 12, 16, 0.6);
38630
+ color: rgba(244, 241, 232, 0.9);
38631
+ }
38632
+ .vr-env-connect-option:hover { border-color: var(--vr-border-strong); background: rgba(232, 193, 90, 0.07); }
38633
+ .vr-env-connect-option b {
38634
+ border-radius: 3px;
38635
+ background: rgba(232, 193, 90, 0.1);
38636
+ color: var(--vr-gold-bright);
38637
+ font-family: var(--vr-hud-mono);
38638
+ text-transform: uppercase;
38639
+ letter-spacing: 0.04em;
38640
+ }
38641
+
38642
+ /* ----- Area 4e: focus ring ----- */
38643
+ button:focus-visible,
38644
+ a:focus-visible {
38645
+ outline: 2px solid var(--vr-gold);
38646
+ outline-offset: 2px;
38647
+ box-shadow: 0 0 0 4px rgba(232, 193, 90, 0.16);
38648
+ }
38649
+
38650
+ /* ----- Area 5: raven watermarks fade away from text (readability) ----- */
38651
+ .vr-chat-agent-response::before {
38652
+ opacity: 0.05;
38653
+ -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 62%);
38654
+ mask-image: linear-gradient(90deg, transparent 0%, #000 62%);
38655
+ }
38656
+ .vr-table-view > *,
38657
+ .vr-studio-architecture.is-region-map > * { position: relative; z-index: 1; }
38658
+ .vr-table-view::after,
38659
+ .vr-studio-architecture.is-region-map::after {
38660
+ opacity: 0.05;
38661
+ -webkit-mask-image: radial-gradient(circle at right bottom, #000 28%, transparent 72%);
38662
+ mask-image: radial-gradient(circle at right bottom, #000 28%, transparent 72%);
38663
+ }
38664
+
38665
+ /* ----- placed cards get an always-visible X to clear them off the table.
38666
+ NB: has-art cards have overflow:hidden, so the X must sit INSIDE the card
38667
+ corner (top-left, away from the grade coin) or it gets clipped. ----- */
38668
+ .vr-tcard.is-placed .vr-tcard-unplace,
38669
+ .vr-vcard.is-mini.is-placed .vr-tcard-unplace {
38670
+ opacity: 1;
38671
+ width: 21px;
38672
+ height: 21px;
38673
+ top: 5px;
38674
+ left: 5px;
38675
+ right: auto;
38676
+ z-index: 6;
38677
+ background: rgba(6, 6, 8, 0.92);
38678
+ border: 1.5px solid var(--vr-gold);
38679
+ color: var(--vr-gold-bright);
38680
+ font-size: 11px;
38681
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.7);
38682
+ transition: transform 0.1s ease, background 0.1s ease, color 0.1s ease;
38683
+ }
38684
+ .vr-tcard.is-placed:hover .vr-tcard-unplace,
38685
+ .vr-vcard.is-mini.is-placed:hover .vr-tcard-unplace { opacity: 1; }
38686
+ .vr-tcard.is-placed .vr-tcard-unplace:hover,
38687
+ .vr-vcard.is-mini.is-placed .vr-tcard-unplace:hover {
38688
+ background: var(--vr-red);
38689
+ border-color: var(--vr-red);
38690
+ color: #fff;
38691
+ transform: scale(1.12);
38692
+ }
38693
+
38694
+
38695
+ /* ==========================================================================
38696
+ MANGA B&W CONTRAST PASS \u2014 appended AFTER "MANGA THEME PASS" so it wins by
38697
+ source order. Direction: blacker blacks, whiter/cream ink, crisp near-white
38698
+ hairlines, screentone dots for grey; gold pulled back to an ACCENT only
38699
+ (spines, selected rails, small marks). Semantic red kept for errors.
38700
+ De-duplicated + conflict-resolved (higher contrast wins). No !important
38701
+ except where an existing same-file !important rule must be overridden.
38702
+ ========================================================================== */
38703
+
38704
+ /* ---------- (1) B&W CONTRAST BASE ---------- */
38705
+
38706
+ /* Retone root tokens: fixes the majority of muted labels + gold-alpha edges
38707
+ in one place. --vr-muted lifted off the dim tan; borders become near-white
38708
+ ink hairlines; gold moves to an explicit accent hook. */
38709
+ :root {
38710
+ --vr-bg: #050506;
38711
+ --vr-bg-2: #0e0e11;
38712
+ --vr-text: #f7f4ec;
38713
+ --vr-muted: #c4bcaf;
38714
+ --vr-border: rgba(238, 232, 220, 0.16);
38715
+ --vr-border-strong: rgba(238, 232, 220, 0.34);
38716
+ --vr-panel: rgba(9, 9, 11, 0.92);
38717
+ --vr-panel-2: rgba(14, 14, 17, 0.9);
38718
+ --vr-hud-surface: rgba(6, 6, 8, 0.94);
38719
+ --vr-hud-line: rgba(238, 232, 220, 0.14);
38720
+ --vr-accent-line: rgba(232, 193, 90, 0.55);
38721
+ }
38722
+
38723
+ /* World background: drop the gold cross-hatch haze for black paper + faint
38724
+ neutral screentone + vignette. */
38725
+ body {
38726
+ background-color: #050506;
38727
+ background-image:
38728
+ radial-gradient(rgba(244, 241, 232, 0.028) 1px, transparent 1.5px),
38729
+ radial-gradient(ellipse 68% 40% at 50% -8%, rgba(255, 255, 255, 0.035), transparent 62%),
38730
+ radial-gradient(130% 120% at 50% 42%, transparent 56%, rgba(0, 0, 0, 0.72) 100%),
38731
+ linear-gradient(180deg, #0c0c0f 0%, #08080a 48%, #050506 100%);
38732
+ background-size: 8px 8px, 100% 100%, 100% 100%, 100% 100%;
38733
+ background-position: 0 0, center top, center, center;
38734
+ background-repeat: repeat, no-repeat, no-repeat, no-repeat;
38735
+ }
38736
+
38737
+ /* Composed shell: near-flat true-black with one white top hairline. */
38738
+ .vr-composed-shell {
38739
+ background:
38740
+ radial-gradient(ellipse 62% 26% at 50% -3%, rgba(255, 255, 255, 0.04), transparent 60%),
38741
+ rgba(7, 7, 9, 0.975);
38742
+ box-shadow:
38743
+ inset 0 1px 0 rgba(255, 255, 255, 0.07),
38744
+ inset 0 0 0 1px rgba(238, 232, 220, 0.05),
38745
+ 0 24px 60px rgba(0, 0, 0, 0.62);
38746
+ }
38747
+
38748
+ /* Studio topbar: neutral top-light + single thin gold underline accent. */
38749
+ .vr-studio-topbar {
38750
+ background:
38751
+ linear-gradient(180deg, rgba(255, 255, 255, 0.045), transparent 70%),
38752
+ rgba(6, 6, 9, 0.72);
38753
+ border-bottom: 1px solid var(--vr-border);
38754
+ box-shadow: inset 0 -1px 0 var(--vr-accent-line);
38755
+ }
38756
+
38757
+ /* Persistent top-bar readout (was #64748b cold slate, ~3.4:1). */
38758
+ .vr-topbar-spacer { color: var(--vr-muted); font-weight: 780; }
38759
+
38760
+ /* Studio tabs: active tab reads as inked-white paper with a gold underline
38761
+ mark, not a gold slab; idle-tab contrast raised. */
38762
+ .vr-studio-tabs button { color: rgba(244, 241, 232, 0.82); }
38763
+ .vr-studio-tabs button.is-active {
38764
+ background: rgba(244, 241, 232, 0.1);
38765
+ color: #f7f4ec;
38766
+ box-shadow: inset 0 -2px 0 var(--vr-gold), inset 0 0 0 1px rgba(238, 232, 220, 0.14);
38767
+ }
38768
+ .vr-studio-tabs button:hover { color: #ffe9ad; background: rgba(255, 255, 255, 0.05); }
38769
+
38770
+ /* Dropdown / popup menus: clean black sheet (kills the gold halftone),
38771
+ gold only on the left spine + hover/selected rail; item + label contrast up. */
38772
+ .vr-model-menu,
38773
+ .vr-scope-menu,
38774
+ .vr-chat-command-popover {
38775
+ background-color: rgba(5, 5, 7, 0.99);
38776
+ background-image: none;
38777
+ border-color: var(--vr-border-strong);
38778
+ border-left: 3px solid var(--vr-gold);
38779
+ }
38780
+ .vr-model-menu button,
38781
+ .vr-scope-menu button,
38782
+ .vr-chat-command-popover button { color: #f0ece2; }
38783
+ .vr-model-menu button em { color: #c4bcae; }
38784
+ .vr-model-menu button:hover,
38785
+ .vr-scope-menu button:hover,
38786
+ .vr-chat-command-popover button:hover {
38787
+ background: rgba(255, 255, 255, 0.06);
38788
+ box-shadow: inset 3px 0 0 var(--vr-gold);
38789
+ color: #ffe9ad;
38790
+ }
38791
+
38792
+ /* HUD strip (telemetry line): raise the faded cool-grey meta to readable ink. */
38793
+ .vr-hud-strip { color: #c9c1b4; border-bottom-color: rgba(238, 232, 220, 0.12); }
38794
+ .vr-hud-strip strong { color: #f7f4ec; }
38795
+ .vr-hud-strip em { color: #c9c1b4; }
38796
+ .vr-hud-strip b { color: var(--vr-muted); font-weight: 600; } /* merged: was #b0a89b / rgba(156,163,175,.72) */
38797
+
38798
+ /* HUD card chassis: white-ruled black panel; the ONE spot of color is the
38799
+ left rail (semantic status). */
38800
+ .vr-hud-card {
38801
+ background: rgba(6, 6, 8, 0.94);
38802
+ border-color: rgba(238, 232, 220, 0.16);
38803
+ border-left-color: rgba(238, 232, 220, 0.45);
38804
+ }
38805
+ .vr-hud-card.is-nominal { border-left-color: var(--vr-green); }
38806
+ .vr-hud-card.is-blocked { border-left-color: var(--vr-red); }
38807
+ .vr-hud-card.is-working { border-left-color: var(--vr-orange); }
38808
+ .vr-hud-card.is-mission { border-left-color: var(--vr-gold); }
38809
+
38810
+ /* HUD gate row + severity: lift the primary line to full-opacity ink,
38811
+ metadata to solid grey. */
38812
+ .vr-hud-gate { background: rgba(255, 255, 255, 0.02); border-color: rgba(238, 232, 220, 0.14); }
38813
+ .vr-hud-gate header { border-bottom-color: rgba(238, 232, 220, 0.12); }
38814
+ .vr-hud-gate-row { border-bottom-color: rgba(238, 232, 220, 0.09); }
38815
+ .vr-hud-gate-row em { color: #f3efe6; }
38816
+ .vr-hud-gate-row em small { color: #c4bcae; }
38817
+ .vr-hud-gate-row b { color: #b6ae9f; }
38818
+ .vr-hud-sev.is-info { color: #b6ae9f; }
38819
+ .vr-hud-gate-foot {
38820
+ border-top-color: rgba(238, 232, 220, 0.12);
38821
+ background: rgba(255, 255, 255, 0.025);
38822
+ color: #f4f1e8;
38823
+ }
38824
+ .vr-hud-gate-foot b { color: #bcb4a6; }
38825
+ .vr-hud-gate-foot:hover { background: rgba(255, 255, 255, 0.05); }
38826
+ .vr-hud-raw { color: #bcb4a6; }
38827
+ .vr-agent-raw-sub { color: #c4bcae; }
38828
+
38829
+ /* ---------- (2) VERSIONS / WORKTREE / SHUFFLE ---------- */
38830
+
38831
+ /* Table view container + header: solid ink base so cream text pops; header
38832
+ is cream Anton with a hairline rule, gold reserved for the spine accents. */
38833
+ .vr-table-view { background: #0b0b0d !important; }
38834
+ .vr-table-view-head { border-bottom: 1.5px solid var(--vr-border-strong) !important; }
38835
+ .vr-table-view-head strong { color: var(--vr-text) !important; -webkit-text-stroke: 0.4px rgba(0, 0, 0, 0.35); }
38836
+ .vr-table-view-head span { color: var(--vr-muted) !important; }
38837
+
38838
+ /* Big version card: ink plate + white hairline + comic notch (large panel \u2014
38839
+ notch is safe here); gold only as border tint on current. NOTE: base rules
38840
+ at ~13450-13492 use !important, so these must too. */
38841
+ .vr-vcard {
38842
+ border: 1.5px solid var(--vr-border-strong) !important;
38843
+ border-radius: 4px !important;
38844
+ background: rgba(10, 10, 12, 0.96) !important;
38845
+ box-shadow: 0 10px 24px rgba(0, 0, 0, 0.55) !important;
38846
+ clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px));
38847
+ }
38848
+ .vr-vcard::before {
38849
+ inset: 4px !important;
38850
+ border-radius: 3px !important;
38851
+ background-color: rgba(11, 11, 13, 0.9) !important;
38852
+ background-image: radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.4px) !important;
38853
+ background-size: 9px 9px !important;
38854
+ }
38855
+ .vr-vcard:not(.is-current) { filter: none !important; opacity: 0.9; }
38856
+ .vr-vcard.is-current {
38857
+ border-color: var(--vr-gold) !important;
38858
+ border-left: 3px solid var(--vr-gold) !important; /* thin gold spine = "chosen" */
38859
+ box-shadow: 0 10px 24px rgba(0, 0, 0, 0.55), 0 0 16px rgba(232, 193, 90, 0.16) !important;
38860
+ }
38861
+ .vr-vcard-top b,
38862
+ .vr-vpile-card b { color: var(--vr-text) !important; font-family: var(--vr-display) !important; letter-spacing: 0.5px; }
38863
+ /* merged .vr-vcard-meta: prefer --vr-muted (brightest) + the larger 8.5px */
38864
+ .vr-vcard-meta { color: var(--vr-muted) !important; font-size: 8.5px !important; letter-spacing: 0.8px; opacity: 0.95; }
38865
+ .vr-vcard-branch { color: #cfc6b6 !important; font-size: 8.5px !important; }
38866
+
38867
+ /* Version pile (bottom-left floating pile): thin hairline + gold spine, not a
38868
+ thick foil frame. */
38869
+ .vr-vpile-card {
38870
+ border: 1px solid var(--vr-border-strong) !important;
38871
+ border-left: 3px solid var(--vr-gold) !important;
38872
+ background: rgba(11, 11, 13, 0.96) !important;
38873
+ box-shadow: 0 6px 14px rgba(0, 0, 0, 0.5) !important;
38874
+ }
38875
+ .vr-vpile-card.is-top {
38876
+ border-color: var(--vr-gold) !important;
38877
+ box-shadow: 0 6px 14px rgba(0, 0, 0, 0.5), 0 0 12px rgba(232, 193, 90, 0.15) !important;
38878
+ }
38879
+ .vr-vpile-card:not(.is-top) { opacity: 0.8; }
38880
+ .vr-vpile-card em { color: #cfc6b6 !important; }
38881
+ .vr-version-pile-label { color: var(--vr-muted) !important; letter-spacing: 1.8px; }
38882
+
38883
+ /* SHUFFLE button: crisp ink pill + gold hairline; hover = thin gold-tint, not
38884
+ a solid gold slab. */
38885
+ .vr-pile-shuffle {
38886
+ border: 1.5px solid var(--vr-border-strong) !important;
38887
+ border-radius: 4px !important;
38888
+ background: rgba(11, 11, 13, 0.95) !important;
38889
+ color: var(--vr-text) !important;
38890
+ font-family: var(--vr-hud-mono) !important;
38891
+ font-size: 8.5px !important;
38892
+ font-weight: 700;
38893
+ letter-spacing: 1.4px;
38894
+ padding: 4px 10px !important;
38895
+ }
38896
+ .vr-pile-shuffle:hover {
38897
+ background: rgba(232, 193, 90, 0.12) !important;
38898
+ border-color: var(--vr-gold) !important;
38899
+ color: var(--vr-gold-bright) !important;
38900
+ }
38901
+
38902
+ /* Worktree branch card (bcard): gold spine + readable title/sub. */
38903
+ .vr-bcard { border-left: 3px solid var(--vr-gold) !important; }
38904
+ .vr-bcard b { color: var(--vr-text) !important; font-size: 13.5px !important; }
38905
+ .vr-bcard em { color: #cfc6b6 !important; font-size: 9px !important; }
38906
+ .vr-bcard-actions button {
38907
+ border: 1.5px solid var(--vr-border-strong) !important;
38908
+ color: var(--vr-text) !important;
38909
+ background: rgba(11, 11, 13, 0.6) !important;
38910
+ letter-spacing: 1px;
38911
+ }
38912
+ .vr-bcard-actions button:hover {
38913
+ background: rgba(232, 193, 90, 0.12) !important;
38914
+ border-color: var(--vr-gold) !important;
38915
+ color: var(--vr-gold-bright) !important;
38916
+ }
38917
+
38918
+ /* Worktree uncommitted-changes warning: readable body, orange COMMIT button. */
38919
+ .vr-table-warning { color: var(--vr-text) !important; font-size: 13px !important; }
38920
+ .vr-table-warning button { color: var(--vr-orange) !important; font-size: 9.5px !important; letter-spacing: 1.2px; }
38921
+ .vr-table-warning button:hover { background: var(--vr-orange) !important; color: #0b0b0d !important; }
38922
+
38923
+ /* Empty-state + add-version footer button. */
38924
+ .vr-table-empty { color: #cfc6b6 !important; font-size: 11.5px !important; }
38925
+ .vr-table-foot button { color: var(--vr-text) !important; border-color: var(--vr-border-strong) !important; }
38926
+ .vr-table-foot button:hover { background: rgba(232, 193, 90, 0.1) !important; color: var(--vr-gold-bright) !important; }
38927
+
38928
+ /* ---------- (3) CLEAR STAMP FIX (region-map node badge clipped) ---------- */
38929
+ /* The comic-notch clip-path on .vr-arch-node:not(.is-card) clips its
38930
+ corner-mounted .vr-arch-node-badge, cutting "CLEAR" -> "CLEA.". Per the
38931
+ manga rule (never notch anything whose badge reaches a corner), drop the
38932
+ notch on these nodes and keep the ink/halftone look via a hairline. */
38933
+ .vr-arch-node:not(.is-card) {
38934
+ clip-path: none;
38935
+ overflow: visible;
38936
+ border: 1.5px solid var(--vr-border-strong);
38937
+ background-image: radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.4px);
38938
+ background-size: 8px 8px;
38939
+ }
38940
+ .vr-studio-architecture.is-region-map .vr-arch-node:not(.is-card) .vr-arch-node-badge.vr-stamp.is-mini {
38941
+ overflow: visible;
38942
+ z-index: 3;
38943
+ background: var(--vr-bg);
38944
+ }
38945
+
38946
+ /* ---------- (4) WELCOME MODAL QUICKSTART CARDS (flip bleed-through) ---------- */
38947
+ /* On a DONE card (is-flipped, e.g. SCAN "SETUP \xB7 WON") the back-face mascot +
38948
+ FLIP label bled through because both faces trusted only backface-visibility.
38949
+ Hide the inactive face explicitly (opacity+visibility) and give the flip a
38950
+ fixed height so the four cards stay aligned. Make the back opaque. */
38951
+ .vr-qs-card.is-hero { position: relative; }
38952
+ .vr-qs-card.is-hero [data-flip-card],
38953
+ .vr-qs-card.is-hero .vr-flip-inner { height: 100%; }
38954
+ .vr-qs-card.is-hero .vr-flip-inner { min-height: 318px; }
38955
+ .vr-qs-card .vr-flip-face,
38956
+ .vr-qs-card .vr-flip-back {
38957
+ position: absolute;
38958
+ inset: 0;
38959
+ height: 100%;
38960
+ transition: opacity 200ms ease, visibility 200ms ease;
38961
+ }
38962
+ .vr-qs-card:not(.is-flipped) .vr-flip-face { opacity: 0; visibility: hidden; }
38963
+ .vr-qs-card.is-flipped .vr-flip-face { opacity: 1; visibility: visible; }
38964
+ .vr-qs-card.is-flipped .vr-flip-back { opacity: 0; visibility: hidden; }
38965
+ .vr-qs-card:not(.is-flipped) .vr-flip-back { opacity: 1; visibility: visible; }
38966
+ .vr-qs-card .vr-qs-back { background-color: #0e0e12; }
38967
+ .vr-qs-card.is-hero .vr-qs-card-inner { position: relative; z-index: 1; }
38968
+
38969
+ /* ---------- (5) READABILITY (retone off-palette / low-contrast text) ---------- */
38970
+
38971
+ /* Agent answer body / structured sections: warm near-white cream on the
38972
+ highest-traffic reading surface (was cool blue-greys). */
38973
+ .vr-agent-answer-body,
38974
+ .vr-agent-answer-text { color: #f2efe6; }
38975
+ .vr-agent-answer-body p { color: #eae6db; }
38976
+ .vr-agent-list li,
38977
+ .vr-chat-message p { color: var(--vr-text); } /* merged: cream token wins over #eae6db/#e7e7e9 */
38978
+ .vr-agent-answer-body .vr-agent-empty { color: #bcb4a6; }
38979
+
38980
+ /* Inline code / pill chips: near-white on true-black chip + white hairline,
38981
+ gold only as the faint left tick (was gold body text). */
38982
+ .vr-agent-answer-body code {
38983
+ background: rgba(4, 4, 6, 0.85);
38984
+ border-color: rgba(238, 232, 220, 0.16);
38985
+ border-left: 2px solid var(--vr-gold);
38986
+ color: #f4f1e8;
38987
+ }
38988
+ .vr-chat-message b {
38989
+ border-color: rgba(238, 232, 220, 0.24);
38990
+ background: rgba(255, 255, 255, 0.05);
38991
+ color: #f4f1e8;
38992
+ }
38993
+
38994
+ /* Chat sub-caption / notes / inline em: readable neutral cream-grey. */
38995
+ .vr-chat-message em,
38996
+ .vr-chat-copy { color: #c6bfb2; }
38997
+ .vr-chat-note { color: #cfc8ba; }
38998
+
38999
+ /* Chat bubbles (agent/system telemetry): true-black glass + white hairline +
39000
+ warm-white text (was #1d1e22 grey slab w/ blue-grey borders). */
39001
+ .vr-chat-bubble {
39002
+ background: rgba(7, 7, 9, 0.92);
39003
+ border-color: rgba(238, 232, 220, 0.16);
39004
+ }
39005
+ .vr-chat-bubble.is-system { background: rgba(4, 4, 6, 0.9); border-color: rgba(238, 232, 220, 0.14); }
39006
+ .vr-chat-bubble span { color: #e9e2d5; }
39007
+ .vr-chat-bubble p,
39008
+ .vr-chat-bubble ul { color: #f5f1e8; }
39009
+
39010
+ /* Terminal output: warm near-white body color (was cold #cbd5e1). */
39011
+ .vr-terminal-line { color: #ece7db; }
39012
+
39013
+ /* Region-map / architecture captions + hints (cold slate -> warm muted). */
39014
+ .vr-arch-zone { color: rgba(232, 214, 176, 0.82); font-weight: 800; }
39015
+ .vr-arch-hint { color: var(--vr-muted); }
39016
+ .vr-inspector-body .vr-studio-architecture.is-inspector-map .vr-arch-zone { color: rgba(244, 241, 232, 0.86); }
39017
+
39018
+ /* Card / hero TYPE eyebrows (were 6-7.5px at 0.5-0.55 alpha, illegible). */
39019
+ .vr-tcard-type { color: rgba(244, 241, 232, 0.9); font-size: 8px; letter-spacing: 1px; }
39020
+ .vr-qs-type {
39021
+ color: rgba(244, 241, 232, 0.9);
39022
+ font-size: 9px;
39023
+ position: relative; /* merged w/ Bug-2 stacking so SETUP\xB7WON sits above art */
39024
+ z-index: 2;
39025
+ margin-top: 2px;
39026
+ white-space: nowrap;
39027
+ }
39028
+
39029
+ /* Misc muted secondary lines / captions (cold slate -> warm muted). */
39030
+ .vr-context-chip-label .muted { color: var(--vr-muted); font-weight: 550; }
39031
+ .vr-recent-chat em,
39032
+ .vr-recent-chat small { color: var(--vr-muted); }
39033
+ .vr-provider-drop-zone { color: var(--vr-muted); }
39034
+ .vr-chat-agent-task header em { color: var(--vr-muted); }
39035
+ .vr-provider-mcp-panel em { color: var(--vr-muted); }
39036
+
39037
+ /* Icon-only close/secondary button: readable at rest, gold on hover. */
39038
+ .vr-context-chip-remove { color: var(--vr-muted); }
39039
+ .vr-context-chip-remove:hover { color: var(--vr-gold-bright); }
39040
+
39041
+ /* ==========================================================================
39042
+ LIVE TABLE STATS strip \u2014 real repo/provider metrics on the table (the
39043
+ "Active Users" KPI idea, sourced from real data). Manga stat cards: ink
39044
+ plate, white hairline, gold top-tick accent, big Anton number, mono label.
39045
+ ========================================================================== */
39046
+ .vr-stat-strip {
39047
+ display: grid;
39048
+ grid-template-columns: repeat(6, minmax(0, 1fr));
39049
+ gap: 10px;
39050
+ margin: 0 0 16px;
39051
+ }
39052
+ @media (max-width: 1080px) { .vr-stat-strip { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
39053
+ .vr-stat-card {
39054
+ position: relative;
39055
+ padding: 11px 13px 10px;
39056
+ border: 1px solid var(--vr-border-strong);
39057
+ border-top: 2px solid rgba(238, 232, 220, 0.28);
39058
+ border-radius: 4px;
39059
+ background:
39060
+ radial-gradient(rgba(232, 193, 90, 0.045) 1px, transparent 1.4px),
39061
+ rgba(9, 9, 11, 0.94);
39062
+ background-size: 9px 9px, auto;
39063
+ overflow: hidden;
39064
+ }
39065
+ .vr-stat-card::after {
39066
+ content: '';
39067
+ position: absolute;
39068
+ left: 0; top: 0;
39069
+ width: 26px; height: 2px;
39070
+ background: var(--vr-gold);
39071
+ }
39072
+ .vr-stat-card b {
39073
+ display: block;
39074
+ font-family: var(--mg-display);
39075
+ font-weight: 400;
39076
+ font-size: 27px;
39077
+ line-height: 1;
39078
+ letter-spacing: 0.01em;
39079
+ color: var(--vr-text);
39080
+ }
39081
+ .vr-stat-card.is-word b { font-size: 18px; padding: 4px 0 3px; }
39082
+ .vr-stat-card span {
39083
+ display: block;
39084
+ margin-top: 5px;
39085
+ font-family: var(--vr-hud-mono);
39086
+ font-size: 8.5px;
39087
+ font-weight: 650;
39088
+ letter-spacing: 0.14em;
39089
+ text-transform: uppercase;
39090
+ color: var(--vr-muted);
39091
+ }
39092
+ .vr-stat-card.is-gold b { color: var(--vr-gold-bright); }
39093
+ .vr-stat-card.is-green b { color: var(--vr-green); }
39094
+ .vr-stat-card.is-orange b { color: var(--vr-orange); }
39095
+ .vr-stat-card.is-muted b { color: rgba(244, 241, 232, 0.6); }
39096
+ .vr-stat-card.is-green::after { background: var(--vr-green); }
39097
+ .vr-stat-card.is-orange::after { background: var(--vr-orange); }
39098
+
39099
+ /* ==========================================================================
39100
+ FINAL FIXES \u2014 hand spread + hover, worktree tag clip, placed-card readability
39101
+ ========================================================================== */
39102
+ /* Hand: cards stick together more (was -34 = too spread) so the fan reads as a
39103
+ held hand, with a clean hover lift on a high z so it never hides behind a
39104
+ neighbour. */
39105
+ .vr-hand-fan .vr-tcard { margin-left: -46px; }
39106
+ .vr-hand-fan .vr-tcard:first-child { margin-left: 0; }
39107
+ /* Stable hover: lift STRAIGHT up with almost no scale. A big scale grows the
39108
+ card footprint and flickers against the overlapping neighbours (the "buggy"
39109
+ hover). Keep the same width, just rise + straighten; smooth + GPU-hinted. */
39110
+ .vr-hand-fan .vr-tcard {
39111
+ transition: transform 130ms ease, box-shadow 130ms ease;
39112
+ will-change: transform;
39113
+ }
39114
+ .vr-hand-fan .vr-tcard:hover {
39115
+ transform: rotate(0deg) translateY(-26px) scale(1.02);
39116
+ z-index: 30;
39117
+ }
39118
+
39119
+ /* Worktree branch cards: drop the comic-notch that was slicing the
39120
+ "YOU ARE HERE" tag on the current branch (same clip bug as the region-map
39121
+ stamp). Keep the ink+gold-spine look via border only. */
39122
+ .vr-bcard { clip-path: none; overflow: visible; }
39123
+ .vr-bcard .vr-stamp.is-mini { z-index: 4; }
39124
+
39125
+ /* Placed-card X sits above the holo foil so it is always clickable. */
39126
+ .vr-tcard.is-placed .vr-tcard-holofoil,
39127
+ .vr-tcard.is-placed .vr-tcard-glare { pointer-events: none; }
39128
+
39129
+ /* Placed PROVIDER card X: top-left (grade coin is top-right), small + clean. */
39130
+ .vr-tcard.is-placed .vr-tcard-unplace {
39131
+ width: 17px;
39132
+ height: 17px;
39133
+ top: 5px;
39134
+ left: 5px;
39135
+ right: auto;
39136
+ font-size: 9px;
39137
+ }
39138
+ /* Placed VERSION card X: top-RIGHT (the V1.x label sits top-left), small. */
39139
+ .vr-vcard.is-mini.is-placed .vr-tcard-unplace {
39140
+ opacity: 1;
39141
+ width: 17px;
39142
+ height: 17px;
39143
+ top: 5px;
39144
+ right: 5px;
39145
+ left: auto;
39146
+ font-size: 9px;
39147
+ background: rgba(6, 6, 8, 0.92);
39148
+ border: 1.5px solid var(--vr-gold);
39149
+ color: var(--vr-gold-bright);
39150
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.7);
39151
+ }
39152
+ .vr-vcard.is-mini.is-placed .vr-tcard-unplace:hover { background: var(--vr-red); border-color: var(--vr-red); color: #fff; }
39153
+
39154
+ /* Placed-card HOVER must KEEP the translate(-50%,-50%) centering \u2014 the has-art
39155
+ hover rule was replacing it, so the card JUMPED off-position (the "buggy
39156
+ hover"). Gentle lift, keep centered, no snap. */
39157
+ .vr-tcard.is-placed:hover,
39158
+ .vr-tcard.is-placed.has-art:hover {
39159
+ transform: translate(-50%, -50%) rotate(0deg) scale(1.05) !important;
39160
+ z-index: 40;
39161
+ }
39162
+
39163
+ /* ==========================================================================
39164
+ MANGA READABILITY + CARD-VIBE PASS
39165
+ Appended after the FINAL FIXES block (>16354) so it wins by source order.
39166
+ (1) Typography: 3-tier hierarchy \u2014 Anton=headline only, mono=legible floor,
39167
+ sans=running/secondary UI. (2) Manga card-vibe: faint margin-only ink,
39168
+ gold halftone + card/raven/poster watermarks that NEVER sit behind text.
39169
+ !important is used ONLY where an existing same-file !important forces it.
39170
+ ========================================================================== */
39171
+
39172
+ /* -------------------------------------------------------------------------
39173
+ (1) TYPOGRAPHY
39174
+ ------------------------------------------------------------------------- */
39175
+
39176
+ /* 1a. Small mono-telemetry label FLOOR \u2014 lift every sub-9.5px cap to legible.
39177
+ (.vr-tcard-type/-grade base 7-8px; .vr-bcard em / .vr-vpile-card em carry
39178
+ an existing 9px !important, so those need !important to reach the floor.) */
39179
+ .vr-tcard-type,
39180
+ .vr-tcard-grade,
39181
+ .vr-tcard.is-back .vr-tcard-backname,
39182
+ .vr-qs-local,
39183
+ .vr-bcard em,
39184
+ .vr-vpile-card em,
39185
+ .vr-addpick-list button em {
39186
+ font-size: 9.5px !important; /* was 6\u20139px; beats 16104/16079 !important */
39187
+ line-height: 1.25;
39188
+ font-weight: 620; /* thin mono needs weight on black paper */
39189
+ }
39190
+ .vr-tcard.is-dealt .vr-tcard-type,
39191
+ .vr-tcard.is-dealt .vr-tcard-grade { font-size: 10px !important; }
39192
+
39193
+ /* 1b. Letter-spacing CAP on tiny caps \u2014 extreme 1.4\u20132px tracking smears words.
39194
+ (Cap to ~0.07em. .vr-hud-strip / chip .muted already retoned at 15992+,
39195
+ so only their tracking is nudged, not restated.) */
39196
+ .vr-tcard-type,
39197
+ .vr-tcard-grade,
39198
+ .vr-tcard.is-back .vr-tcard-backname,
39199
+ .vr-qs-local,
39200
+ .vr-pile-shuffle,
39201
+ .vr-bcard-actions button,
39202
+ .vr-table-warning button {
39203
+ letter-spacing: 0.07em;
39204
+ }
39205
+ .vr-hud-strip { letter-spacing: 0.04em; }
39206
+
39207
+ /* 1c. Anton DEMOTION \u2014 pull the condensed poster face off running UI text.
39208
+ Menu labels + action-button captions move to the readable sans stack. */
39209
+ .vr-scope-menu p,
39210
+ .vr-studio-add-chat,
39211
+ .vr-provider-ask-agent,
39212
+ .vr-new-chat-button,
39213
+ .vr-agent-followups button.is-primary,
39214
+ .vr-chat-command-popover button.is-primary,
39215
+ .vr-cli-action-button.is-primary {
39216
+ font-family: var(--vr-hud-sans, 'Geist', 'Inter', system-ui, sans-serif);
39217
+ font-weight: 700;
39218
+ letter-spacing: 0.03em;
39219
+ text-transform: uppercase;
39220
+ }
39221
+ .vr-scope-menu p { font-size: 10px; }
39222
+
39223
+ /* 1d. Studio tab bar \u2014 Anton stays (display scale) but sized up + opened for
39224
+ scannable CHAT / ARCHITECTURE / TERMINAL / DIFF. */
39225
+ .vr-studio-tabs button {
39226
+ font-size: 13px;
39227
+ letter-spacing: 0.06em;
39228
+ font-weight: 400; /* Anton is already heavy */
39229
+ }
39230
+
39231
+ /* 1e. HUD telemetry strip \u2014 bigger, weightier instrument line. */
39232
+ .vr-hud-strip { font-size: 10px; color: #d2cabc; }
39233
+ .vr-hud-strip strong { font-weight: 720; color: #f7f4ec; }
39234
+ .vr-hud-strip em,
39235
+ .vr-hud-strip b { font-weight: 600; }
39236
+
39237
+ /* 1f. Context chips \u2014 cream sans label, drop the cold-grey muted qualifier. */
39238
+ .vr-context-chip-label {
39239
+ font-size: 12px;
39240
+ font-weight: 620;
39241
+ letter-spacing: 0.01em;
39242
+ }
39243
+ .vr-context-chip-label .muted {
39244
+ color: var(--vr-muted);
39245
+ font-weight: 560;
39246
+ }
39247
+
39248
+ /* 1g. Region-map / architecture node badges + zone/hint \u2014 weight up, cap track. */
39249
+ .vr-arch-node-badge {
39250
+ font-size: 10.5px;
39251
+ font-weight: 780;
39252
+ letter-spacing: 0.02em;
39253
+ }
39254
+ .vr-arch-zone { font-size: 10px; letter-spacing: 0.06em; }
39255
+ .vr-arch-hint { font-size: 11px; letter-spacing: 0.01em; line-height: 1.4; }
39256
+
39257
+ /* 1h. Model/scope picker button \u2014 selected model reads clean, non-condensed. */
39258
+ .vr-model-picker > button strong {
39259
+ font-family: var(--vr-hud-sans, 'Geist', 'Inter', system-ui, sans-serif);
39260
+ font-size: 12px;
39261
+ font-weight: 680;
39262
+ letter-spacing: 0.01em;
39263
+ }
39264
+ .vr-model-picker > button span {
39265
+ font-size: 10px;
39266
+ letter-spacing: 0.02em;
39267
+ color: var(--vr-muted);
39268
+ }
39269
+
39270
+ /* 1i. Dropdown menu rows \u2014 readable item body + sub-caption, sans not condensed. */
39271
+ .vr-model-menu button,
39272
+ .vr-scope-menu button,
39273
+ .vr-chat-command-popover button {
39274
+ font-size: 12.5px;
39275
+ letter-spacing: 0.005em;
39276
+ }
39277
+ .vr-model-menu button strong { font-weight: 660; }
39278
+ .vr-model-menu button em { font-size: 11px; font-style: normal; color: var(--vr-muted); }
39279
+
39280
+ /* 1j. Stat-card label under the big Anton KPI number \u2014 size up the smear. */
39281
+ .vr-stat-card span {
39282
+ font-size: 9.5px;
39283
+ letter-spacing: 0.08em;
39284
+ font-weight: 620;
39285
+ color: #ccc4b6;
39286
+ }
39287
+
39288
+ /* 1k. Version card meta / branch / pile label \u2014 floor size + cap tracking.
39289
+ (Overrides the contrast-pass 8.5px !important at 16063-64 and the pile
39290
+ letter-spacing at 16080, so font-size/letter-spacing need !important.) */
39291
+ .vr-vcard-meta,
39292
+ .vr-vcard-branch {
39293
+ font-size: 9.5px !important; /* was 8.5px !important */
39294
+ letter-spacing: 0.05em !important;
39295
+ }
39296
+ .vr-version-pile-label {
39297
+ font-size: 9.5px !important;
39298
+ letter-spacing: 0.08em !important; /* was 1.8px */
39299
+ font-weight: 640;
39300
+ }
39301
+
39302
+ /* 1l. SHUFFLE / worktree / table-foot action captions \u2014 legible click labels. */
39303
+ .vr-pile-shuffle,
39304
+ .vr-bcard-actions button,
39305
+ .vr-table-warning button,
39306
+ .vr-table-foot button {
39307
+ font-size: 10px;
39308
+ letter-spacing: 0.07em;
39309
+ font-weight: 680;
39310
+ }
39311
+
39312
+ /* 1m. Highest-traffic reading surfaces \u2014 comfortable body size + leading. */
39313
+ .vr-agent-answer-body,
39314
+ .vr-agent-answer-body p,
39315
+ .vr-chat-message p,
39316
+ .vr-chat-bubble p {
39317
+ font-size: 13.5px;
39318
+ line-height: 1.55;
39319
+ letter-spacing: 0.003em;
39320
+ }
39321
+ .vr-terminal-line { font-size: 12.5px; line-height: 1.5; }
39322
+
39323
+ /* 1n. Empty-state / drop-zone / note / recent-chat meta \u2014 lift learn-the-UI text. */
39324
+ .vr-table-empty { font-size: 12.5px; line-height: 1.45; }
39325
+ .vr-provider-drop-zone { font-size: 12px; letter-spacing: 0.01em; }
39326
+ .vr-chat-note { font-size: 12px; line-height: 1.45; }
39327
+ .vr-recent-chat em,
39328
+ .vr-recent-chat small { font-size: 11px; font-style: normal; }
39329
+
39330
+ /* 1o. Env-connect code chip + terminal compose \u2014 legible mono command hints. */
39331
+ .vr-env-connect-option b {
39332
+ font-size: 10px;
39333
+ letter-spacing: 0.04em;
39334
+ font-weight: 640;
39335
+ }
39336
+ .vr-terminal-compose textarea {
39337
+ font-size: 13px;
39338
+ line-height: 1.5;
39339
+ letter-spacing: 0;
39340
+ }
39341
+
39342
+ /* -------------------------------------------------------------------------
39343
+ (2) MANGA CARD-VIBE BACKGROUND \u2014 all margin/corner-masked, never behind text
39344
+ ------------------------------------------------------------------------- */
39345
+
39346
+ /* Keep app chrome above the fixed texture layers so opaque panels occlude them. */
39347
+ .vr-app { position: relative; z-index: 1; }
39348
+ .vr-composed-shell { position: relative; z-index: 1; }
39349
+
39350
+ /* 2a. Fixed full-viewport ink-splatter, painted ONLY in the outer page gutter.
39351
+ The .vr-composed-shell (opaque near-black, margin:10px 12px) sits on top and
39352
+ blocks it wherever real content lives; screen-blend keeps it light ink. */
39353
+ body::before {
39354
+ content: '';
39355
+ position: fixed;
39356
+ inset: 0;
39357
+ z-index: 0;
39358
+ pointer-events: none;
39359
+ background: url('/manga/bg.webp') no-repeat center center / cover;
39360
+ /* opacity + mask only \u2014 no mix-blend/filter (those are GPU-heavy on a
39361
+ fixed full-viewport layer and stall headless capture). The art is already
39362
+ ink-on-black so it reads as faint ink in the gutter at this opacity. */
39363
+ opacity: 0.09;
39364
+ -webkit-mask-image: radial-gradient(ellipse 74% 70% at 50% 46%, transparent 60%, #000 92%);
39365
+ mask-image: radial-gradient(ellipse 74% 70% at 50% 46%, transparent 60%, #000 92%);
39366
+ }
39367
+
39368
+ /* 2b. Corner-weighted fine GOLD halftone screentone (manga-card dot vibe),
39369
+ masked to two opposite corners only so the chat/panel mid-screen stays clean. */
39370
+ body::after {
39371
+ content: '';
39372
+ position: fixed;
39373
+ inset: 0;
39374
+ z-index: 0;
39375
+ pointer-events: none;
39376
+ background-image: radial-gradient(rgba(232, 193, 90, 0.10) 0.9px, transparent 1.3px);
39377
+ background-size: 5px 5px;
39378
+ opacity: 0.5;
39379
+ -webkit-mask-image:
39380
+ radial-gradient(circle at 100% 0%, #000 0%, transparent 34%),
39381
+ radial-gradient(circle at 0% 100%, #000 0%, transparent 34%);
39382
+ mask-image:
39383
+ radial-gradient(circle at 100% 0%, #000 0%, transparent 34%),
39384
+ radial-gradient(circle at 0% 100%, #000 0%, transparent 34%);
39385
+ -webkit-mask-composite: source-over;
39386
+ mask-composite: add;
39387
+ }
39388
+
39389
+ /* 2c. Ghost provider card tucked into the shell's top-left gutter, behind the
39390
+ translucent topbar/rail only. Lives inside overflow:hidden (clipped to shell),
39391
+ blurred + rotated like a card left on the table, faded before the chat column.
39392
+ RISK NOTE: .vr-composed-shell::after is reused as the drop overlay while
39393
+ dragging (.vr-app.is-dragging-*), which harmlessly hides this watermark
39394
+ during an active drag; it returns on drop. Idle state is unaffected. */
39395
+ .vr-composed-shell .vr-studio-body::after {
39396
+ content: '';
39397
+ position: absolute;
39398
+ left: -70px;
39399
+ top: -40px;
39400
+ width: 300px;
39401
+ height: 420px;
39402
+ z-index: 0;
39403
+ pointer-events: none;
39404
+ background: url('/manga/cards/supabase.webp') no-repeat center / contain;
39405
+ opacity: 0.05;
39406
+ transform: rotate(-13deg);
39407
+ filter: grayscale(0.6) contrast(0.95) blur(0.4px);
39408
+ -webkit-mask-image: linear-gradient(135deg, #000 12%, transparent 62%);
39409
+ mask-image: linear-gradient(135deg, #000 12%, transparent 62%);
39410
+ }
39411
+
39412
+ /* 2d. Existing raven emblem watermark (base at 15577, opacity 0.06, no mask):
39413
+ add a corner-fade mask + push below content so it never creeps under rows.
39414
+ !important here re-targets the already-textured pseudo across states. */
39415
+ .vr-table-view::after,
39416
+ .vr-studio-architecture.is-region-map::after {
39417
+ z-index: 0 !important;
39418
+ opacity: 0.07 !important;
39419
+ -webkit-mask-image: radial-gradient(circle at 100% 100%, #000 0%, transparent 58%) !important;
39420
+ mask-image: radial-gradient(circle at 100% 100%, #000 0%, transparent 58%) !important;
39421
+ }
39422
+ .vr-table-view > *,
39423
+ .vr-studio-architecture.is-region-map > * { position: relative; z-index: 1; }
39424
+
39425
+ /* 2e. Poster ghost low in the left gutter of the studio body \u2014 far-corner masked
39426
+ + blurred so it never reaches the readable center/right columns. */
39427
+ .vr-composed-shell .vr-studio-body { position: relative; }
39428
+ .vr-composed-shell .vr-studio-body::before {
39429
+ content: '';
39430
+ position: absolute;
39431
+ left: -30px;
39432
+ bottom: -30px;
39433
+ width: 280px;
39434
+ height: 360px;
39435
+ z-index: 0;
39436
+ pointer-events: none;
39437
+ background: url('/manga/poster.webp') no-repeat left bottom / contain;
39438
+ opacity: 0.045;
39439
+ filter: grayscale(0.7) contrast(1.05) blur(0.6px);
39440
+ -webkit-mask-image: radial-gradient(circle at 0% 100%, #000 0%, transparent 55%);
39441
+ mask-image: radial-gradient(circle at 0% 100%, #000 0%, transparent 55%);
39442
+ }
39443
+
39444
+ /* 2f. CONTRAST GUARD \u2014 the three translucent panels are rgba(18,20,24,.78),
39445
+ 22% transparent, so texture could faintly show under text. Bump the ink base
39446
+ more opaque; margins keep the vibe, text stays crisp. (background-color only,
39447
+ later source order beats the shorthand at 1737 without !important.) */
39448
+ .vr-studio-chat,
39449
+ .vr-studio-terminal,
39450
+ .vr-studio-diff { background-color: rgba(14, 15, 19, 0.9); }
39451
+
39452
+ /* ============================================================================
39453
+ MANGA READABILITY \u2014 FINAL ENFORCEMENT PASS (appended after all prior
39454
+ override chains: MANGA THEME PASS / B&W CONTRAST / FINAL FIXES / READABILITY).
39455
+ Wins by source order. Two groups:
39456
+ (1) READABLE FONTS \u2014 strict 3-tier type system, no coloured/gradient
39457
+ button text, condensed Anton pulled off all running/clickable UI.
39458
+ (2) MENTION + WELCOME/HANDOFF MANGA THEME \u2014 re-skin the old blue-slate
39459
+ PromptModal + quickstart to ink/gold manga.
39460
+ !important is used ONLY where an existing same-file !important forces it
39461
+ (documented inline). Readability always wins over theme where they overlap.
39462
+ RISK NOTES: clip-path notches added to modal/menu panels can clip focus
39463
+ rings / drop-shadows on the notched corner \u2014 acceptable (decorative corner
39464
+ only, ~12-18px, no interactive target sits there). No overflow:hidden added,
39465
+ so popovers/tooltips are unaffected.
39466
+ ========================================================================= */
39467
+
39468
+ /* ---------------------------------------------------------------------------
39469
+ GROUP 1 \u2014 READABLE FONTS
39470
+ --------------------------------------------------------------------------- */
39471
+
39472
+ /* 1.0 \u2014 Foundation: guarantee one clean non-condensed sans stack for TIER 2.
39473
+ Every rule below that says var(--vr-hud-sans) resolves to this. */
39474
+ :root {
39475
+ --vr-hud-sans: 'Geist', 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
39476
+ }
39477
+
39478
+ /* 1.1 \u2014 Composer action bar (ADD CONTEXT / MENTION / COMMANDS / WORST CARD /
39479
+ ADD CARD): the exact pills the user screenshotted. Clean sans, normal-case,
39480
+ comfortable size, single cream colour. Neutralise any inherited condensed
39481
+ face and any -webkit-text-fill on the label span. */
39482
+ .vr-chat-inline-actions > button,
39483
+ .vr-chat-command-trigger > button,
39484
+ .vr-chat-inline-actions button {
39485
+ font-family: var(--vr-hud-sans);
39486
+ font-size: 12.5px;
39487
+ font-weight: 650;
39488
+ letter-spacing: 0.02em;
39489
+ text-transform: none;
39490
+ color: var(--vr-text);
39491
+ }
39492
+ .vr-chat-inline-actions > button span,
39493
+ .vr-chat-command-trigger > button span {
39494
+ font-family: var(--vr-hud-sans);
39495
+ font-weight: 650;
39496
+ letter-spacing: 0.02em;
39497
+ color: var(--vr-text);
39498
+ -webkit-text-fill-color: currentColor;
39499
+ background: none;
39500
+ }
39501
+
39502
+ /* 1.2 \u2014 Composer action icons: kill MENTION multicolour / ADD CARD purple.
39503
+ Pin all glyphs to the same muted cream as the label so each pill is one
39504
+ legible colour. (Worst-card icon keeps a gold accent \u2014 see 1.3.) */
39505
+ .vr-chat-inline-actions > button i[data-icon],
39506
+ .vr-chat-inline-actions > button svg,
39507
+ .vr-chat-command-trigger > button i[data-icon],
39508
+ .vr-chat-command-trigger > button svg {
39509
+ color: var(--vr-muted);
39510
+ stroke: var(--vr-muted);
39511
+ }
39512
+
39513
+ /* 1.3 \u2014 WORST CARD pill: remove the gold TEXT (overrides 14663-14664). Label
39514
+ goes cream like its siblings; gold survives only as border + icon accent. */
39515
+ .vr-chat-inline-actions > button.is-worst-card,
39516
+ .vr-chat-inline-actions > button.is-worst-card span {
39517
+ color: var(--vr-text);
39518
+ }
39519
+ .vr-chat-inline-actions > button.is-worst-card {
39520
+ border-color: rgba(232, 193, 90, 0.42);
39521
+ }
39522
+ .vr-chat-inline-actions > button.is-worst-card i[data-icon],
39523
+ .vr-chat-inline-actions > button.is-worst-card svg {
39524
+ color: var(--vr-gold);
39525
+ stroke: var(--vr-gold);
39526
+ }
39527
+
39528
+ /* 1.4 \u2014 is-primary composer pills (gold fill): dark ink text, not gold text. */
39529
+ .vr-chat-inline-actions button.is-primary,
39530
+ .vr-chat-inline-actions button.is-primary span {
39531
+ color: #1a1206;
39532
+ }
39533
+
39534
+ /* 1.5 \u2014 Hover on composer buttons: emphasis via border + bright ink, not by
39535
+ flipping the whole label. Gold-bright label on hover is legible on the
39536
+ darkened background and matches the menu hover language elsewhere. */
39537
+ .vr-chat-inline-actions button:hover,
39538
+ .vr-chat-inline-actions > button:hover,
39539
+ .vr-chat-command-trigger > button:hover {
39540
+ color: var(--vr-gold-bright);
39541
+ border-color: var(--vr-gold);
39542
+ background: rgba(20, 18, 12, 0.85);
39543
+ }
39544
+ .vr-chat-inline-actions > button:hover span { color: var(--vr-gold-bright); }
39545
+ .vr-chat-inline-actions > button:hover i[data-icon],
39546
+ .vr-chat-inline-actions > button:hover svg {
39547
+ color: var(--vr-gold-bright);
39548
+ stroke: var(--vr-gold-bright);
39549
+ }
39550
+
39551
+ /* 1.6 \u2014 ADD CARD shelf chip + is-gold variant. Base 8.5px/1.4px mono (14503)
39552
+ is NOT !important, so plain declarations win by source order \u2014 no
39553
+ !important needed. Readable sans, >=11px, tight tracking; gold stays as the
39554
+ FILL with dark ink text. */
39555
+ .vr-shelf-chip {
39556
+ font-family: var(--vr-hud-sans);
39557
+ font-size: 11px;
39558
+ font-weight: 650;
39559
+ letter-spacing: 0.03em;
39560
+ text-transform: none;
39561
+ color: var(--vr-text);
39562
+ }
39563
+ .vr-shelf-chip.is-gold { color: #1a1206; }
39564
+
39565
+ /* 1.7 \u2014 Quick-action tiles (left rail). Sans, readable weight/size, single
39566
+ cream colour; icon a quiet accent. */
39567
+ .vr-quick-action,
39568
+ .vr-quick-action strong,
39569
+ .vr-quick-action em {
39570
+ font-family: var(--vr-hud-sans);
39571
+ letter-spacing: 0.01em;
39572
+ text-transform: none;
39573
+ color: var(--vr-text);
39574
+ }
39575
+ .vr-quick-action strong { font-size: 12.5px; font-weight: 660; }
39576
+ .vr-quick-action em { font-size: 11px; font-weight: 500; color: var(--vr-muted); }
39577
+ .vr-quick-action i, .vr-quick-action svg { color: var(--vr-muted); stroke: var(--vr-muted); }
39578
+
39579
+ /* 1.8 \u2014 Dropdown menu rows (model / scope / command popover). Sans, comfortable
39580
+ size, cream text. Section HEADERS (.vr-model-menu p / .vr-scope-menu p) stay
39581
+ Anton gold \u2014 that is a valid TIER-1 eyebrow, restyled in 1.9 / GROUP 2. */
39582
+ .vr-model-menu button,
39583
+ .vr-scope-menu button,
39584
+ .vr-chat-command-popover button {
39585
+ font-family: var(--vr-hud-sans);
39586
+ font-size: 12.5px;
39587
+ font-weight: 600;
39588
+ letter-spacing: 0.005em;
39589
+ text-transform: none;
39590
+ color: var(--vr-text);
39591
+ }
39592
+ .vr-model-menu button strong,
39593
+ .vr-scope-menu button strong { font-family: var(--vr-hud-sans); font-weight: 660; }
39594
+ .vr-model-menu button em,
39595
+ .vr-scope-menu button em {
39596
+ font-family: var(--vr-hud-sans);
39597
+ font-style: normal;
39598
+ font-weight: 500;
39599
+ color: var(--vr-muted);
39600
+ }
39601
+
39602
+ /* 1.9 \u2014 Quickstrike command menu: clickable rows go sans/cream; header stays a
39603
+ small tracked eyebrow. */
39604
+ .vr-quickstrike button {
39605
+ font-family: var(--vr-hud-sans);
39606
+ font-size: 12.5px;
39607
+ font-weight: 600;
39608
+ letter-spacing: 0.01em;
39609
+ text-transform: none;
39610
+ color: var(--vr-text);
39611
+ }
39612
+ .vr-quickstrike header {
39613
+ font-size: 11px;
39614
+ letter-spacing: 0.06em;
39615
+ }
39616
+
39617
+ /* 1.10 \u2014 Model/scope picker trigger (selected-model label). */
39618
+ .vr-model-picker > button strong {
39619
+ font-family: var(--vr-hud-sans);
39620
+ font-size: 12px;
39621
+ font-weight: 660;
39622
+ letter-spacing: 0.01em;
39623
+ color: var(--vr-text);
39624
+ }
39625
+ .vr-model-picker > button span {
39626
+ font-family: var(--vr-hud-sans);
39627
+ font-size: 10.5px;
39628
+ letter-spacing: 0.02em;
39629
+ color: var(--vr-muted);
39630
+ }
39631
+
39632
+ /* 1.11 \u2014 Primary gold CTAs that were switched to Anton (15704+): pull them to
39633
+ bold sans, dark ink on gold. Gold is fill/accent, never text colour. */
39634
+ .vr-new-chat-button,
39635
+ .vr-provider-ask-agent,
39636
+ .vr-approve-all,
39637
+ .vr-chat-command-popover button.is-primary,
39638
+ .vr-agent-followups button.is-primary,
39639
+ .vr-agent-inline-action.is-primary,
39640
+ .vr-cli-action-button.is-primary,
39641
+ .vr-studio-add-chat,
39642
+ .vr-studio-architecture > footer button.is-primary,
39643
+ .vr-arch-drawer footer button.is-primary,
39644
+ .vr-wt-actions button.is-primary {
39645
+ font-family: var(--vr-hud-sans);
39646
+ font-weight: 700;
39647
+ letter-spacing: 0.02em;
39648
+ text-transform: none;
39649
+ color: #1a1206;
39650
+ }
39651
+
39652
+ /* 1.12 \u2014 Studio tab bar (CHAT / ARCHITECTURE / TERMINAL / DIFF): legitimate
39653
+ TIER-1 marquee. Keep Anton, but lock size/tracking legible; active tab keeps
39654
+ dark ink on gold. */
39655
+ .vr-studio-tabs button {
39656
+ font-family: var(--mg-display);
39657
+ font-size: 13.5px;
39658
+ letter-spacing: 0.06em;
39659
+ font-weight: 400;
39660
+ }
39661
+ .vr-studio-tabs button.is-active { color: #0a0b0e; }
39662
+
39663
+ /* 1.13 \u2014 Context chips + provider drop-label in the composer. */
39664
+ .vr-context-chip-label,
39665
+ .vr-provider-drop-label {
39666
+ font-family: var(--vr-hud-sans);
39667
+ font-size: 12px;
39668
+ font-weight: 620;
39669
+ letter-spacing: 0.01em;
39670
+ text-transform: none;
39671
+ color: var(--vr-text);
39672
+ }
39673
+ .vr-context-chip-label .muted { font-weight: 520; color: var(--vr-muted); }
39674
+
39675
+ /* 1.14 \u2014 Stat-card LABEL beneath the Anton KPI number. The number
39676
+ (.vr-stat-card b) keeps Anton untouched; only the micro-label is lifted. */
39677
+ .vr-stat-card span {
39678
+ font-family: var(--vr-hud-sans);
39679
+ font-size: 10.5px;
39680
+ font-weight: 600;
39681
+ letter-spacing: 0.04em;
39682
+ text-transform: uppercase;
39683
+ color: #ccc4b6;
39684
+ }
39685
+
39686
+ /* 1.15 \u2014 Ribbon stat qualifier + table-ribbon brand value. */
39687
+ .vr-ribbon-stat i {
39688
+ font-family: var(--vr-hud-sans);
39689
+ font-size: 10px;
39690
+ font-weight: 600;
39691
+ letter-spacing: 0.03em;
39692
+ color: var(--vr-muted);
39693
+ }
39694
+ .vr-table-ribbon > b {
39695
+ font-family: var(--vr-hud-sans);
39696
+ font-size: 12.5px;
39697
+ font-weight: 700;
39698
+ color: var(--vr-gold-bright);
39699
+ }
39700
+
39701
+ /* 1.16 \u2014 Version card meta / branch / pile labels. These carry same-file
39702
+ !important (16062/16064/16065/16081) on color/size/family, so !important is
39703
+ required here to win on family + tracking. Size floors from 16386 are left
39704
+ intact (readability floor already >=9.5px). */
39705
+ .vr-vcard-meta,
39706
+ .vr-vcard-branch,
39707
+ .vr-version-pile-label {
39708
+ font-family: var(--vr-hud-sans) !important;
39709
+ letter-spacing: 0.03em !important;
39710
+ text-transform: none;
39711
+ }
39712
+ .vr-vpile-card b {
39713
+ font-family: var(--vr-hud-sans) !important; /* beats 16062 !important */
39714
+ letter-spacing: 0.01em;
39715
+ }
39716
+
39717
+ /* 1.17 \u2014 Shuffle / worktree / table-foot / back-card / addpick action captions:
39718
+ every click label a plain readable word, not a condensed cap. */
39719
+ .vr-pile-shuffle,
39720
+ .vr-bcard-actions button,
39721
+ .vr-table-warning button,
39722
+ .vr-table-foot button,
39723
+ .vr-addpick-list button strong {
39724
+ font-family: var(--vr-hud-sans);
39725
+ font-size: 11px;
39726
+ font-weight: 640;
39727
+ letter-spacing: 0.03em;
39728
+ text-transform: none;
39729
+ color: var(--vr-text);
39730
+ }
39731
+
39732
+ /* 1.18 \u2014 Global guard: no gradient/clipped text may re-enter any button/label.
39733
+ (No same-file !important on -webkit-text-fill-color, so none needed here.) */
39734
+ .vr-chat-inline-actions button,
39735
+ .vr-chat-command-trigger button,
39736
+ .vr-quick-action,
39737
+ .vr-quick-action strong,
39738
+ .vr-quick-action em,
39739
+ .vr-shelf-chip,
39740
+ .vr-studio-tabs button,
39741
+ .vr-model-menu button,
39742
+ .vr-scope-menu button,
39743
+ .vr-chat-command-popover button {
39744
+ -webkit-text-fill-color: currentColor;
39745
+ -webkit-background-clip: border-box;
39746
+ background-clip: border-box;
39747
+ }
39748
+
39749
+ /* ---------------------------------------------------------------------------
39750
+ GROUP 2 \u2014 MENTION + WELCOME/HANDOFF MANGA THEME
39751
+ The composer "Mention" button (data-action="prompt") opens the PromptModal,
39752
+ so re-theming the PromptModal fixes both the "mention screen" and the Scoped
39753
+ Handoff / Agent Mission modal. Also sweeps the leftover blue-slate theme off
39754
+ Guide/Plan/Notifications and finishes the quickstart cards.
39755
+ RISK NOTE: clip-path notch on modal panels clips the far corner only; no
39756
+ text or control sits there.
39757
+ --------------------------------------------------------------------------- */
39758
+
39759
+ /* 2.1 \u2014 Modal header shell (Agent Mission / Guide): navy gradient + slate
39760
+ border -> ink panel, gold left spine, gold halftone. */
39761
+ .vr-modal-prompt-head,
39762
+ .vr-modal-guide-head {
39763
+ border: 1px solid var(--vr-border-strong);
39764
+ border-left: 3px solid var(--vr-gold);
39765
+ border-radius: 0;
39766
+ clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 18px 100%, 0 calc(100% - 18px));
39767
+ background-color: rgba(9, 9, 11, 0.94);
39768
+ background-image:
39769
+ radial-gradient(circle at 12% 30%, rgba(232, 193, 90, 0.07), transparent 40%),
39770
+ radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.4px);
39771
+ background-size: auto, 9px 9px;
39772
+ box-shadow: 5px 6px 0 rgba(0, 0, 0, 0.55);
39773
+ }
39774
+
39775
+ /* 2.2 \u2014 Header eyebrow: gold token, small Anton poster eyebrow. */
39776
+ .vr-modal-prompt-head p,
39777
+ .vr-modal-guide-head p {
39778
+ color: var(--vr-gold);
39779
+ font-family: var(--mg-display);
39780
+ font-weight: 400;
39781
+ font-size: 12px;
39782
+ letter-spacing: 0.14em;
39783
+ }
39784
+
39785
+ /* 2.3 \u2014 Header title: Anton poster caps, cream ink. */
39786
+ .vr-modal-prompt-head h2,
39787
+ .vr-modal-guide-head h2 {
39788
+ font-family: var(--mg-display);
39789
+ font-weight: 400;
39790
+ font-size: 40px;
39791
+ letter-spacing: 0.01em;
39792
+ text-transform: uppercase;
39793
+ color: var(--vr-text);
39794
+ }
39795
+
39796
+ /* 2.4 \u2014 Header sub-copy: ice-blue -> readable cream sans. */
39797
+ .vr-modal-prompt-head em,
39798
+ .vr-modal-guide-head em {
39799
+ font-family: var(--vr-hud-sans);
39800
+ color: var(--vr-muted);
39801
+ font-size: 14px;
39802
+ font-style: normal;
39803
+ line-height: 1.5;
39804
+ }
39805
+
39806
+ /* 2.5 \u2014 Header side aside (Scope / One gap): slate glass -> ink + gold hairline. */
39807
+ .vr-modal-prompt-head aside,
39808
+ .vr-modal-guide-head aside {
39809
+ border: 1px solid var(--vr-border-strong);
39810
+ border-radius: 0;
39811
+ background: rgba(0, 0, 0, 0.28);
39812
+ }
39813
+ .vr-modal-prompt-head aside > i,
39814
+ .vr-modal-guide-head aside > i {
39815
+ background: rgba(232, 193, 90, 0.10);
39816
+ color: var(--vr-gold-bright);
39817
+ border-radius: 0;
39818
+ }
39819
+ .vr-modal-prompt-head aside > span,
39820
+ .vr-modal-guide-head aside > span {
39821
+ color: var(--vr-gold);
39822
+ font-family: var(--vr-hud-sans);
39823
+ letter-spacing: 0.06em;
39824
+ }
39825
+ .vr-modal-prompt-head aside > strong,
39826
+ .vr-modal-guide-head aside > strong { color: var(--vr-text); }
39827
+
39828
+ /* 2.6 \u2014 Scoped-handoff mission <pre> card: navy border + ice-blue mono ->
39829
+ ink card, gold spine, cream mono. */
39830
+ .vr-modal-prompt-card {
39831
+ border: 1px solid var(--vr-border);
39832
+ border-left: 3px solid var(--vr-gold);
39833
+ border-radius: 0;
39834
+ clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 14px 100%, 0 calc(100% - 14px));
39835
+ background:
39836
+ radial-gradient(circle at 88% 0%, rgba(232, 193, 90, 0.05), transparent 34%),
39837
+ rgba(9, 9, 11, 0.94);
39838
+ }
39839
+ .vr-modal-prompt-card pre {
39840
+ color: var(--vr-text);
39841
+ font-family: var(--vr-hud-mono);
39842
+ font-size: 12.5px;
39843
+ line-height: 1.7;
39844
+ }
39845
+
39846
+ /* 2.7 \u2014 Flow/signal list panels: navy -> ink + gold halftone. */
39847
+ .vr-modal-flow-list,
39848
+ .vr-modal-signal-list {
39849
+ border: 1px solid var(--vr-border-strong);
39850
+ border-radius: 0;
39851
+ clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px));
39852
+ background:
39853
+ radial-gradient(rgba(232, 193, 90, 0.04) 1px, transparent 1.4px) 0 0 / 9px 9px,
39854
+ rgba(9, 9, 11, 0.94);
39855
+ }
39856
+
39857
+ /* 2.8 \u2014 Flow-list step rows: slate borders + purple glow + cold-blue caption
39858
+ -> ink rows, gold-tinted hover spine, readable sans. */
39859
+ .vr-modal-flow-list button {
39860
+ border: 1px solid var(--vr-border);
39861
+ border-radius: 0;
39862
+ background: rgba(0, 0, 0, 0.25);
39863
+ }
39864
+ .vr-modal-flow-list button:hover {
39865
+ border-color: var(--vr-gold);
39866
+ box-shadow: inset 3px 0 0 var(--vr-gold);
39867
+ background: rgba(232, 193, 90, 0.10);
39868
+ }
39869
+ .vr-modal-flow-list button > span {
39870
+ border: 2px solid var(--vr-gold);
39871
+ color: var(--vr-gold-bright);
39872
+ }
39873
+ .vr-modal-flow-list strong { font-family: var(--vr-hud-sans); color: var(--vr-text); }
39874
+ .vr-modal-flow-list em {
39875
+ font-family: var(--vr-hud-sans);
39876
+ color: var(--vr-muted);
39877
+ font-size: 12px;
39878
+ line-height: 1.45;
39879
+ }
39880
+
39881
+ /* 2.9 \u2014 Composer scope/mention popup panel: reinforce ink + gold spine +
39882
+ comic-notch so it reads as part of the manga card system. */
39883
+ .vr-scope-menu {
39884
+ border-radius: 0;
39885
+ border-left: 3px solid var(--vr-gold);
39886
+ clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));
39887
+ background-color: rgba(9, 9, 11, 0.96);
39888
+ box-shadow: 4px 6px 0 rgba(0, 0, 0, 0.55);
39889
+ }
39890
+
39891
+ /* 2.10 \u2014 Scope/mention popup section label: Anton gold eyebrow, legible size.
39892
+ (Group 1 already forced the ROWS to sans; this only styles the <p> header.) */
39893
+ .vr-scope-menu p {
39894
+ font-family: var(--mg-display);
39895
+ font-size: 11px;
39896
+ letter-spacing: 0.14em;
39897
+ color: var(--vr-gold);
39898
+ }
39899
+
39900
+ /* 2.11 \u2014 Scope/mention popup rows: gold-tinted hover + selected state.
39901
+ (Font-family/size/colour already set sans in Group 1 \xA71.8 \u2014 here we only add
39902
+ the hover/selected theming so the two passes don't fight.) */
39903
+ .vr-scope-menu button:hover {
39904
+ background: rgba(232, 193, 90, 0.14);
39905
+ box-shadow: inset 3px 0 0 var(--vr-gold);
39906
+ color: var(--vr-gold-bright);
39907
+ }
39908
+ .vr-scope-menu button:has(> b) {
39909
+ background: rgba(232, 193, 90, 0.18);
39910
+ box-shadow: inset 3px 0 0 var(--vr-gold-bright);
39911
+ color: var(--vr-gold-bright);
39912
+ }
39913
+
39914
+ /* 2.12 \u2014 Welcome/quickstart title: condensed --vr-display -> Anton poster. */
39915
+ .vr-qs-head h2 {
39916
+ font-family: var(--mg-display);
39917
+ font-weight: 400;
39918
+ font-size: 32px;
39919
+ letter-spacing: 0.02em;
39920
+ color: var(--vr-text);
39921
+ }
39922
+
39923
+ /* 2.13 \u2014 Welcome sub-copy + LOCAL badge. The 9.5px floor at 16386 keeps
39924
+ .vr-qs-local legible; here we only recolor to gold (no size restated, so we
39925
+ don't fight that !important). */
39926
+ .vr-qs-head p {
39927
+ font-family: var(--vr-hud-sans);
39928
+ font-size: 13px;
39929
+ color: var(--vr-muted);
39930
+ }
39931
+ .vr-qs-local {
39932
+ color: var(--vr-gold);
39933
+ font-weight: 660;
39934
+ }
39935
+
39936
+ /* 2.14 \u2014 Welcome hero cards: rounded flat -> gold clipped-frame collector card. */
39937
+ .vr-qs-card.is-hero {
39938
+ border-radius: 0;
39939
+ clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 14px 100%, 0 calc(100% - 14px));
39940
+ background: linear-gradient(160deg, var(--vr-gold), rgba(232, 193, 90, 0.35));
39941
+ padding: 2px;
39942
+ box-shadow: 0 14px 30px rgba(0, 0, 0, 0.6);
39943
+ }
39944
+ .vr-qs-card.is-hero .vr-qs-card-inner {
39945
+ border-radius: 0;
39946
+ clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));
39947
+ background:
39948
+ radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.4px) 0 0 / 8px 8px,
39949
+ rgba(9, 9, 11, 0.96);
39950
+ }
39951
+
39952
+ /* 2.15 \u2014 Welcome hero card title: Anton only on the title; body/labels go sans. */
39953
+ .vr-qs-card.is-hero .vr-qs-card-top strong {
39954
+ font-family: var(--mg-display);
39955
+ font-weight: 400;
39956
+ font-size: 15px;
39957
+ letter-spacing: 0.02em;
39958
+ text-transform: uppercase;
39959
+ color: var(--vr-text);
39960
+ }
39961
+ .vr-qs-card.is-hero .vr-qs-card em,
39962
+ .vr-qs-card.is-hero .vr-qs-type {
39963
+ font-family: var(--vr-hud-sans);
39964
+ color: var(--vr-muted);
39965
+ }
39966
+ .vr-qs-card.is-hero button {
39967
+ font-family: var(--vr-hud-sans);
39968
+ font-weight: 700;
39969
+ letter-spacing: 0.02em;
39970
+ text-transform: none;
39971
+ color: var(--vr-gold-bright);
39972
+ border-color: var(--vr-gold);
39973
+ }
39974
+
39975
+ /* ==========================================================================
39976
+ FINAL POLISH \u2014 drag ghost, active tab, low-quality chat cards
39977
+ ========================================================================== */
39978
+ /* Drag ghost: a clean floating card preview. It was a clone that KEPT the
39979
+ placed/hand card's position:absolute + translate(-50%,-50%) / rotation /
39980
+ negative margin, so it rendered as a corrupted half-card. Reset to a plain
39981
+ fixed, upright (slightly tilted) card sized to the source. */
39982
+ .vr-drag-ghost {
39983
+ position: fixed !important;
39984
+ margin: 0 !important;
39985
+ transform: rotate(-4deg) !important;
39986
+ transform-origin: top left;
39987
+ z-index: 100000 !important;
39988
+ pointer-events: none !important;
39989
+ opacity: 0.96;
39990
+ box-shadow: 0 24px 46px rgba(0, 0, 0, 0.78);
39991
+ transition: none !important;
39992
+ }
39993
+ .vr-drag-ghost .vr-tcard-unplace { display: none !important; }
39994
+ .vr-drag-ghost .vr-tcard-holofoil,
39995
+ .vr-drag-ghost .vr-tcard-glare { animation: none !important; opacity: 0.2 !important; }
39996
+
39997
+ /* Active tab: was dark #0a0b0e text on a near-transparent wash = an unreadable
39998
+ "black mark". Gold-bright text on a subtle wash + gold underline reads clean. */
39999
+ .vr-studio-tabs button.is-active {
40000
+ color: var(--vr-gold-bright) !important;
40001
+ background: rgba(244, 241, 232, 0.05);
40002
+ box-shadow: inset 0 -2px 0 var(--vr-gold), inset 0 0 0 1px rgba(232, 193, 90, 0.26);
40003
+ border-radius: 4px;
40004
+ }
40005
+
40006
+ /* ==========================================================================
40007
+ VERSION CARDS \u2014 richer "release card" (was small/plain crimson-inner).
40008
+ Gold ornate frame + comic notch + halftone + BIG Anton version number,
40009
+ matching the premium provider-card language. No full-art (releases have no
40010
+ art), so the version number IS the hero.
40011
+ ========================================================================== */
40012
+ .vr-vcard {
40013
+ border: 1.5px solid var(--vr-border-strong) !important;
40014
+ border-radius: 5px !important;
40015
+ clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px));
40016
+ background: rgba(9, 9, 11, 0.97) !important;
40017
+ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(232, 193, 90, 0.16) !important;
40018
+ overflow: hidden !important;
40019
+ }
40020
+ .vr-vcard::before {
40021
+ inset: 0 !important;
40022
+ border-radius: 0 !important;
40023
+ background-color: transparent !important;
40024
+ background-image:
40025
+ radial-gradient(rgba(232, 193, 90, 0.05) 1px, transparent 1.4px),
40026
+ radial-gradient(ellipse 78% 46% at 50% 0%, rgba(232, 193, 90, 0.07), transparent 60%) !important;
40027
+ background-size: 9px 9px, 100% 100% !important;
40028
+ background-repeat: repeat, no-repeat !important;
40029
+ opacity: 1 !important;
40030
+ }
40031
+ .vr-vcard:not(.is-current) { filter: none !important; opacity: 0.92; }
40032
+ .vr-vcard.is-current {
40033
+ border-color: var(--vr-gold) !important;
40034
+ border-left: 3px solid var(--vr-gold) !important;
40035
+ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.6), 0 0 22px rgba(232, 193, 90, 0.24), inset 0 0 0 1px rgba(232, 193, 90, 0.2) !important;
40036
+ }
40037
+ .vr-vcard-top b {
40038
+ font-family: var(--mg-display) !important;
40039
+ font-size: 26px !important;
40040
+ font-weight: 400 !important;
40041
+ color: var(--vr-gold-bright) !important;
40042
+ letter-spacing: 0.02em !important;
40043
+ }
40044
+ .vr-vcard.is-mini .vr-vcard-top b { font-size: 22px !important; }
40045
+ .vr-vcard-branch { color: var(--vr-muted) !important; }
40046
+ /* four gold corner ticks like the provider cards */
40047
+ .vr-vcard .vr-vcard-foot::after {
40048
+ content: '';
40049
+ position: absolute;
40050
+ left: 8px; bottom: 8px; right: 8px;
40051
+ height: 1px;
40052
+ background: linear-gradient(90deg, var(--vr-gold), transparent 70%);
40053
+ opacity: 0.5;
40054
+ }
40055
+
40056
+ /* ==========================================================================
40057
+ WELCOME FLIP-CARD BACK \u2014 flat manga halftone (was a gold spiral). Ink plate,
40058
+ gold halftone screentone, a soft gold top-glow, the fierce raven mark, gold
40059
+ FLIP label. Reads as a face-down manga card, not a casino spiral.
40060
+ ========================================================================== */
40061
+ .vr-qs-back,
40062
+ .vr-tcard.is-back:not(.has-art) {
40063
+ border: 1.5px solid var(--vr-border-strong) !important;
40064
+ border-radius: 5px !important;
40065
+ background:
40066
+ radial-gradient(rgba(232, 193, 90, 0.06) 1px, transparent 1.4px) 0 0 / 9px 9px,
40067
+ radial-gradient(ellipse 72% 52% at 50% 40%, rgba(232, 193, 90, 0.08), transparent 62%),
40068
+ #0a0a0d !important;
40069
+ }
40070
+ .vr-qs-back::after,
40071
+ .vr-tcard.is-back:not(.has-art)::after {
40072
+ content: '';
40073
+ position: absolute;
40074
+ inset: 5px;
40075
+ border: 1px solid rgba(232, 193, 90, 0.22);
40076
+ border-radius: 3px;
40077
+ pointer-events: none;
40078
+ }
40079
+ .vr-qs-back img,
40080
+ .vr-tcard.is-back:not(.has-art) .vr-tcard-backart img {
40081
+ width: 62px !important;
40082
+ height: 62px !important;
40083
+ opacity: 0.96 !important;
40084
+ filter: drop-shadow(0 0 10px rgba(232, 193, 90, 0.22));
40085
+ }
40086
+ .vr-qs-back b,
40087
+ .vr-tcard.is-back:not(.has-art) .vr-tcard-backname {
40088
+ color: var(--vr-gold-bright) !important;
40089
+ font-family: var(--vr-hud-mono) !important;
40090
+ letter-spacing: 0.16em !important;
40091
+ }
40092
+
40093
+ /* Region map: keep the grade medallion in the top-right corner like the binder,
40094
+ just scaled down to fit the small map cards so it stays tucked in the corner
40095
+ instead of covering the provider name. */
40096
+ .vr-studio-architecture.is-region-map .vr-arch-node.is-card .vr-tcard-hp.is-onart {
40097
+ top: 5px;
40098
+ right: 5px;
40099
+ bottom: auto;
40100
+ transform: none;
40101
+ width: 16px;
40102
+ height: 16px;
40103
+ }
40104
+ .vr-studio-architecture.is-region-map .vr-arch-node.is-card .vr-tcard-hp.is-onart i {
40105
+ font-size: 9px;
40106
+ }
40107
+ `;
40108
+ }
40109
+ });
40110
+
40111
+ // src/local-ui/static/appHtml.ts
40112
+ function escapeJsonForHtml(value) {
40113
+ return JSON.stringify(value).replace(/</g, "\\u003c");
40114
+ }
40115
+ function renderLocalUiHtml(state) {
40116
+ return `<!doctype html>
40117
+ <html lang="en">
40118
+ <head>
40119
+ <meta charset="utf-8" />
40120
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
40121
+ <meta name="theme-color" content="#0b0b0d" />
40122
+ <title>VibeRaven</title>
40123
+ <link rel="icon" type="image/png" href="/manga/favicon.png?v=manga1" />
40124
+ <link rel="shortcut icon" type="image/png" href="/manga/favicon.png?v=manga1" />
40125
+ <link rel="apple-touch-icon" href="/manga/favicon-180.png?v=manga1" />
40126
+ <link rel="preload" as="image" href="/report/assets/viberaven-mascot.png?v=raven-cards-2" />
40127
+ <style>${localUiCss}</style>
40128
+ </head>
40129
+ <body>
40130
+ <div id="vr-app" class="vr-app" aria-label="VibeRaven"></div>
40131
+ <script id="vr-state" type="application/json">${escapeJsonForHtml(state)}</script>
40132
+ <script src="/vendor/anime.umd.min.js"></script>
40133
+ <script>${localUiClientJs}</script>
40134
+ </body>
40135
+ </html>`;
40136
+ }
40137
+ var init_appHtml = __esm({
40138
+ "src/local-ui/static/appHtml.ts"() {
40139
+ "use strict";
40140
+ init_appClient();
40141
+ init_appCss();
40142
+ }
40143
+ });
40144
+
40145
+ // src/local-ui/server.ts
40146
+ function normalizeShellCommand(value) {
40147
+ return value.trim().replace(/\s+/g, " ");
40148
+ }
40149
+ function resolveApprovedShellCommand(command, requestedCliId) {
40150
+ const normalized = normalizeShellCommand(command);
40151
+ const candidateIds = requestedCliId ? [requestedCliId] : ["codex", "claude", "gemini"];
40152
+ for (const cliId of candidateIds) {
40153
+ const setup = CLI_AGENT_SETUP[cliId];
40154
+ const install = normalizeShellCommand(setup.installCommand);
40155
+ if (normalized === install) {
40156
+ return { cliId, step: "install", command: setup.installCommand };
40157
+ }
40158
+ const signIn = normalizeShellCommand(setup.signInCommand);
40159
+ if (normalized === signIn) {
40160
+ return { cliId, step: "sign-in", command: setup.signInCommand };
40161
+ }
40162
+ }
40163
+ return null;
40164
+ }
40165
+ async function connectProviderMcp(cwd, providerId) {
40166
+ const recipe = MCP_SERVER_RECIPES[providerId];
40167
+ if (!recipe) {
40168
+ return { ok: false, message: `No known MCP server for ${providerId} yet. Add one manually to .mcp.json.` };
40169
+ }
40170
+ const configPath = (0, import_node_path30.join)(cwd, ".mcp.json");
40171
+ let config = {};
40172
+ try {
40173
+ const raw = await (0, import_promises23.readFile)(configPath, "utf8");
40174
+ const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
40175
+ if (isRecord4(parsed)) config = parsed;
40176
+ } catch {
40177
+ }
40178
+ const servers = isRecord4(config.mcpServers) ? { ...config.mcpServers } : {};
40179
+ if (servers[providerId]) {
40180
+ return { ok: true, message: `${providerId} MCP server already configured in .mcp.json.`, path: ".mcp.json" };
40181
+ }
40182
+ servers[providerId] = recipe.entry;
40183
+ const next = { ...config, mcpServers: servers };
40184
+ await (0, import_promises23.writeFile)(configPath, `${JSON.stringify(next, null, 2)}
40185
+ `, "utf8");
40186
+ const suffix = recipe.note ? ` ${recipe.note}` : "";
40187
+ return { ok: true, message: `Added ${providerId} MCP server to .mcp.json.${suffix}`, path: ".mcp.json" };
40188
+ }
40189
+ async function defaultScanRunner(flags, positional) {
40190
+ const cli = await Promise.resolve().then(() => (init_cli(), cli_exports));
40191
+ return cli.runScanCommand(flags, positional);
40192
+ }
40193
+ async function readPackageJson(cwd) {
40194
+ try {
40195
+ const raw = await (0, import_promises23.readFile)((0, import_node_path30.join)(cwd, "package.json"), "utf8");
40196
+ const parsed = JSON.parse(raw.replace(/^\uFEFF/, ""));
40197
+ return isRecord4(parsed) ? parsed : {};
40198
+ } catch {
40199
+ return {};
40200
+ }
40201
+ }
40202
+ function dependencyNames(packageJson) {
40203
+ const names = /* @__PURE__ */ new Set();
40204
+ for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
40205
+ const value = packageJson[key];
40206
+ if (!isRecord4(value)) continue;
40207
+ Object.keys(value).forEach((name) => names.add(name.toLowerCase()));
40208
+ }
40209
+ return names;
40210
+ }
40211
+ async function listProjectFiles(cwd, maxFiles = 1800) {
40212
+ const skipped = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", "coverage", ".turbo", ".viberaven"]);
40213
+ const files = [];
40214
+ async function walk(dir, prefix = "") {
40215
+ if (files.length >= maxFiles) return;
40216
+ let entries;
40217
+ try {
40218
+ entries = await (0, import_promises23.readdir)(dir, { withFileTypes: true });
37686
40219
  } catch {
37687
40220
  return;
37688
40221
  }
@@ -38316,13 +40849,13 @@ function parseAgentChatRequest(value) {
38316
40849
  function taskKindExecutionBlock(taskKind) {
38317
40850
  switch (taskKind) {
38318
40851
  case "analyze":
38319
- return "Execution mode: analyze launch readiness. Read repo files and .viberaven artifacts; return concise findings with paths. Do not dump CLI install manuals or numbered manual runbooks.";
40852
+ return "Execution mode: analyze launch readiness. Inspect the real application code \u2014 auth, protected routes, middleware, database/RLS, migrations, webhooks, and env usage. Return a short prose summary plus up to 3 concrete blockers, each with the real code file path. Do not read, grep, or cite the .viberaven/ folder; those are stale internal artifacts.";
38320
40853
  case "plan":
38321
40854
  return "Execution mode: build an ordered fix plan from repo evidence. If the top blocker is repo-code, apply that fix in the project before answering. Do not reply with only manual instructions when code edits are possible.";
38322
40855
  case "fix":
38323
40856
  return "Execution mode: apply one minimal scoped repo fix IN CODE NOW. Read existing files, edit the project, save changes, then summarize. Forbidden: telling the user what to do manually when you can patch the repo yourself.";
38324
40857
  case "verify":
38325
- return "Execution mode: verify launch readiness. Prefer `npx -y viberaven --verify` or read .viberaven/gate-result.json and agent-tasklist.md.";
40858
+ return "Execution mode: verify launch readiness against the real code. Re-check the specific files you changed and run the project tests or build when available. Do not rely on .viberaven/ scan artifacts.";
38326
40859
  case "provider":
38327
40860
  return "Execution mode: provider proof or connect mission. Edit repo wiring when missing; separate dashboard-only steps from code changes; never claim live connection without proof.";
38328
40861
  case "diff":
@@ -38347,9 +40880,18 @@ function missionCardsContextBlock(state) {
38347
40880
  });
38348
40881
  return lines.length > 1 ? lines.join("\n") : void 0;
38349
40882
  }
40883
+ async function hasPriorReleases(cwd) {
40884
+ try {
40885
+ const tags = await runGit(cwd, ["tag", "--list"]);
40886
+ return Boolean(tags.trim());
40887
+ } catch {
40888
+ return false;
40889
+ }
40890
+ }
38350
40891
  async function buildAgentChatPrompt(request, cwd) {
38351
40892
  const state = await readState(cwd);
38352
40893
  const executionBlock = taskKindExecutionBlock(request.taskKind);
40894
+ const priorReleases = await hasPriorReleases(cwd);
38353
40895
  const cardsBlock = missionCardsContextBlock(state);
38354
40896
  const providerState2 = request.provider?.id ? state.providers.find((provider2) => provider2.id === request.provider?.id) : state.providers.find((provider2) => provider2.name === request.provider?.name);
38355
40897
  const mcp = providerState2?.mcp;
@@ -38367,6 +40909,7 @@ async function buildAgentChatPrompt(request, cwd) {
38367
40909
  releaseDiffBlock,
38368
40910
  state.gate?.label ? `Project status: ${state.gate.label} (${state.gate.status ?? "unknown"})` : void 0,
38369
40911
  state.missionControl?.mainBlocker ? `Main blocker: ${state.missionControl.mainBlocker}` : void 0,
40912
+ priorReleases ? "Deployment state: this project already has release tags, so it may already be in production. When asked about production readiness, prioritize what changed since the last release and the risk in the NEXT change over first-launch blockers." : void 0,
38370
40913
  executionBlock,
38371
40914
  cardsBlock,
38372
40915
  "",
@@ -38378,9 +40921,11 @@ async function buildAgentChatPrompt(request, cwd) {
38378
40921
  "- Return only the final user-facing answer. Do not echo this prompt, session metadata, logs, or command transcripts.",
38379
40922
  "- For fix/plan/provider repo gaps: edit files in this project before answering. Manual dashboard steps are only for gaps that cannot be solved in repo code.",
38380
40923
  "- Never paste long VibeRaven operator manuals, heal loops, or PowerShell command blocks unless the user explicitly asked how to run verify.",
40924
+ "- Do not read, grep, cite, or paste the contents of the .viberaven/ folder. Those are stale internal scan artifacts, not source of truth. Reason from the real application code (auth, routes, middleware, migrations, webhooks, env usage).",
40925
+ "- Keep the whole answer under ~15 lines. Never paste file contents, grep output, JSON, or command transcripts; summarize in prose and short bullets.",
38381
40926
  "",
38382
40927
  "Response format (markdown):",
38383
- "1. **Findings** \u2014 bullets with file paths or artifact names",
40928
+ "1. **Findings** \u2014 up to 3 bullets, each naming the real code file and the risk",
38384
40929
  "2. **Changes made** \u2014 what you edited, or say none only if the blocker is dashboard-only",
38385
40930
  '3. **Files touched** \u2014 list paths (or "none")',
38386
40931
  "4. **Next step** \u2014 one verify action or one dashboard action (do not paste raw npx commands; the UI renders Verify buttons)",
@@ -38786,7 +41331,7 @@ async function sendReportArtifact(cwd, response) {
38786
41331
  <body>
38787
41332
  <main>
38788
41333
  <div class="brand">
38789
- <img src="/report/assets/viberaven-agent-icon.png" alt="" />
41334
+ <img src="/report/assets/viberaven-mascot.png" alt="" />
38790
41335
  <div>
38791
41336
  <h1>Report preview is ready</h1>
38792
41337
  <p>${projectName} does not have a generated report artifact yet. The launch screen still gives you the provider map, agent handoff, and verification preview.</p>
@@ -38815,6 +41360,30 @@ async function sendExtensionIcon(cwd, response) {
38815
41360
  sendText(response, 404, "Icon not found");
38816
41361
  }
38817
41362
  }
41363
+ async function sendMangaAsset(pathname, response) {
41364
+ const name = decodeURIComponent(pathname.replace(/^\/manga\//, ""));
41365
+ if (name.includes("..") || !/^(cards\/)?[a-z0-9._-]+\.(png|webp|svg|jpg|jpeg)$/i.test(name)) {
41366
+ sendText(response, 404, "Asset not found");
41367
+ return;
41368
+ }
41369
+ const candidates = [
41370
+ (0, import_node_path30.join)(__dirname, "manga", name),
41371
+ // installed + dev: <pkg>/dist/manga
41372
+ (0, import_node_path30.join)(__dirname, "..", "assets", "manga", name),
41373
+ // fallback: <pkg>/assets/manga (source)
41374
+ (0, import_node_path30.join)(__dirname, "..", "..", "assets", "manga", name)
41375
+ ];
41376
+ for (const candidate of candidates) {
41377
+ try {
41378
+ const body = await (0, import_promises23.readFile)(candidate);
41379
+ response.setHeader("cache-control", "no-cache");
41380
+ sendBinary(response, 200, contentTypeForAsset(candidate), body);
41381
+ return;
41382
+ } catch {
41383
+ }
41384
+ }
41385
+ sendText(response, 404, "Asset not found");
41386
+ }
38818
41387
  function contentTypeForAsset(pathname) {
38819
41388
  switch ((0, import_node_path30.extname)(pathname).toLowerCase()) {
38820
41389
  case ".svg":
@@ -38866,7 +41435,12 @@ async function handleRequest(request, response, options) {
38866
41435
  }
38867
41436
  if (request.method === "GET" && url.pathname === "/") {
38868
41437
  const state = await readState(options.cwd);
38869
- response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
41438
+ response.writeHead(200, {
41439
+ "content-type": "text/html; charset=utf-8",
41440
+ "cache-control": "no-store, no-cache, must-revalidate",
41441
+ pragma: "no-cache",
41442
+ expires: "0"
41443
+ });
38870
41444
  response.end(renderLocalUiHtml(state));
38871
41445
  return;
38872
41446
  }
@@ -39199,6 +41773,27 @@ async function handleRequest(request, response, options) {
39199
41773
  await sendBundledReportAsset(url.pathname, response);
39200
41774
  return;
39201
41775
  }
41776
+ if (request.method === "GET" && url.pathname.startsWith("/manga/")) {
41777
+ await sendMangaAsset(url.pathname, response);
41778
+ return;
41779
+ }
41780
+ if (request.method === "GET" && url.pathname === "/vendor/anime.umd.min.js") {
41781
+ for (const candidate of [
41782
+ (0, import_node_path30.join)(__dirname, "vendor", "anime.umd.min.js"),
41783
+ (0, import_node_path30.join)(__dirname, "..", "assets", "vendor", "anime.umd.min.js"),
41784
+ (0, import_node_path30.join)(__dirname, "..", "..", "assets", "vendor", "anime.umd.min.js")
41785
+ ]) {
41786
+ try {
41787
+ const body = await (0, import_promises23.readFile)(candidate);
41788
+ response.setHeader("cache-control", "public, max-age=604800, immutable");
41789
+ sendBinary(response, 200, "text/javascript; charset=utf-8", body);
41790
+ return;
41791
+ } catch {
41792
+ }
41793
+ }
41794
+ sendText(response, 404, "Asset not found");
41795
+ return;
41796
+ }
39202
41797
  if (request.method === "POST" && url.pathname === "/api/scan") {
39203
41798
  await options.scanRunner({ "agent-mode": true }, [options.cwd]);
39204
41799
  sendJson(response, await readState(options.cwd));