feat: refactor favicon and logo assets

This commit is contained in:
Bohdan Triapitsyn
2025-12-21 22:59:06 +02:00
parent 7699aab123
commit 4ab4616dea
31 changed files with 1088 additions and 195 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+58 -7
View File
@@ -1,9 +1,60 @@
<svg width="24" height="24" viewBox="0 0 70 70" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(35, 35) scale(0.95) translate(-35, -35)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M0 13H35V58H0V13ZM26.25 22.1957H8.75V48.701H26.25V22.1957Z"
fill="#808080"/>
<path d="M43.75 13H70V22.1957H52.5V48.701H70V57.8967H43.75V13Z"
fill="#808080"/>
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bgGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#303030"/>
<stop offset="100%" stop-color="#141414"/>
</linearGradient>
</defs>
<!-- Background -->
<rect x="0" y="0" width="128" height="128" rx="26" ry="26" fill="url(#bgGradient)"/>
<!-- OpenChamber logo centered -->
<g transform="translate(64, 64) scale(1)">
<!-- Left face -->
<path d="M0 0 L-41.568 -24 L-41.568 24 L0 48 Z" fill="white" fill-opacity="0.15" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
<!-- Left face grid cells -->
<path d="M0 0 L-10.392 -6 L-10.392 6 L0 12 Z" fill="white" fill-opacity="0.07"/>
<path d="M-10.392 -6 L-20.784 -12 L-20.784 0 L-10.392 6 Z" fill="white" fill-opacity="0.16"/>
<path d="M-20.784 -12 L-31.176 -18 L-31.176 -6 L-20.784 0 Z" fill="white" fill-opacity="0.05"/>
<path d="M-31.176 -18 L-41.568 -24 L-41.568 -12 L-31.176 -6 Z" fill="white" fill-opacity="0.19"/>
<path d="M0 12 L-10.392 6 L-10.392 18 L0 24 Z" fill="white" fill-opacity="0.12"/>
<path d="M-10.392 6 L-20.784 0 L-20.784 12 L-10.392 18 Z" fill="white" fill-opacity="0.04"/>
<path d="M-20.784 0 L-31.176 -6 L-31.176 6 L-20.784 12 Z" fill="white" fill-opacity="0.18"/>
<path d="M-31.176 -6 L-41.568 -12 L-41.568 0 L-31.176 6 Z" fill="white" fill-opacity="0.09"/>
<path d="M0 24 L-10.392 18 L-10.392 30 L0 36 Z" fill="white" fill-opacity="0.14"/>
<path d="M-10.392 18 L-20.784 12 L-20.784 24 L-10.392 30 Z" fill="white" fill-opacity="0.11"/>
<path d="M-20.784 12 L-31.176 6 L-31.176 18 L-20.784 24 Z" fill="white" fill-opacity="0.16"/>
<path d="M-31.176 6 L-41.568 0 L-41.568 12 L-31.176 18 Z" fill="white" fill-opacity="0.05"/>
<path d="M0 36 L-10.392 30 L-10.392 42 L0 48 Z" fill="white" fill-opacity="0.19"/>
<path d="M-10.392 30 L-20.784 24 L-20.784 36 L-10.392 42 Z" fill="white" fill-opacity="0.07"/>
<path d="M-20.784 24 L-31.176 18 L-31.176 30 L-20.784 36 Z" fill="white" fill-opacity="0.12"/>
<path d="M-31.176 18 L-41.568 12 L-41.568 24 L-31.176 30 Z" fill="white" fill-opacity="0.04"/>
<!-- Right face -->
<path d="M0 0 L41.568 -24 L41.568 24 L0 48 Z" fill="white" fill-opacity="0.15" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
<!-- Right face grid cells -->
<path d="M0 0 L10.392 -6 L10.392 6 L0 12 Z" fill="white" fill-opacity="0.11"/>
<path d="M10.392 -6 L20.784 -12 L20.784 0 L10.392 6 Z" fill="white" fill-opacity="0.05"/>
<path d="M20.784 -12 L31.176 -18 L31.176 -6 L20.784 0 Z" fill="white" fill-opacity="0.16"/>
<path d="M31.176 -18 L41.568 -24 L41.568 -12 L31.176 -6 Z" fill="white" fill-opacity="0.09"/>
<path d="M0 12 L10.392 6 L10.392 18 L0 24 Z" fill="white" fill-opacity="0.18"/>
<path d="M10.392 6 L20.784 0 L20.784 12 L10.392 18 Z" fill="white" fill-opacity="0.12"/>
<path d="M20.784 0 L31.176 -6 L31.176 6 L20.784 12 Z" fill="white" fill-opacity="0.04"/>
<path d="M31.176 -6 L41.568 -12 L41.568 0 L31.176 6 Z" fill="white" fill-opacity="0.14"/>
<path d="M0 24 L10.392 18 L10.392 30 L0 36 Z" fill="white" fill-opacity="0.07"/>
<path d="M10.392 18 L20.784 12 L20.784 24 L10.392 30 Z" fill="white" fill-opacity="0.19"/>
<path d="M20.784 12 L31.176 6 L31.176 18 L20.784 24 Z" fill="white" fill-opacity="0.11"/>
<path d="M31.176 6 L41.568 0 L41.568 12 L31.176 18 Z" fill="white" fill-opacity="0.05"/>
<path d="M0 36 L10.392 30 L10.392 42 L0 48 Z" fill="white" fill-opacity="0.16"/>
<path d="M10.392 30 L20.784 24 L20.784 36 L10.392 42 Z" fill="white" fill-opacity="0.09"/>
<path d="M20.784 24 L31.176 18 L31.176 30 L20.784 36 Z" fill="white" fill-opacity="0.14"/>
<path d="M31.176 18 L41.568 12 L41.568 24 L31.176 30 Z" fill="white" fill-opacity="0.07"/>
<!-- Top face - open -->
<path d="M0 -48 L-41.568 -24 L0 0 L41.568 -24 Z" fill="none" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
<!-- OpenCode logo on top face -->
<g transform="matrix(0.866, 0.5, -0.866, 0.5, 0, -24) scale(0.75)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M-16 -20 L16 -20 L16 20 L-16 20 Z M-8 -12 L-8 12 L8 12 L8 -12 Z" fill="white"/>
<path d="M-8 -4 L8 -4 L8 12 L-8 12 Z" fill="white" fill-opacity="0.4"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 411 B

After

Width:  |  Height:  |  Size: 4.3 KiB

+11 -6
View File
@@ -1,6 +1,7 @@
import React from 'react';
import { useChatStore } from './stores/chatStore';
import { useNavigation } from './hooks/useNavigation';
import { OpenChamberLogo } from './components/OpenChamberLogo';
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
@@ -11,9 +12,18 @@ function ConnectionStatusBanner({ status, error, onRetry }: {
}) {
if (status === 'connected') return null;
// Show full-screen logo for connecting state
if (status === 'connecting') {
return (
<div className="flex flex-col items-center justify-center h-full gap-4">
<OpenChamberLogo width={80} height={80} isAnimated />
</div>
);
}
const messages: Record<ConnectionStatus, string> = {
disconnected: 'Not connected to OpenCode API',
connecting: 'Connecting...',
connecting: '',
connected: '',
error: error || 'Connection error',
};
@@ -22,9 +32,6 @@ function ConnectionStatusBanner({ status, error, onRetry }: {
<div className={`flex items-center justify-center gap-2 px-4 py-2 text-sm border-b ${
status === 'error' ? 'bg-destructive/10 text-destructive' : 'bg-muted text-muted-foreground'
}`}>
{status === 'connecting' && (
<span className="inline-block w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
)}
<span>{messages[status]}</span>
{(status === 'disconnected' || status === 'error') && (
<button onClick={onRetry} className="px-2 py-0.5 text-xs rounded bg-primary text-primary-foreground hover:bg-primary/90">
@@ -265,8 +272,6 @@ export function VSCodeApp() {
<div className="flex-1 min-h-0">
{currentView === 'sessions' ? (
<SessionsList />
) : currentView === 'settings' ? (
<SettingsView />
) : (
<ChatPanel />
)}
@@ -0,0 +1,192 @@
import React, { useMemo } from 'react';
interface OpenChamberLogoProps {
className?: string;
width?: number;
height?: number;
isAnimated?: boolean;
}
// Generate grid cells for a face (4x4 grid)
const generateFaceGrid = (
topLeft: { x: number; y: number },
topRight: { x: number; y: number },
bottomRight: { x: number; y: number },
bottomLeft: { x: number; y: number },
gridSize: number = 4
) => {
const cells: Array<{ path: string; row: number; col: number }> = [];
for (let row = 0; row < gridSize; row++) {
for (let col = 0; col < gridSize; col++) {
const t1 = col / gridSize;
const t2 = (col + 1) / gridSize;
const s1 = row / gridSize;
const s2 = (row + 1) / gridSize;
const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
const bilinear = (tl: number, tr: number, br: number, bl: number, t: number, s: number) => {
const top = lerp(tl, tr, t);
const bottom = lerp(bl, br, t);
return lerp(top, bottom, s);
};
const p1 = {
x: bilinear(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x, t1, s1),
y: bilinear(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y, t1, s1),
};
const p2 = {
x: bilinear(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x, t2, s1),
y: bilinear(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y, t2, s1),
};
const p3 = {
x: bilinear(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x, t2, s2),
y: bilinear(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y, t2, s2),
};
const p4 = {
x: bilinear(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x, t1, s2),
y: bilinear(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y, t1, s2),
};
cells.push({
path: `M${p1.x} ${p1.y} L${p2.x} ${p2.y} L${p3.x} ${p3.y} L${p4.x} ${p4.y} Z`,
row,
col,
});
}
}
return cells;
};
export const OpenChamberLogo: React.FC<OpenChamberLogoProps> = ({
className = '',
width = 70,
height = 70,
isAnimated = false,
}) => {
// VSCode uses CSS variables for theming
const strokeColor = 'var(--vscode-foreground)';
const fillColor = 'var(--vscode-foreground)';
const logoFillColor = 'var(--vscode-foreground)';
const cellHighlightColor = 'var(--vscode-foreground)';
// Isometric cube geometry
const edge = 48;
const cos30 = 0.866;
const sin30 = 0.5;
const centerY = 50;
const top = { x: 50, y: centerY - edge };
const left = { x: 50 - edge * cos30, y: centerY - edge * sin30 };
const right = { x: 50 + edge * cos30, y: centerY - edge * sin30 };
const center = { x: 50, y: centerY };
const bottomLeft = { x: 50 - edge * cos30, y: centerY + edge * sin30 };
const bottomRight = { x: 50 + edge * cos30, y: centerY + edge * sin30 };
const bottom = { x: 50, y: centerY + edge };
const topFaceCenterY = (top.y + left.y + center.y + right.y) / 4;
const isoMatrix = `matrix(0.866, 0.5, -0.866, 0.5, 50, ${topFaceCenterY})`;
const leftFaceCells = generateFaceGrid(left, center, bottom, bottomLeft);
const rightFaceCells = generateFaceGrid(center, right, bottomRight, bottom);
const cellOpacities = useMemo(() => {
const opacities: number[] = [];
for (let i = 0; i < 32; i++) {
opacities.push(0.1 + Math.random() * 0.5);
}
return opacities;
}, []);
return (
<svg
width={width}
height={height}
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
role="img"
aria-label="OpenChamber logo"
>
{/* Left face */}
<path
d={`M${center.x} ${center.y} L${left.x} ${left.y} L${bottomLeft.x} ${bottomLeft.y} L${bottom.x} ${bottom.y} Z`}
fill={fillColor}
fillOpacity="0.15"
stroke={strokeColor}
strokeWidth="2"
strokeLinejoin="round"
/>
{/* Left face grid cells */}
{leftFaceCells.map((cell, i) => (
<path
key={`left-${i}`}
d={cell.path}
fill={cellHighlightColor}
fillOpacity={0.35 * cellOpacities[i]}
/>
))}
{/* Right face */}
<path
d={`M${center.x} ${center.y} L${right.x} ${right.y} L${bottomRight.x} ${bottomRight.y} L${bottom.x} ${bottom.y} Z`}
fill={fillColor}
fillOpacity="0.15"
stroke={strokeColor}
strokeWidth="2"
strokeLinejoin="round"
/>
{/* Right face grid cells */}
{rightFaceCells.map((cell, i) => (
<path
key={`right-${i}`}
d={cell.path}
fill={cellHighlightColor}
fillOpacity={0.35 * cellOpacities[i + 16]}
/>
))}
{/* Top face - open */}
<path
d={`M${top.x} ${top.y} L${left.x} ${left.y} L${center.x} ${center.y} L${right.x} ${right.y} Z`}
fill="none"
stroke={strokeColor}
strokeWidth="2"
strokeLinejoin="round"
/>
{/* OpenCode logo on top face */}
<g opacity={isAnimated ? undefined : 1}>
{isAnimated && (
<animate
attributeName="opacity"
values="0.4;1;0.4"
dur="3s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.6 1; 0.4 0 0.6 1"
/>
)}
<g transform={`${isoMatrix} scale(0.75)`}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M-16 -20 L16 -20 L16 20 L-16 20 Z M-8 -12 L-8 12 L8 12 L8 -12 Z"
fill={logoFillColor}
/>
<path
d="M-8 -4 L8 -4 L8 12 L-8 12 Z"
fill={logoFillColor}
fillOpacity="0.4"
/>
</g>
</g>
</svg>
);
};
export default OpenChamberLogo;