style(design): redesign controlled Chrome indicator

Replace crude green border + label with polished indicator:
- 2px shimmer gradient at top edge (green→cyan→green, 3s loop)
- Floating pill bottom-right with frosted glass bg, fades to 25%
  opacity after 4s so it doesn't compete with page content
- prefers-reduced-motion disables shimmer animation
- Much more subtle — looks like a developer tool, not broken CSS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-03-21 12:47:40 -07:00
parent c35db6dfc8
commit 6e9ad62805
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
1 changed files with 51 additions and 21 deletions

View File

@ -141,32 +141,62 @@ export class BrowserManager {
}; };
this.context = await this.browser.newContext(contextOptions); this.context = await this.browser.newContext(contextOptions);
// Inject visual indicator on every page — thin green border so user // Inject visual indicator — subtle top-edge gradient + floating pill
// knows this window is controlled by gstack // so the user always knows which Chrome window gstack controls
await this.context.addInitScript(() => { await this.context.addInitScript(() => {
const injectIndicator = () => { const injectIndicator = () => {
if (document.getElementById('gstack-controlled-indicator')) return; if (document.getElementById('gstack-ctrl')) return;
// Green border around viewport
const border = document.createElement('div'); // Thin gradient line at the very top of the viewport
border.id = 'gstack-controlled-indicator'; const topLine = document.createElement('div');
border.style.cssText = ` topLine.id = 'gstack-ctrl';
position: fixed; top: 0; left: 0; right: 0; bottom: 0; topLine.style.cssText = `
border: 2px solid rgba(74, 222, 128, 0.6); position: fixed; top: 0; left: 0; right: 0; height: 2px;
background: linear-gradient(90deg, #4ade80, #22d3ee, #4ade80);
background-size: 200% 100%;
animation: gstack-shimmer 3s linear infinite;
pointer-events: none; z-index: 2147483647; pointer-events: none; z-index: 2147483647;
opacity: 0.8;
`; `;
// Small label in top-left
const label = document.createElement('div'); // Floating pill — bottom-right, fades to subtle
label.style.cssText = ` const pill = document.createElement('div');
position: fixed; top: 0; left: 0; z-index: 2147483647; pill.id = 'gstack-pill';
background: rgba(74, 222, 128, 0.9); color: #000; pill.style.cssText = `
font: 600 10px -apple-system, sans-serif; position: fixed; bottom: 12px; right: 12px;
padding: 2px 8px; pointer-events: none; z-index: 2147483647; pointer-events: none;
border-bottom-right-radius: 4px; display: flex; align-items: center; gap: 5px;
letter-spacing: 0.05em; padding: 4px 10px;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(74, 222, 128, 0.25);
border-radius: 100px;
font: 500 10px -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
color: rgba(255, 255, 255, 0.7);
letter-spacing: 0.03em;
transition: opacity 0.5s ease;
opacity: 1;
`; `;
label.textContent = 'gstack'; pill.innerHTML = '<span style="width:5px;height:5px;border-radius:50%;background:#4ade80;box-shadow:0 0 4px rgba(74,222,128,0.5);flex-shrink:0;"></span>gstack';
document.documentElement.appendChild(border);
document.documentElement.appendChild(label); // Keyframe for shimmer animation
const style = document.createElement('style');
style.textContent = `
@keyframes gstack-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
#gstack-ctrl { animation: none !important; }
}
`;
document.documentElement.appendChild(style);
document.documentElement.appendChild(topLine);
document.documentElement.appendChild(pill);
// Fade pill to subtle after 4s
setTimeout(() => { pill.style.opacity = '0.25'; }, 4000);
}; };
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', injectIndicator); document.addEventListener('DOMContentLoaded', injectIndicator);