Add CI and Release workflows; enhance loading skeletons and splash screen
- Introduced CI workflow for automated testing and validation on push and pull request events. - Added Release workflow for packaging and distributing the application on version tag pushes. - Enhanced loading skeletons in ChatHistory and ProjectsGrid components for improved visual feedback. - Updated splash screen with new animations and styles for better user experience. - Refined CSS variables for skeleton loading states to ensure consistency across themes.
This commit is contained in:
parent
5a0e4c474f
commit
e8f65cf492
7 changed files with 286 additions and 73 deletions
45
.github/workflows/ci.yml
vendored
Normal file
45
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, windows-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10.25.0
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --no-frozen-lockfile
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: pnpm typecheck
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: pnpm lint
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
48
.github/workflows/release.yml
vendored
Normal file
48
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10.25.0
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --no-frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build app
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Package macOS
|
||||||
|
env:
|
||||||
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
run: pnpm dist:mac
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-macos
|
||||||
|
path: release/**
|
||||||
|
if-no-files-found: error
|
||||||
|
|
@ -1,20 +1,40 @@
|
||||||
/**
|
/**
|
||||||
* Loading skeleton for ChatHistory while conversation is loading.
|
* Loading skeleton for ChatHistory while conversation is loading.
|
||||||
|
* Industrial shimmer with organic line widths — no generic pulse.
|
||||||
*/
|
*/
|
||||||
export const ChatHistoryLoadingState = (): JSX.Element => {
|
export const ChatHistoryLoadingState = (): JSX.Element => {
|
||||||
|
const rows = [
|
||||||
|
{ user: ['85%', '60%'], ai: ['92%', '70%', '82%', '45%'] },
|
||||||
|
{ user: ['75%', '92%', '40%'], ai: ['88%', '65%', '78%'] },
|
||||||
|
{ user: ['95%', '55%'], ai: ['72%', '85%', '60%', '92%', '35%'] },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 items-center justify-center overflow-hidden bg-[#141416]">
|
<div className="flex flex-1 items-center justify-center overflow-hidden bg-surface">
|
||||||
<div className="w-full max-w-5xl space-y-8 px-6">
|
<div className="w-full max-w-5xl space-y-8 px-6">
|
||||||
{/* Loading skeleton */}
|
{rows.map((row, i) => (
|
||||||
{[1, 2, 3].map((i) => (
|
<div key={i} className="space-y-6">
|
||||||
<div key={i} className="animate-pulse space-y-6">
|
{/* User message skeleton — right aligned */}
|
||||||
{/* User message skeleton - right aligned */}
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<div className="h-16 w-2/3 rounded-2xl rounded-br-sm border border-white/5 bg-[#27272A]/50" />
|
<div className="w-2/3 space-y-2">
|
||||||
|
{row.user.map((width, j) => (
|
||||||
|
<div
|
||||||
|
key={j}
|
||||||
|
className="skeleton-shimmer ml-auto h-3 rounded-sm"
|
||||||
|
style={{ width, backgroundColor: 'var(--skeleton-base)' }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* AI response skeleton - left aligned with border accent */}
|
{/* AI response skeleton — left aligned with border accent */}
|
||||||
<div className="border-l-2 border-white/5 pl-3">
|
<div className="space-y-2.5 border-l-2 border-border pl-3">
|
||||||
<div className="h-24 w-full rounded-lg bg-[#27272A]/30" />
|
{row.ai.map((width, j) => (
|
||||||
|
<div
|
||||||
|
key={j}
|
||||||
|
className="skeleton-shimmer h-3 rounded-sm"
|
||||||
|
style={{ width, backgroundColor: 'var(--skeleton-base)' }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ const CommandSearch = ({ value, onChange }: Readonly<CommandSearchProps>): React
|
||||||
<div className="relative mx-auto w-full max-w-xl">
|
<div className="relative mx-auto w-full max-w-xl">
|
||||||
{/* Search container with glow effect on focus */}
|
{/* Search container with glow effect on focus */}
|
||||||
<div
|
<div
|
||||||
className={`relative flex items-center gap-3 rounded-lg border bg-surface-raised px-4 py-3 transition-all duration-200 ${
|
className={`relative flex items-center gap-3 rounded-sm border bg-surface-raised px-4 py-3 transition-all duration-200 ${
|
||||||
isFocused
|
isFocused
|
||||||
? 'border-zinc-500 shadow-[0_0_20px_rgba(255,255,255,0.04)] ring-1 ring-zinc-600/30'
|
? 'border-zinc-500 shadow-[0_0_20px_rgba(255,255,255,0.04)] ring-1 ring-zinc-600/30'
|
||||||
: 'border-border hover:border-zinc-600'
|
: 'border-border hover:border-zinc-600'
|
||||||
|
|
@ -154,14 +154,14 @@ const RepositoryCard = ({
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`group relative flex min-h-[120px] flex-col overflow-hidden rounded-xl border p-4 text-left transition-all duration-300 ${
|
className={`group relative flex min-h-[120px] flex-col overflow-hidden rounded-sm border p-4 text-left transition-all duration-300 ${
|
||||||
isHighlighted
|
isHighlighted
|
||||||
? 'border-border-emphasis bg-surface-raised'
|
? 'border-border-emphasis bg-surface-raised'
|
||||||
: 'bg-surface/50 border-border hover:border-border-emphasis hover:bg-surface-raised'
|
: 'bg-surface/50 border-border hover:border-border-emphasis hover:bg-surface-raised'
|
||||||
} `}
|
} `}
|
||||||
>
|
>
|
||||||
{/* Icon with subtle border */}
|
{/* Icon with subtle border */}
|
||||||
<div className="mb-3 flex size-8 items-center justify-center rounded-lg border border-border bg-surface-overlay transition-colors duration-300 group-hover:border-border-emphasis">
|
<div className="mb-3 flex size-8 items-center justify-center rounded-sm border border-border bg-surface-overlay transition-colors duration-300 group-hover:border-border-emphasis">
|
||||||
<FolderGit2 className="size-4 text-text-secondary transition-colors group-hover:text-text" />
|
<FolderGit2 className="size-4 text-text-secondary transition-colors group-hover:text-text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -232,11 +232,11 @@ const NewProjectCard = (): React.JSX.Element => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="hover:bg-surface/30 group relative flex min-h-[120px] flex-col items-center justify-center rounded-xl border border-dashed border-border bg-transparent p-4 transition-all duration-300 hover:border-border-emphasis"
|
className="hover:bg-surface/30 group relative flex min-h-[120px] flex-col items-center justify-center rounded-sm border border-dashed border-border bg-transparent p-4 transition-all duration-300 hover:border-border-emphasis"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
title="Select a project folder"
|
title="Select a project folder"
|
||||||
>
|
>
|
||||||
<div className="mb-2 flex size-8 items-center justify-center rounded-lg border border-dashed border-border transition-colors duration-300 group-hover:border-border-emphasis">
|
<div className="mb-2 flex size-8 items-center justify-center rounded-sm border border-dashed border-border transition-colors duration-300 group-hover:border-border-emphasis">
|
||||||
<FolderOpen className="size-4 text-text-muted transition-colors group-hover:text-text-secondary" />
|
<FolderOpen className="size-4 text-text-muted transition-colors group-hover:text-text-secondary" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-text-muted transition-colors group-hover:text-text-secondary">
|
<span className="text-xs text-text-muted transition-colors group-hover:text-text-secondary">
|
||||||
|
|
@ -295,24 +295,52 @@ const ProjectsGrid = ({
|
||||||
}, [repositoryGroups, searchQuery, maxProjects]);
|
}, [repositoryGroups, searchQuery, maxProjects]);
|
||||||
|
|
||||||
if (repositoryGroupsLoading) {
|
if (repositoryGroupsLoading) {
|
||||||
|
// Organic widths per card — no repeating stamp
|
||||||
|
const titleWidths = [60, 66, 50, 55, 75, 45, 40, 65];
|
||||||
|
const pathWidths = [80, 75, 85, 66, 70, 80, 60, 72];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-3 xl:grid-cols-4">
|
<div className="grid grid-cols-2 gap-3 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
{Array.from({ length: 8 }).map((_, i) => (
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="skeleton-card bg-surface/50 flex min-h-[120px] flex-col rounded-xl border border-border p-4"
|
className="skeleton-card flex min-h-[120px] flex-col rounded-sm border border-border p-4"
|
||||||
style={{ animationDelay: `${i * 80}ms` }}
|
style={{
|
||||||
|
animationDelay: `${i * 80}ms`,
|
||||||
|
backgroundColor: 'var(--skeleton-base)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{/* Icon placeholder */}
|
{/* Icon placeholder */}
|
||||||
<div className="mb-3 size-8 rounded-lg bg-surface-raised" />
|
<div
|
||||||
|
className="mb-3 size-8 rounded-sm"
|
||||||
|
style={{ backgroundColor: 'var(--skeleton-base-light)' }}
|
||||||
|
/>
|
||||||
{/* Title placeholder */}
|
{/* Title placeholder */}
|
||||||
<div className="mb-2 h-3.5 w-3/5 rounded bg-surface-raised" />
|
<div
|
||||||
|
className="mb-2 h-3.5 rounded-sm"
|
||||||
|
style={{
|
||||||
|
width: `${titleWidths[i]}%`,
|
||||||
|
backgroundColor: 'var(--skeleton-base-light)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/* Path placeholder */}
|
{/* Path placeholder */}
|
||||||
<div className="bg-surface-raised/60 mb-auto h-2.5 w-4/5 rounded" />
|
<div
|
||||||
|
className="mb-auto h-2.5 rounded-sm"
|
||||||
|
style={{
|
||||||
|
width: `${pathWidths[i]}%`,
|
||||||
|
backgroundColor: 'var(--skeleton-base-dim)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/* Meta row placeholder */}
|
{/* Meta row placeholder */}
|
||||||
<div className="mt-3 flex gap-2">
|
<div className="mt-3 flex gap-2">
|
||||||
<div className="bg-surface-raised/40 h-2.5 w-16 rounded" />
|
<div
|
||||||
<div className="bg-surface-raised/40 h-2.5 w-12 rounded" />
|
className="h-2.5 w-16 rounded-sm"
|
||||||
|
style={{ backgroundColor: 'var(--skeleton-base-dim)' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="h-2.5 w-12 rounded-sm"
|
||||||
|
style={{ backgroundColor: 'var(--skeleton-base-dim)' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -322,8 +350,8 @@ const ProjectsGrid = ({
|
||||||
|
|
||||||
if (filteredRepos.length === 0 && searchQuery.trim()) {
|
if (filteredRepos.length === 0 && searchQuery.trim()) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border px-8 py-16">
|
<div className="flex flex-col items-center justify-center rounded-sm border border-dashed border-border px-8 py-16">
|
||||||
<div className="mb-4 flex size-12 items-center justify-center rounded-xl border border-border bg-surface-raised">
|
<div className="mb-4 flex size-12 items-center justify-center rounded-sm border border-border bg-surface-raised">
|
||||||
<Search className="size-6 text-text-muted" />
|
<Search className="size-6 text-text-muted" />
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-1 text-sm text-text-secondary">No projects found</p>
|
<p className="mb-1 text-sm text-text-secondary">No projects found</p>
|
||||||
|
|
@ -334,8 +362,8 @@ const ProjectsGrid = ({
|
||||||
|
|
||||||
if (repositoryGroups.length === 0) {
|
if (repositoryGroups.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border px-8 py-16">
|
<div className="flex flex-col items-center justify-center rounded-sm border border-dashed border-border px-8 py-16">
|
||||||
<div className="mb-4 flex size-12 items-center justify-center rounded-xl border border-border bg-surface-raised">
|
<div className="mb-4 flex size-12 items-center justify-center rounded-sm border border-border bg-surface-raised">
|
||||||
<FolderGit2 className="size-6 text-text-muted" />
|
<FolderGit2 className="size-6 text-text-muted" />
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-1 text-sm text-text-secondary">No projects found</p>
|
<p className="mb-1 text-sm text-text-secondary">No projects found</p>
|
||||||
|
|
|
||||||
|
|
@ -203,22 +203,28 @@ export const DateGroupedSessions = (): React.JSX.Element => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sessionsLoading && sessions.length === 0) {
|
if (sessionsLoading && sessions.length === 0) {
|
||||||
|
const widths = [
|
||||||
|
{ header: '30%', title: '75%', sub: '90%' },
|
||||||
|
{ header: '22%', title: '60%', sub: '80%' },
|
||||||
|
{ header: '26%', title: '85%', sub: '65%' },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{[...Array<undefined>(3)].map((_, i) => (
|
{widths.map((w, i) => (
|
||||||
<div key={i} className="animate-pulse">
|
<div key={i} className="space-y-2">
|
||||||
<div
|
<div
|
||||||
className="mb-3 h-3 w-1/4 rounded"
|
className="skeleton-shimmer h-3 rounded-sm"
|
||||||
style={{ backgroundColor: 'var(--color-surface-raised)' }}
|
style={{ backgroundColor: 'var(--skeleton-base-dim)', width: w.header }}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="mb-2 h-4 w-2/3 rounded"
|
className="skeleton-shimmer h-4 rounded-sm"
|
||||||
style={{ backgroundColor: 'var(--color-surface-raised)' }}
|
style={{ backgroundColor: 'var(--skeleton-base)', width: w.title }}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="h-3 w-full rounded"
|
className="skeleton-shimmer h-3 rounded-sm"
|
||||||
style={{ backgroundColor: 'var(--color-surface-raised)', opacity: 0.5 }}
|
style={{ backgroundColor: 'var(--skeleton-base-dim)', width: w.sub }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,11 @@
|
||||||
--context-btn-bg-hover: rgba(255, 255, 255, 0.14);
|
--context-btn-bg-hover: rgba(255, 255, 255, 0.14);
|
||||||
--context-btn-active-bg: rgba(99, 102, 241, 0.45);
|
--context-btn-active-bg: rgba(99, 102, 241, 0.45);
|
||||||
--context-btn-active-text: #e0e7ff;
|
--context-btn-active-text: #e0e7ff;
|
||||||
|
|
||||||
|
/* Skeleton — tinted deep charcoal (2% cool shift) */
|
||||||
|
--skeleton-base: #24262c;
|
||||||
|
--skeleton-base-light: #2c2e35;
|
||||||
|
--skeleton-base-dim: rgba(36, 38, 44, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Light theme overrides - Warm neutral palette for eye comfort */
|
/* Light theme overrides - Warm neutral palette for eye comfort */
|
||||||
|
|
@ -371,6 +376,11 @@
|
||||||
--context-btn-bg-hover: rgba(0, 0, 0, 0.1);
|
--context-btn-bg-hover: rgba(0, 0, 0, 0.1);
|
||||||
--context-btn-active-bg: rgba(99, 102, 241, 0.35);
|
--context-btn-active-bg: rgba(99, 102, 241, 0.35);
|
||||||
--context-btn-active-text: #3730a3;
|
--context-btn-active-text: #3730a3;
|
||||||
|
|
||||||
|
/* Skeleton — tinted cool gray */
|
||||||
|
--skeleton-base: #d6d8de;
|
||||||
|
--skeleton-base-light: #cdd0d7;
|
||||||
|
--skeleton-base-dim: rgba(205, 208, 215, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
@ -485,21 +495,55 @@ body {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
transparent 0%,
|
transparent 0%,
|
||||||
rgba(255, 255, 255, 0.04) 40%,
|
rgba(255, 255, 255, 0.06) 40%,
|
||||||
rgba(255, 255, 255, 0.06) 50%,
|
rgba(255, 255, 255, 0.1) 50%,
|
||||||
rgba(255, 255, 255, 0.04) 60%,
|
rgba(255, 255, 255, 0.06) 60%,
|
||||||
transparent 100%
|
transparent 100%
|
||||||
);
|
);
|
||||||
animation: shimmer 1.8s ease-in-out infinite;
|
animation: shimmer 1.2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.light .skeleton-card::after {
|
:root.light .skeleton-card::after {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
transparent 0%,
|
transparent 0%,
|
||||||
rgba(0, 0, 0, 0.03) 40%,
|
rgba(0, 0, 0, 0.04) 40%,
|
||||||
rgba(0, 0, 0, 0.05) 50%,
|
rgba(0, 0, 0, 0.07) 50%,
|
||||||
rgba(0, 0, 0, 0.03) 60%,
|
rgba(0, 0, 0, 0.04) 60%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Per-element shimmer — fast, high-contrast sweep */
|
||||||
|
.skeleton-shimmer {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-shimmer::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(255, 255, 255, 0.06) 40%,
|
||||||
|
rgba(255, 255, 255, 0.1) 50%,
|
||||||
|
rgba(255, 255, 255, 0.06) 60%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
animation: shimmer 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.light .skeleton-shimmer::after {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(0, 0, 0, 0.04) 40%,
|
||||||
|
rgba(0, 0, 0, 0.07) 50%,
|
||||||
|
rgba(0, 0, 0, 0.04) 60%,
|
||||||
transparent 100%
|
transparent 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,43 @@
|
||||||
<link rel="icon" type="image/png" href="./favicon.png" />
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
||||||
<title>Claude Code Context</title>
|
<title>Claude Code Context</title>
|
||||||
<style>
|
<style>
|
||||||
@keyframes splash-slide {
|
/* Splash: spotlight gradient + noise overlay */
|
||||||
0% { transform: translateX(-100%); }
|
#splash {
|
||||||
100% { transform: translateX(350%); }
|
position: fixed; inset: 0; z-index: 9999;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center;
|
||||||
|
background: radial-gradient(ellipse 60% 50% at 50% 45%, #1c1c20 0%, #0e0e10 100%);
|
||||||
|
transition: opacity 0.3s ease-out;
|
||||||
}
|
}
|
||||||
#splash-bar {
|
#splash-noise {
|
||||||
animation: splash-slide 1.2s ease-in-out infinite;
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
||||||
|
opacity: 0.03; pointer-events: none;
|
||||||
}
|
}
|
||||||
|
#splash-logo { margin-bottom: 18px; }
|
||||||
|
#splash-text {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
||||||
|
font-size: 15px; font-weight: 500; letter-spacing: 0.05em;
|
||||||
|
color: #a1a1aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logo quadrant breathing — clockwise: TL → TR → BR → BL */
|
||||||
|
@keyframes splash-q {
|
||||||
|
0%, 100% { opacity: 0.12; }
|
||||||
|
10%, 25% { opacity: 0.95; }
|
||||||
|
40% { opacity: 0.12; }
|
||||||
|
}
|
||||||
|
.splash-q {
|
||||||
|
animation: splash-q 3.2s cubic-bezier(0.4, 0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
/* Light theme splash overrides */
|
/* Light theme splash overrides */
|
||||||
:root.light #splash { background: #f4f4f5; }
|
:root.light #splash {
|
||||||
:root.light #splash-text { color: #18181b; }
|
background: radial-gradient(ellipse 60% 50% at 50% 45%, #fafafa 0%, #e4e4e7 100%);
|
||||||
:root.light #splash-track { background: rgba(0,0,0,0.06); }
|
}
|
||||||
:root.light #splash-bar { background: rgba(0,0,0,0.15); }
|
:root.light #splash-text { color: #52525b; }
|
||||||
|
:root.light #splash-noise { opacity: 0.02; }
|
||||||
|
:root.light .splash-logo-bg { fill: #e4e4e7; }
|
||||||
|
:root.light .splash-logo-shape { fill: #52525b; }
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
// Flash prevention: Apply cached theme before React loads
|
// Flash prevention: Apply cached theme before React loads
|
||||||
|
|
@ -32,30 +57,27 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="splash" style="
|
<div id="splash">
|
||||||
position: fixed; inset: 0; z-index: 9999;
|
<!-- SVG noise texture — matte paper grain -->
|
||||||
display: flex; flex-direction: column;
|
<svg id="splash-noise" xmlns="http://www.w3.org/2000/svg">
|
||||||
align-items: center; justify-content: center;
|
<filter id="noiseFilter">
|
||||||
background: #141416;
|
<feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
|
||||||
transition: opacity 0.3s ease-out;
|
</filter>
|
||||||
">
|
<rect width="100%" height="100%" filter="url(#noiseFilter)"/>
|
||||||
<img src="./favicon.png" width="56" height="56" alt=""
|
</svg>
|
||||||
style="border-radius: 14px; margin-bottom: 18px;" />
|
<!-- Logo with animated quadrants -->
|
||||||
<div id="splash-text" style="
|
<svg id="splash-logo" viewBox="0 0 56 56" width="56" height="56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
<rect class="splash-logo-bg" width="56" height="56" rx="14" fill="#1c1c20"/>
|
||||||
font-size: 17px; font-weight: 500; letter-spacing: 0.02em;
|
<!-- TL: small circle -->
|
||||||
color: #fafafa;
|
<circle class="splash-q splash-logo-shape" style="animation-delay:0s" cx="19" cy="19" r="5" fill="#d4d4d8"/>
|
||||||
">Claude Code Context</div>
|
<!-- TR: right-facing semicircle -->
|
||||||
<div id="splash-track" style="
|
<path class="splash-q splash-logo-shape" style="animation-delay:0.8s" d="M34,12 A7,7 0 0,1 34,26 Z" fill="#d4d4d8"/>
|
||||||
margin-top: 24px; width: 120px; height: 2px;
|
<!-- BR: large circle -->
|
||||||
background: rgba(255,255,255,0.08); border-radius: 1px;
|
<circle class="splash-q splash-logo-shape" style="animation-delay:1.6s" cx="37" cy="37" r="6.5" fill="#d4d4d8"/>
|
||||||
overflow: hidden;
|
<!-- BL: down-facing semicircle -->
|
||||||
">
|
<path class="splash-q splash-logo-shape" style="animation-delay:2.4s" d="M12,34 A7,7 0 0,0 26,34 Z" fill="#d4d4d8"/>
|
||||||
<div id="splash-bar" style="
|
</svg>
|
||||||
width: 40%; height: 100%;
|
<div id="splash-text">Claude Code Context</div>
|
||||||
background: rgba(255,255,255,0.25); border-radius: 1px;
|
|
||||||
"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="./main.tsx"></script>
|
<script type="module" src="./main.tsx"></script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue