vibetunnel/web/src/client/components/vibe-logo.ts
Mario Zechner 7c92eb5bdb Implement black theme, mobile improvements, and fit-to-width toggle
- Apply consistent black theme across all components with colored borders
- Add animated VibeTunnel logo with rainbow scrolling gradient
- Implement comprehensive mobile input controls with Ctrl+Alpha overlay
- Add fit-to-width toggle button in session view header with scroll preservation
- Enhance mobile experience with proper viewport handling and keyboard positioning
- Update button styling to use black backgrounds with colored borders throughout
- Resize scroll-to-bottom button for better mobile accessibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-18 10:16:04 +02:00

111 lines
3.1 KiB
TypeScript

import { LitElement, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
@customElement('vibe-logo')
export class VibeLogo extends LitElement {
// Disable shadow DOM to use Tailwind
createRenderRoot() {
return this;
}
@state() private frame = 0;
private animationInterval: number | null = null;
connectedCallback() {
super.connectedCallback();
this.startAnimation();
}
disconnectedCallback() {
super.disconnectedCallback();
this.stopAnimation();
}
private startAnimation() {
this.animationInterval = window.setInterval(() => {
this.frame = (this.frame + 1) % 12; // 12 frames for smooth animation
this.requestUpdate();
}, 200); // Change frame every 200ms
}
private stopAnimation() {
if (this.animationInterval) {
clearInterval(this.animationInterval);
this.animationInterval = null;
}
}
private getLogoFrame(): string {
const frames = [
'░░░▒▒▓▓█ VibeTunnel █▓▓▒▒░░░',
'░░▒▒▓▓█░ VibeTunnel ░█▓▓▒▒░░',
'░▒▒▓▓█░░ VibeTunnel ░░█▓▓▒▒░',
'▒▒▓▓█░░░ VibeTunnel ░░░█▓▓▒▒',
'▒▓▓█░░░░ VibeTunnel ░░░░█▓▓▒',
'▓▓█░░░░░ VibeTunnel ░░░░░█▓▓',
'▓█░░░░░░ VibeTunnel ░░░░░░█▓',
'█░░░░░░░ VibeTunnel ░░░░░░░█',
'░░░░░░░█ VibeTunnel █░░░░░░░',
'░░░░░░█▓ VibeTunnel ▓█░░░░░░',
'░░░░░█▓▓ VibeTunnel ▓▓█░░░░░',
'░░░░█▓▓▒ VibeTunnel ▒▓▓█░░░░',
];
return frames[this.frame];
}
private getRainbowColors() {
return [
'#ff0000',
'#ff4500',
'#ff8c00',
'#ffd700',
'#9acd32',
'#00ff00',
'#00ffff',
'#0080ff',
'#8000ff',
'#ff00ff',
'#ff1493',
'#ff69b4',
'#ffc0cb',
'#ffb6c1',
'#ffa0b4',
'#ff8fa3',
];
}
render() {
const frame = this.getLogoFrame();
const colors = this.getRainbowColors();
// Parse the frame to apply rainbow colors
const parts = frame.split(' VibeTunnel ');
const leftPart = parts[0];
const rightPart = parts[1];
const coloredLeft = leftPart
.split('')
.map((char, i) =>
char === ' ' ? ' ' : html`<span style="color: ${colors[i % colors.length]};">${char}</span>`
);
const coloredRight = rightPart
.split('')
.map((char, i) =>
char === ' '
? ' '
: html`<span style="color: ${colors[(leftPart.length - 1 - i) % colors.length]};"
>${char}</span
>`
);
return html`
<div class="font-mono text-sm select-none leading-tight text-center">
<pre
class="whitespace-pre"
>${coloredLeft} <span class="text-vs-user">VibeTunnel</span> ${coloredRight}</pre>
</div>
`;
}
}