Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
170 changes: 87 additions & 83 deletions frontend/bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion frontend/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import './.next/types/routes.d.ts';
import "./.next/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
42 changes: 24 additions & 18 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,43 @@
"dependencies": {
"@headlessui/react": "^2.2.9",
"@heroicons/react": "^2.2.0",
"@types/dom-speech-recognition": "^0.0.7",
"@vercel/analytics": "^1.6.1",
"@vercel/speed-insights": "^1.3.1",
"@types/dom-speech-recognition": "^0.0.8",
"@vercel/analytics": "^2.0.1",
"@vercel/speed-insights": "^2.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.31.0",
"lucide-react": "^0.563.0",
"next": "^16.1.6",
"framer-motion": "^12.38.0",
"lucide-react": "^1.0.1",
"next": "^16.2.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwind-merge": "^3.4.0",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@next/eslint-plugin-next": "^16.1.6",
"@types/bun": "^1.3.8",
"@types/node": "^25.2.0",
"@types/react": "^19.2.11",
"@eslint/eslintrc": "~3.3.5",
"@eslint/js": "~9.21.0",
"@next/eslint-plugin-next": "15.1.7",
"@types/bun": "^1.3.11",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/eslint-plugin": "^8.57.2",
"@typescript-eslint/parser": "^8.57.2",
"autoprefixer": "^10.4.24",
"eslint": "^9.39.2",
"eslint": "^9.21.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"globals": "^17.3.0",
"globals": "^17.4.0",
"postcss": "^8.5.6",
"postcss-import": "^16.1.1",
"tailwindcss": "^3.4.19",
"typescript": "^5.9.3",
"typescript-eslint": "^8.54.0"
"typescript": "^5.7.3",
"typescript-eslint": "^8.57.2"
},
"overrides": {
"minimatch": "^10.2.4",
"flatted": "^3.4.2",
"@eslint/plugin-kit": "^0.6.1"
}
}
24 changes: 2 additions & 22 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,7 @@ import {
StepByStepResults,
} from '@/components/bankers-algorithm';

/**
* Logo Icon Component for Banker's Algorithm
* Theme-dependent colors matching favicon
*/
const LogoIcon = ({className}: {className?: string}) => (
<svg
width="14"
height="14"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
clipRule="evenodd"
fill="#000000"
fillRule="evenodd"
d="M8.5 0C8.5 0 4.58642 3.74805 3.94122 4.39717C3.86128 4.4776 3.84989 4.60224 3.91398 4.69539C3.97806 4.78854 4.09993 4.82451 4.20557 4.78145L7.90537 3.27345L11.7747 9.36041C11.8406 9.46403 11.9758 9.50133 12.0869 9.44651C12.1979 9.39169 12.2483 9.26276 12.2032 9.1489C11.7103 7.90508 8.5 0 8.5 0ZM6.29304 6.03867C6.35522 5.93334 6.32602 5.79881 6.22554 5.72763C6.12505 5.65645 5.98605 5.67185 5.90418 5.76322C5.12486 6.633 0 12.5 0 12.5C0 12.5 5.18613 13.803 6.03089 13.9939C6.14204 14.0191 6.25587 13.964 6.30355 13.8621C6.35122 13.7603 6.31967 13.6394 6.22796 13.5728L3.1616 11.3431L6.29304 6.03867ZM14.054 7.5893C14.016 7.47964 13.9029 7.4131 13.7867 7.43203C13.6705 7.45096 13.5853 7.5498 13.5853 7.66564V11.3824L6.45275 11.5197C6.32824 11.5221 6.22613 11.6175 6.2173 11.7396C6.20846 11.8618 6.2958 11.9704 6.41871 11.9901C7.68171 12.1927 16 13.5728 16 13.5728C16 13.5728 14.3311 8.38966 14.054 7.5893Z"
className="dark:fill-white"
/>
</svg>
);
import {LogoIcon} from '@/components/ui/LogoIcon';

export default function BankersAlgorithmPage() {
const {isDarkMode, toggleDarkMode, isUsingSystemPreference} = useDarkMode();
Expand Down Expand Up @@ -207,6 +186,7 @@ export default function BankersAlgorithmPage() {
onClick={(e) => {
if (e.target === e.currentTarget) {
setIsSidebarOpen(false);
handleStepChange?.(undefined as unknown as number); // Reset to normal view
}
}}
></div>
Expand Down
29 changes: 7 additions & 22 deletions frontend/src/components/bankers-algorithm/StepByStepResults.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use client';

import {useState, useEffect, useRef, useCallback} from 'react';
import {AlgorithmStep} from '@/types/bankers-algorithm';
import {BooleanBadge} from '@/components/ui/BooleanBadge';
import {LogoIcon} from '@/components/ui/LogoIcon';

interface StepByStepResultsProps {
steps: AlgorithmStep[];
Expand Down Expand Up @@ -45,7 +44,7 @@ export function StepByStepResults({
// Exit step navigation mode with Escape key
if (e.key === 'Escape' && currentStepIndex !== undefined) {
e.preventDefault();
onStepChange?.(undefined as any); // Reset to normal view
onStepChange?.(undefined); // Reset to normal view
return;
}

Expand Down Expand Up @@ -344,25 +343,11 @@ export function StepByStepResults({
}`}
title={animationComplete ? 'Jump to final result' : ''}
>
<svg
width="20"
height="20"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={
safeSequence.length > 0 // Result Icon
? 'text-green-600 dark:text-green-400'
: 'text-red-600 dark:text-red-400'
}
>
<path
clipRule="evenodd"
fillRule="evenodd"
d="M8.5 0C8.5 0 4.58642 3.74805 3.94122 4.39717C3.86128 4.4776 3.84989 4.60224 3.91398 4.69539C3.97806 4.78854 4.09993 4.82451 4.20557 4.78145L7.90537 3.27345L11.7747 9.36041C11.8406 9.46403 11.9758 9.50133 12.0869 9.44651C12.1979 9.39169 12.2483 9.26276 12.2032 9.1489C11.7103 7.90508 8.5 0 8.5 0ZM6.29304 6.03867C6.35522 5.93334 6.32602 5.79881 6.22554 5.72763C6.12505 5.65645 5.98605 5.67185 5.90418 5.76322C5.12486 6.633 0 12.5 0 12.5C0 12.5 5.18613 13.803 6.03089 13.9939C6.14204 14.0191 6.25587 13.964 6.30355 13.8621C6.35122 13.7603 6.31967 13.6394 6.22796 13.5728L3.1616 11.3431L6.29304 6.03867ZM14.054 7.5893C14.016 7.47964 13.9029 7.4131 13.7867 7.43203C13.6705 7.45096 13.5853 7.5498 13.5853 7.66564V11.3824L6.45275 11.5197C6.32824 11.5221 6.22613 11.6175 6.2173 11.7396C6.20846 11.8618 6.2958 11.9704 6.41871 11.9901C7.68171 12.1927 16 13.5728 16 13.5728C16 13.5728 14.3311 8.38966 14.054 7.5893Z"
fill="currentColor"
/>
</svg>
<LogoIcon
theme={safeSequence.length > 0 ? 'green' : 'red'}
width={20}
height={20}
/>
</button>
<div
className={`flex-1 min-w-0 transition-opacity duration-300 ${
Expand Down
74 changes: 74 additions & 0 deletions frontend/src/components/ui/LogoIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use client';

import React, {useId} from 'react';
import {LOGO_THEMES, LOGO_PATHS, LOGO_VIEWBOX} from '@/constants/logo-config';

export type LogoTheme = keyof typeof LOGO_THEMES;

interface LogoIconProps {
className?: string;
theme?: LogoTheme;
width?: number | string;
height?: number | string;
}

export const LogoIcon = ({
className,
theme = 'vibrant',
width = 14,
height = 14,
}: LogoIconProps) => {
const id = useId();
const themeColors = LOGO_THEMES[theme];

return (
<svg
width={width}
height={height}
viewBox={LOGO_VIEWBOX}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<defs>
<linearGradient
id={`logo-grad-top-${id}`}
x1="0%"
y1="0%"
x2="100%"
y2="100%"
>
<stop offset="0%" stopColor={themeColors.top.start} />
<stop offset="100%" stopColor={themeColors.top.end} />
</linearGradient>
<linearGradient
id={`logo-grad-left-${id}`}
x1="100%"
y1="0%"
x2="0%"
y2="100%"
>
<stop offset="0%" stopColor={themeColors.left.start} />
<stop offset="100%" stopColor={themeColors.left.end} />
</linearGradient>
<linearGradient
id={`logo-grad-right-${id}`}
x1="0%"
y1="0%"
x2="100%"
y2="0%"
>
<stop offset="0%" stopColor={themeColors.right.start} />
<stop offset="100%" stopColor={themeColors.right.end} />
</linearGradient>
</defs>
{LOGO_PATHS.map((path) => (
<path
key={path.id}
fill={`url(#${path.gradientId}-${id})`}
d={path.d}
/>
))}
</svg>
);
};
3 changes: 0 additions & 3 deletions frontend/src/components/ui/browser-compatibility-warning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ export const BrowserCompatibilityWarning = ({
}: BrowserCompatibilityWarningProps) => {
const [browserInfo, setBrowserInfo] = useState<BrowserInfo | null>(null);
const [isVisible, setIsVisible] = useState(false);
const [isDismissed, setIsDismissed] = useState(false);

useEffect(() => {
const browser = detectBrowser();
setBrowserInfo(browser);

// Check if user has previously dismissed the warning
const dismissed = localStorage.getItem('browser-warning-dismissed');
setIsDismissed(dismissed === 'true');

// Show warning if browser is not supported and not dismissed
if (!browser.isSupported && (!dismissed || showAlways)) {
Expand All @@ -38,7 +36,6 @@ export const BrowserCompatibilityWarning = ({

const handleDismiss = () => {
setIsVisible(false);
setIsDismissed(true);
localStorage.setItem('browser-warning-dismissed', 'true');
onDismiss?.();
};
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/ui/loading-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export function LoadingScreen({
></div>
</div>

{/* eslint-disable-next-line react/no-unknown-property */}
<style jsx>{`
.loading-container {
min-height: 100vh;
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/constants/logo-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Shared configuration for the Banker's Algorithm Logo
* Ensures consistency between the UI components and the dynamic favicon generator.
*/

export const LOGO_VIEWBOX = '0 0 16 16';

export const LOGO_THEMES = {
vibrant: {
top: {start: '#FF1E76', end: '#FFB800'},
left: {start: '#00E5FF', end: '#007AFF'},
right: {start: '#DFFF00', end: '#34C759'},
},
green: {
top: {start: '#34C759', end: '#00FF00'},
left: {start: '#34C759', end: '#00FF00'},
right: {start: '#34C759', end: '#00FF00'},
},
red: {
top: {start: '#FF0000', end: '#800000'},
left: {start: '#FF0000', end: '#800000'},
right: {start: '#FF0000', end: '#800000'},
},
};

export const LOGO_PATHS = [
// Top Boomerang
{
id: 'top',
d: 'M8.5 0C8.5 0 4.58642 3.74805 3.94122 4.39717C3.86128 4.4776 3.84989 4.60224 3.91398 4.69539C3.97806 4.78854 4.09993 4.82451 4.20557 4.78145L7.90537 3.27345L11.7747 9.36041C11.8406 9.46403 11.9758 9.50133 12.0869 9.44651C12.1979 9.39169 12.2483 9.26276 12.2032 9.1489C11.7103 7.90508 8.5 0 8.5 0Z',
gradientId: 'logo-grad-top',
colorKey: 'top' as const,
},
// Left Boomerang
{
id: 'left',
d: 'M6.29304 6.03867C6.35522 5.93334 6.32602 5.79881 6.22554 5.72763C6.12505 5.65645 5.98605 5.67185 5.90418 5.76322C5.12486 6.633 0 12.5 0 12.5C0 12.5 5.18613 13.803 6.03089 13.9939C6.14204 14.0191 6.25587 13.964 6.30355 13.8621C6.35122 13.7603 6.31967 13.6394 6.22796 13.5728L3.1616 11.3431L6.29304 6.03867Z',
gradientId: 'logo-grad-left',
colorKey: 'left' as const,
},
// Right Boomerang
{
id: 'right',
d: 'M14.054 7.5893C14.016 7.47964 13.9029 7.4131 13.7867 7.43203C13.6705 7.45096 13.5853 7.5498 13.5853 7.66564V11.3824L6.45275 11.5197C6.32824 11.5221 6.22613 11.6175 6.2173 11.7396C6.20846 11.8618 6.2958 11.9704 6.41871 11.9901C7.68171 12.1927 16 13.5728 16 13.5728C16 13.5728 14.3311 8.38966 14.054 7.5893Z',
gradientId: 'logo-grad-right',
colorKey: 'right' as const,
},
];
2 changes: 1 addition & 1 deletion frontend/src/hooks/useDynamicFavicon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function useDynamicFavicon(isDarkMode: boolean) {
}

// Generate SVG favicon based on theme
const faviconDataUri = generateFaviconSvg(isDarkMode);
const faviconDataUri = generateFaviconSvg();

// Update favicon type if needed
if (faviconLink.type !== 'image/svg+xml') {
Expand Down
38 changes: 22 additions & 16 deletions frontend/src/utils/generateFavicon.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
/**
* Generate dynamic SVG favicon based on theme
* Uses the LogoIcon SVG component converted to a data URI
*/
import {LOGO_THEMES, LOGO_PATHS, LOGO_VIEWBOX} from '@/constants/logo-config';

export function generateFaviconSvg(isDarkMode: boolean): string {
// Color based on theme - matches system theme
const color = isDarkMode ? '#ffffff' : '#000000';

// SVG markup for the LogoIcon
export function generateFaviconSvg(): string {
// SVG markup for the LogoIcon with vibrant gradients and original shape
// Note: Using absolute coordinates for gradients to improve browser compatibility in favicons
const svg = `
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
clip-rule="evenodd"
fill="${color}"
fill-rule="evenodd"
d="M8.5 0C8.5 0 4.58642 3.74805 3.94122 4.39717C3.86128 4.4776 3.84989 4.60224 3.91398 4.69539C3.97806 4.78854 4.09993 4.82451 4.20557 4.78145L7.90537 3.27345L11.7747 9.36041C11.8406 9.46403 11.9758 9.50133 12.0869 9.44651C12.1979 9.39169 12.2483 9.26276 12.2032 9.1489C11.7103 7.90508 8.5 0 8.5 0ZM6.29304 6.03867C6.35522 5.93334 6.32602 5.79881 6.22554 5.72763C6.12505 5.65645 5.98605 5.67185 5.90418 5.76322C5.12486 6.633 0 12.5 0 12.5C0 12.5 5.18613 13.803 6.03089 13.9939C6.14204 14.0191 6.25587 13.964 6.30355 13.8621C6.35122 13.7603 6.31967 13.6394 6.22796 13.5728L3.1616 11.3431L6.29304 6.03867ZM14.054 7.5893C14.016 7.47964 13.9029 7.4131 13.7867 7.43203C13.6705 7.45096 13.5853 7.5498 13.5853 7.66564V11.3824L6.45275 11.5197C6.32824 11.5221 6.22613 11.6175 6.2173 11.7396C6.20846 11.8618 6.2958 11.9704 6.41871 11.9901C7.68171 12.1927 16 13.5728 16 13.5728C16 13.5728 14.3311 8.38966 14.054 7.5893Z"
/>
<svg width="32" height="32" viewBox="${LOGO_VIEWBOX}" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="favicon-grad-top" x1="0" y1="0" x2="16" y2="16" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="${LOGO_THEMES.vibrant.top.start}" />
<stop offset="100%" stop-color="${LOGO_THEMES.vibrant.top.end}" />
</linearGradient>
<linearGradient id="favicon-grad-left" x1="16" y1="0" x2="0" y2="16" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="${LOGO_THEMES.vibrant.left.start}" />
<stop offset="100%" stop-color="${LOGO_THEMES.vibrant.left.end}" />
</linearGradient>
<linearGradient id="favicon-grad-right" x1="0" y1="16" x2="16" y2="0" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="${LOGO_THEMES.vibrant.right.start}" />
<stop offset="100%" stop-color="${LOGO_THEMES.vibrant.right.end}" />
</linearGradient>
</defs>
<path fill="url(#favicon-grad-top)" d="${LOGO_PATHS[0].d}" />
<path fill="url(#favicon-grad-left)" d="${LOGO_PATHS[1].d}" />
<path fill="url(#favicon-grad-right)" d="${LOGO_PATHS[2].d}" />
</svg>
`.trim();

Expand Down
1 change: 1 addition & 0 deletions frontend/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ const config: Config = {
},
},
},
// eslint-disable-next-line @typescript-eslint/no-require-imports
plugins: [require('tailwindcss-animate')],
};

Expand Down
Loading