Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
f2e1dff
docs: specify cosmic black hole interface redesign
Inmerson Aug 6, 2026
2e60329
docs: add cosmic black hole UI implementation plan
Inmerson Aug 6, 2026
3d2dbab
docs: self-review cosmic UI implementation plan
Inmerson Aug 6, 2026
b59c68c
ci: verify cosmic design branch
Inmerson Aug 6, 2026
ed66eaa
chore: initialize cosmic UI execution ledger
Inmerson Aug 6, 2026
67428d8
test: define cosmic hero asset contract
Inmerson Aug 6, 2026
76db4b9
chore: stage cosmic hero binary
Inmerson Aug 6, 2026
63aeb77
ci: add isolated cosmic task runner
Inmerson Aug 6, 2026
0cd9412
chore(sdd): execute cosmic task 1
Inmerson Aug 6, 2026
6105d4b
chore: add task 1 commit message
Inmerson Aug 6, 2026
041f259
chore: stage compact cosmic hero binary
Inmerson Aug 6, 2026
30fef6d
chore(sdd): retry cosmic task 1 with compact asset
Inmerson Aug 6, 2026
76863ae
feat: add optimized cosmic hero asset
github-actions[bot] Aug 6, 2026
dcb9ed4
docs: record completed cosmic task 1
Inmerson Aug 6, 2026
bd241a7
chore: prepare cosmic task 2 commit message
Inmerson Aug 6, 2026
af2cff5
chore(sdd): execute cosmic task 2
Inmerson Aug 6, 2026
152c249
feat: establish cosmic visual system
github-actions[bot] Aug 6, 2026
1e66a5e
docs: record completed cosmic task 2
Inmerson Aug 6, 2026
68f1034
chore: prepare cosmic task 3 commit message
Inmerson Aug 6, 2026
d34e8a8
chore(sdd): execute cosmic task 3
Inmerson Aug 6, 2026
b577833
feat: add cosmic UI primitives and overall progress
github-actions[bot] Aug 6, 2026
5aa859f
docs: record completed cosmic task 3
Inmerson Aug 6, 2026
8a13506
chore: prepare cosmic task 4 commit message
Inmerson Aug 6, 2026
f2372b2
chore(sdd): execute cosmic task 4
Inmerson Aug 6, 2026
5622341
feat: redesign cosmic application shell
github-actions[bot] Aug 6, 2026
e0a0651
docs: record completed cosmic task 4
Inmerson Aug 6, 2026
70f022e
chore: prepare cosmic task 5 commit message
Inmerson Aug 6, 2026
ca85ef5
chore(sdd): execute cosmic task 5
Inmerson Aug 6, 2026
298445f
chore: declare Vite client environment types
Inmerson Aug 6, 2026
d538b4a
chore(sdd): retry cosmic task 5 with Vite types
Inmerson Aug 6, 2026
923f5e8
feat: add cinematic cosmic dashboard
github-actions[bot] Aug 6, 2026
7225fe9
docs: record completed cosmic task 5
Inmerson Aug 6, 2026
a106ace
chore: prepare cosmic task 6 commit message
Inmerson Aug 6, 2026
b45808e
chore(sdd): execute cosmic task 6
Inmerson Aug 6, 2026
91753f9
feat: give courses distinct cosmic identities
github-actions[bot] Aug 6, 2026
50789e4
docs: record completed cosmic task 6
Inmerson Aug 6, 2026
bcfe437
chore: prepare cosmic task 7 commit message
Inmerson Aug 6, 2026
4f4a1b7
chore(sdd): execute cosmic task 7
Inmerson Aug 6, 2026
944bbc9
feat: apply restrained cosmic styling to learning views
github-actions[bot] Aug 6, 2026
a63f1a8
docs: record completed cosmic task 7
Inmerson Aug 6, 2026
a88c187
chore: prepare cosmic task 8 commit message
Inmerson Aug 6, 2026
7637753
chore(sdd): execute cosmic task 8
Inmerson Aug 6, 2026
b2d8012
chore(sdd): retry cosmic task 8 active navigation
Inmerson Aug 6, 2026
6972679
test: align mobile navigation accessible name
Inmerson Aug 6, 2026
69033b1
chore(sdd): rerun cosmic task 8 after accessibility test alignment
Inmerson Aug 6, 2026
bd8a0f0
fix: harden cosmic UI responsiveness and accessibility
github-actions[bot] Aug 6, 2026
4394f55
docs: record completed cosmic task 8
Inmerson Aug 6, 2026
6fa8188
chore: prepare cosmic task 9 commit message
Inmerson Aug 6, 2026
a7690bd
chore(sdd): execute cosmic task 9 acceptance verification
Inmerson Aug 6, 2026
3fc5a10
test: record cosmic UI acceptance verification
github-actions[bot] Aug 6, 2026
1edd4db
docs: close cosmic UI execution ledger
Inmerson Aug 6, 2026
f35ebcd
chore: remove temporary cosmic execution ledger
Inmerson Aug 6, 2026
09d3163
chore: remove temporary cosmic task runner
Inmerson Aug 6, 2026
0856e3a
chore: remove temporary cosmic branch CI
Inmerson Aug 6, 2026
735639e
docs: record final cosmic UI review
Inmerson Aug 6, 2026
8fc6523
docs: index implementation verification reports
Inmerson Aug 6, 2026
1b0f9db
chore: preserve verification report directory
Inmerson Aug 6, 2026
269796b
chore: remove unnecessary reports placeholder
Inmerson Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,30 @@
import { render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it } from 'vitest';
import App from './App';
describe('Math-CS app shell', () => { beforeEach(() => localStorage.clear()); it('provides an accessible notebook shell', () => { render(<App />); expect(screen.getAllByRole('main')).toHaveLength(1); expect(screen.getAllByRole('heading', { level: 1 })).toHaveLength(1); expect(screen.getByLabelText('Primary navigation')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Open Math I' })).toBeVisible(); expect(document.querySelector('[aria-hidden="true"]')).not.toBeNull(); }); });
import { COURSES } from './data/courseCatalog';
import { createDefaultMathCsState, MATH_CS_STORAGE_PREFIX } from './utils/mathCsStorage';
import { markTopicComplete } from './utils/progress';

describe('Math-CS app shell', () => {
beforeEach(() => localStorage.clear());

it('provides an accessible notebook shell', () => {
render(<App />);
expect(screen.getAllByRole('main')).toHaveLength(1);
expect(screen.getAllByRole('heading', { level: 1 })).toHaveLength(1);
expect(screen.getByRole('navigation', { name: 'Primary navigation' })).toBeInTheDocument();
expect(screen.getByRole('navigation', { name: 'Mobile navigation' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Open Math I' })).toBeVisible();
expect(document.querySelector('[aria-hidden="true"]')).not.toBeNull();
});

it('derives and passes overall progress from stored course completion', () => {
const seeded = COURSES[0].topics.slice(0, 4).reduce(
(state, topic) => markTopicComplete(state, topic.id),
createDefaultMathCsState(),
);
localStorage.setItem(`${MATH_CS_STORAGE_PREFIX}state`, JSON.stringify(seeded));
render(<App />);
expect(screen.getByRole('img', { name: 'Overall progress: 24%' })).toBeInTheDocument();
});
});
7 changes: 4 additions & 3 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import { FormulaWorkspaceView } from './views/FormulaWorkspaceView';
import { AIChatView } from './views/AIChatView';
import { QuizSubmission } from './components/quiz/QuizRunner';
import { AppDestination } from './types';
import { getCourse, getTopic } from './data/courseCatalog';
import { COURSES, getCourse, getTopic } from './data/courseCatalog';
import { createDefaultMathCsState, loadMathCsState, MathCsState, saveMathCsState } from './utils/mathCsStorage';
import { markTopicComplete, recordQuizResult, removeFormula, saveFormula, saveFormulaNote } from './utils/progress';
import { getOverallProgress, markTopicComplete, recordQuizResult, removeFormula, saveFormula, saveFormulaNote } from './utils/progress';

const isValidDestination = (destination: AppDestination): boolean => {
if (destination.section === 'course') return Boolean(destination.courseId && getCourse(destination.courseId));
Expand All @@ -31,6 +31,7 @@ const App: React.FC = () => {
const stored = initial.lastDestination as AppDestination | null;
const [destination, setDestination] = useState<AppDestination>(stored && isValidDestination(stored) ? stored : { section: 'dashboard' });
const [menuOpen, setMenuOpen] = useState(false);
const overallProgress = getOverallProgress(state, COURSES);

const updateState = (next: MathCsState) => { setState(next); saveMathCsState(next); };
const navigate = (next: AppDestination) => {
Expand Down Expand Up @@ -60,6 +61,6 @@ const App: React.FC = () => {
}
};

return <div className="min-h-screen bg-[#07111f] text-[#f4f1e8]"><AnimatedBackground /><Sidebar destination={destination} onNavigate={navigate} isOpen={menuOpen} onClose={() => setMenuOpen(false)} /><header className="sticky top-0 z-30 flex h-14 items-center border-b border-white/8 bg-[#07111f]/90 px-4 backdrop-blur md:hidden"><button className="focus-ring rounded-lg p-2" onClick={() => setMenuOpen(true)} aria-label="Open navigation"><Menu /></button><span className="ml-3 font-semibold text-white">Math-CS</span></header><main className="min-h-screen px-4 py-6 pb-24 md:ml-72 md:px-8 md:py-8 md:pb-10">{renderContent()}</main><BottomNavigation destination={destination} onNavigate={navigate} /></div>;
return <div className="cosmic-shell min-h-screen text-[#f5f7fb]"><AnimatedBackground /><Sidebar destination={destination} onNavigate={navigate} isOpen={menuOpen} onClose={() => setMenuOpen(false)} overallProgress={overallProgress} /><header className="cosmic-glass sticky top-0 z-30 flex h-14 items-center border-x-0 border-t-0 px-4 md:hidden"><button className="focus-ring rounded-lg p-2" onClick={() => setMenuOpen(true)} aria-label="Open navigation"><Menu /></button><span className="ml-3 font-semibold text-white">Math-CS</span></header><main className="min-h-screen px-4 py-6 pb-24 md:ml-72 md:px-8 md:py-8 md:pb-10">{renderContent()}</main><BottomNavigation destination={destination} onNavigate={navigate} /></div>;
};
export default App;
13 changes: 13 additions & 0 deletions components/AnimatedBackground.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { AnimatedBackground } from './AnimatedBackground';

describe('AnimatedBackground', () => {
it('renders decorative restrained cosmic layers', () => {
render(<AnimatedBackground />);
const background = screen.getByTestId('cosmic-background');
expect(background).toHaveAttribute('aria-hidden', 'true');
expect(background).toHaveClass('cosmic-shell');
expect(background.querySelectorAll('[data-cosmic-layer]')).toHaveLength(3);
});
});
16 changes: 15 additions & 1 deletion components/AnimatedBackground.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,16 @@
import React from 'react';
export const AnimatedBackground: React.FC = () => <div aria-hidden="true" className="pointer-events-none fixed inset-0 -z-10 overflow-hidden bg-[#07111f]"><div className="absolute inset-0 bg-[radial-gradient(circle_at_80%_10%,rgba(34,211,238,0.08),transparent_34%),linear-gradient(180deg,#07111f_0%,#091522_55%,#06101b_100%)]" /><div className="coordinate-grid absolute inset-0 opacity-40" /><div className="ambient-light absolute -right-32 top-1/3 size-[32rem] rounded-full bg-cyan-400/5 blur-[110px]" /></div>;

export const AnimatedBackground: React.FC = () => (
<div
data-testid="cosmic-background"
aria-hidden="true"
className="cosmic-shell pointer-events-none fixed inset-0 -z-10 overflow-hidden"
>
<div
data-cosmic-layer="nebula"
className="absolute inset-0 bg-[radial-gradient(circle_at_82%_8%,rgba(98,168,255,.12),transparent_31%),radial-gradient(circle_at_18%_75%,rgba(124,131,255,.07),transparent_34%),linear-gradient(180deg,#030711_0%,#07111f_58%,#030711_100%)]"
/>
<div data-cosmic-layer="stars" className="cosmic-starfield absolute inset-0" />
<div data-cosmic-layer="grid" className="coordinate-grid absolute inset-0 opacity-20" />
</div>
);
20 changes: 17 additions & 3 deletions components/Sidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,23 @@ import { describe, expect, it, vi } from 'vitest';
import { Sidebar } from './Sidebar';

describe('Sidebar', () => {
it('shows the approved navigation and excludes old domains', () => {
render(<Sidebar destination={{ section: 'dashboard' }} onNavigate={vi.fn()} isOpen onClose={vi.fn()} />);
for (const label of ['Dashboard', 'Math I', 'Math II', 'Math Lab', 'Practice', 'Exams', 'Progress']) expect(screen.getByRole('button', { name: label })).toBeInTheDocument();
it('shows the approved navigation, active state, and real overall progress', () => {
render(
<Sidebar
destination={{ section: 'dashboard' }}
onNavigate={vi.fn()}
isOpen
onClose={vi.fn()}
overallProgress={42}
/>,
);
expect(screen.getByText('Inmerson')).toBeInTheDocument();
expect(screen.getByText('Math-CS')).toBeInTheDocument();
expect(screen.getByRole('img', { name: 'Overall progress: 42%' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Dashboard' })).toHaveAttribute('aria-current', 'page');
for (const label of ['Dashboard', 'Math I', 'Math II', 'Math Lab', 'Practice', 'Exams', 'Progress']) {
expect(screen.getByRole('button', { name: label })).toBeInTheDocument();
}
expect(screen.queryByText(/Differential Equations|Population Models|biotech/i)).not.toBeInTheDocument();
});
});
105 changes: 88 additions & 17 deletions components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import React from 'react';
import { BookOpen, FlaskConical, Gauge, GraduationCap, Home, Library, NotebookTabs, Sigma, Sparkles, X } from 'lucide-react';
import {
BookOpen,
FlaskConical,
Gauge,
GraduationCap,
Home,
Library,
NotebookTabs,
Sigma,
Sparkles,
X,
} from 'lucide-react';
import { AppDestination } from '../types';
import { BRAND_SHORT_NAME } from '../data/branding';
import { CosmicLogoMark } from './cosmic/CosmicLogoMark';
import { CosmicProgressRing } from './cosmic/CosmicProgressRing';

interface SidebarProps {
destination: AppDestination;
onNavigate: (destination: AppDestination) => void;
isOpen: boolean;
onClose: () => void;
overallProgress: number;
}

const primaryItems: { label: string; icon: React.ComponentType<{ size?: number }>; destination: AppDestination }[] = [
type NavItem = {
label: string;
icon: React.ComponentType<{ size?: number }>;
destination: AppDestination;
};

const primaryItems: NavItem[] = [
{ label: 'Dashboard', icon: Home, destination: { section: 'dashboard' } },
{ label: 'Math I', icon: Sigma, destination: { section: 'course', courseId: 'math-analysis' } },
{ label: 'Math II', icon: BookOpen, destination: { section: 'course', courseId: 'linear-algebra-geometry' } },
Expand All @@ -20,45 +40,96 @@ const primaryItems: { label: string; icon: React.ComponentType<{ size?: number }
{ label: 'Progress', icon: Gauge, destination: { section: 'progress' } },
];

const secondaryItems: { label: string; icon: React.ComponentType<{ size?: number }>; destination: AppDestination }[] = [
const secondaryItems: NavItem[] = [
{ label: 'Formulas', icon: Library, destination: { section: 'formulas' } },
{ label: 'Assistant', icon: Sparkles, destination: { section: 'assistant' } },
];

const matches = (current: AppDestination, target: AppDestination) =>
current.section === target.section && (!target.courseId || current.courseId === target.courseId);

export const Sidebar: React.FC<SidebarProps> = ({ destination, onNavigate, isOpen, onClose }) => {
const renderItem = (item: typeof primaryItems[number]) => {
export const Sidebar: React.FC<SidebarProps> = ({
destination,
onNavigate,
isOpen,
onClose,
overallProgress,
}) => {
const activate = (next: AppDestination) => {
onNavigate(next);
onClose();
};

const renderItem = (item: NavItem) => {
const Icon = item.icon;
const active = matches(destination, item.destination);
return (
<button
key={item.label}
type="button"
onClick={() => { onNavigate(item.destination); onClose(); }}
onClick={() => activate(item.destination)}
aria-current={active ? 'page' : undefined}
className={`focus-ring flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-left text-sm font-medium transition ${active ? 'bg-cyan-300/12 text-cyan-100 ring-1 ring-cyan-300/25' : 'text-slate-300 hover:bg-white/5 hover:text-white'}`}
className={`focus-ring group flex min-h-11 w-full items-center gap-3 rounded-xl border px-3 py-2.5 text-left text-sm font-medium transition ${active
? 'border-blue-300/25 bg-blue-400/10 text-blue-100 shadow-[inset_3px_0_0_rgba(98,168,255,.9),0_0_28px_rgba(56,139,253,.08)]'
: 'border-transparent text-slate-300 hover:border-white/8 hover:bg-white/4 hover:text-white'}`}
>
<Icon size={18} />
<span className={`grid size-8 shrink-0 place-items-center rounded-lg ${active ? 'bg-blue-300/10 text-blue-200' : 'bg-white/3 text-slate-400 group-hover:text-slate-200'}`}>
<Icon size={17} />
</span>
<span>{item.label}</span>
</button>
);
};

return (
<>
{isOpen && <button className="fixed inset-0 z-40 bg-black/60 md:hidden" aria-label="Close navigation overlay" onClick={onClose} />}
<aside className={`fixed inset-y-0 left-0 z-50 flex w-72 flex-col border-r border-white/8 bg-[#091321]/95 p-4 backdrop-blur-xl transition-transform md:translate-x-0 ${isOpen ? 'translate-x-0' : '-translate-x-full'}`} aria-label="Primary navigation">
<div className="mb-7 flex items-center justify-between px-2 pt-2">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-cyan-300">Inmerson</p>
<p className="mt-1 text-lg font-bold text-white">{BRAND_SHORT_NAME}</p>
{isOpen && (
<button
className="fixed inset-0 z-40 bg-black/70 backdrop-blur-sm md:hidden"
aria-label="Close navigation overlay"
onClick={onClose}
/>
)}
<aside
className={`fixed inset-y-0 left-0 z-50 flex w-72 flex-col border-r border-blue-200/10 bg-[#030711]/94 p-4 shadow-[24px_0_80px_rgba(0,0,0,.32)] backdrop-blur-2xl transition-transform md:translate-x-0 ${isOpen ? 'translate-x-0' : '-translate-x-full'}`}
aria-label="Math-CS sidebar"
>
<div className="mb-6 flex items-center justify-between px-1 pt-1">
<div className="flex items-center gap-3">
<CosmicLogoMark size={44} className="shrink-0" />
<div>
<p className="text-sm font-semibold tracking-[0.04em] text-slate-200">Inmerson</p>
<p className="text-lg font-bold tracking-tight text-white">{BRAND_SHORT_NAME}</p>
</div>
</div>
<button type="button" className="focus-ring rounded-lg p-2 text-slate-300 md:hidden" onClick={onClose} aria-label="Close navigation"><X size={20} /></button>
<button
type="button"
className="focus-ring rounded-lg p-2 text-slate-300 hover:bg-white/5 md:hidden"
onClick={onClose}
aria-label="Close navigation"
>
<X size={20} />
</button>
</div>
<nav className="space-y-1" aria-label="Learning sections">{primaryItems.map(renderItem)}</nav>
<div className="mt-auto border-t border-white/8 pt-4"><nav className="space-y-1" aria-label="Learning tools">{secondaryItems.map(renderItem)}</nav></div>

<nav className="min-h-0 flex-1 overflow-y-auto pr-1" aria-label="Primary navigation">
<div className="space-y-1">{primaryItems.map(renderItem)}</div>
<div className="my-4 h-px bg-gradient-to-r from-transparent via-blue-200/12 to-transparent" />
<div className="space-y-1">{secondaryItems.map(renderItem)}</div>
</nav>

<button
type="button"
onClick={() => activate({ section: 'progress' })}
className="focus-ring cosmic-glass cosmic-card mt-4 flex items-center gap-3 rounded-2xl p-3 text-left"
aria-label="View detailed progress"
>
<CosmicProgressRing value={overallProgress} label="Overall progress" size="sm" />
<span className="min-w-0">
<span className="block text-sm font-semibold text-white">Your Progress</span>
<span className="mt-1 block text-xs leading-5 text-slate-400">Across both approved mathematics courses</span>
</span>
</button>
</aside>
</>
);
Expand Down
31 changes: 31 additions & 0 deletions components/cosmic/CosmicHero.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { CosmicHero } from './CosmicHero';

const renderHero = () => render(
<CosmicHero
title="Inmerson Math-CS"
tagline="Interactive Mathematics for Computer Science"
description="Academic workspace"
/>,
);

describe('CosmicHero', () => {
it('renders a readable cinematic header with a decorative Pages-aware image', () => {
renderHero();
expect(screen.getByRole('heading', { level: 1, name: 'Inmerson Math-CS' })).toBeInTheDocument();
expect(screen.getByText('Interactive Mathematics for Computer Science')).toBeInTheDocument();
expect(screen.getByTestId('black-hole-hero')).toHaveClass('cosmic-hero-fallback');
const image = screen.getByTestId('black-hole-image');
expect(image).toHaveAttribute('alt', '');
expect(image).toHaveAttribute('src', expect.stringContaining('assets/cosmic/black-hole-hero.webp'));
});

it('keeps all educational content readable when the image fails', () => {
renderHero();
fireEvent.error(screen.getByTestId('black-hole-image'));
expect(screen.queryByTestId('black-hole-image')).not.toBeInTheDocument();
expect(screen.getByRole('heading', { level: 1, name: 'Inmerson Math-CS' })).toBeVisible();
expect(screen.getByText('Academic workspace')).toBeVisible();
});
});
42 changes: 42 additions & 0 deletions components/cosmic/CosmicHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';

interface CosmicHeroProps {
title: string;
tagline: string;
description: string;
}

export const CosmicHero: React.FC<CosmicHeroProps> = ({ title, tagline, description }) => {
const [imageFailed, setImageFailed] = React.useState(false);
const heroSrc = `${import.meta.env.BASE_URL}assets/cosmic/black-hole-hero.webp`;

return (
<header
data-testid="black-hole-hero"
className="cosmic-hero cosmic-hero-fallback cosmic-glass relative isolate overflow-hidden rounded-[1.75rem]"
>
<div className="cosmic-hero__image" aria-hidden="true">
{!imageFailed && (
<img
data-testid="black-hole-image"
src={heroSrc}
alt=""
decoding="async"
fetchPriority="high"
onError={() => setImageFailed(true)}
/>
)}
</div>
<div className="cosmic-hero__scrim" aria-hidden="true" />
<div className="cosmic-hero__content relative z-10">
<p className="text-xs font-bold uppercase tracking-[0.24em] text-blue-200">{tagline}</p>
<h1 className="mt-4 max-w-3xl text-4xl font-bold tracking-[-0.035em] text-white sm:text-5xl lg:text-6xl">{title}</h1>
<p className="mt-5 max-w-2xl text-base leading-7 text-slate-300 sm:text-lg sm:leading-8">{description}</p>
<div className="mt-7 flex max-w-xl items-center" aria-hidden="true">
<span className="cosmic-divider flex-1" />
<span className="size-2 rounded-full bg-blue-200 shadow-[0_0_18px_rgba(183,232,255,.9)]" />
</div>
</div>
</header>
);
};
37 changes: 37 additions & 0 deletions components/cosmic/CosmicLogoMark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';

interface CosmicLogoMarkProps {
className?: string;
size?: number;
}

export const CosmicLogoMark: React.FC<CosmicLogoMarkProps> = ({ className = '', size = 42 }) => (
<svg
aria-hidden="true"
focusable="false"
viewBox="0 0 64 64"
width={size}
height={size}
className={className}
>
<defs>
<radialGradient id="cosmic-logo-core" cx="50%" cy="50%" r="50%">
<stop offset="0%" stopColor="#01030a" />
<stop offset="68%" stopColor="#02050d" />
<stop offset="100%" stopColor="#101b32" />
</radialGradient>
<linearGradient id="cosmic-logo-ring" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#b7e8ff" />
<stop offset="45%" stopColor="#62a8ff" />
<stop offset="100%" stopColor="#a78bfa" />
</linearGradient>
<filter id="cosmic-logo-glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2.4" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
</defs>
<ellipse cx="32" cy="32" rx="27" ry="10" fill="none" stroke="url(#cosmic-logo-ring)" strokeWidth="3" opacity=".9" filter="url(#cosmic-logo-glow)" transform="rotate(-11 32 32)" />
<circle cx="32" cy="32" r="15" fill="url(#cosmic-logo-core)" stroke="rgba(183,232,255,.55)" strokeWidth="1.5" />
<path d="M8 35c9 6 39 7 49-3" fill="none" stroke="#f4d7aa" strokeWidth="1.4" opacity=".65" />
</svg>
);
Loading
Loading