diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e2b0141
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,53 @@
+# Multi-stage Dockerfile for Next.js 16 static export
+
+# Stage 1: Dependencies
+FROM node:22-alpine AS deps
+RUN apk add --no-cache libc6-compat
+WORKDIR /app
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+# Copy package files
+COPY package.json pnpm-lock.yaml ./
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Stage 2: Builder
+FROM node:22-alpine AS builder
+WORKDIR /app
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+# Copy dependencies from deps stage
+COPY --from=deps /app/node_modules ./node_modules
+
+# Copy source code
+COPY . .
+
+# Build the application (static export)
+RUN pnpm build
+
+# Stage 3: Runner (serve static files)
+FROM nginx:alpine AS runner
+
+# Install curl for health check
+RUN apk add --no-cache curl
+
+# Copy custom nginx config
+COPY --from=builder /app/nginx.conf /etc/nginx/nginx.conf
+
+# Copy static files from build
+COPY --from=builder /app/out /usr/share/nginx/html
+
+# Expose port 80
+EXPOSE 80
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost/ || exit 1
+
+# Start nginx
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md
index e269111..90bab5e 100644
--- a/IMPLEMENTATION_PLAN.md
+++ b/IMPLEMENTATION_PLAN.md
@@ -2,7 +2,7 @@
## Project Overview
-A modern, feature-rich web UI for generating ASCII art text using figlet.js with 700+ fonts from xero/figlet-fonts. This project aims to be the best figlet web interface, significantly improving upon existing solutions like TAAG.
+A modern, feature-rich web UI for generating ASCII art text using figlet.js with 373 fonts from xero/figlet-fonts (.flf format). This project aims to be the best figlet web interface, significantly improving upon existing solutions like TAAG.
**Tech Stack:**
- Next.js 16 with static export
@@ -17,7 +17,7 @@ A modern, feature-rich web UI for generating ASCII art text using figlet.js with
## Key Features & Improvements Over TAAG
### Superior Font Management
-- **700+ fonts** from xero collection (vs ~300 on TAAG)
+- **373 fonts** from xero collection (all .flf FIGlet fonts)
- Visual font preview cards
- Fuzzy search with intelligent matching
- Font categories and tags (3D, block, script, retro, etc.)
diff --git a/README.md b/README.md
index 9822255..3805c95 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# Figlet UI
-A modern, feature-rich web UI for generating ASCII art text using figlet.js with 700+ fonts from the [xero/figlet-fonts](https://github.com/xero/figlet-fonts) collection.
+A modern, feature-rich web UI for generating ASCII art text using figlet.js with 373 fonts from the [xero/figlet-fonts](https://github.com/xero/figlet-fonts) collection.
## Features
-- **700+ Figlet Fonts** - Massive library from xero's curated collection
+- **373 Figlet Fonts** - All .flf fonts from xero's curated collection
- **Live Preview** - Real-time rendering as you type
- **Fuzzy Search** - Quickly find fonts by name or style
- **Visual Font Previews** - See actual rendering in the selector
@@ -84,7 +84,7 @@ figlet-ui/
## Why Figlet UI is Better Than TAAG
-- **10x More Fonts**: 700+ fonts vs ~300 on TAAG
+- **More Fonts**: 373 fonts vs ~300 on TAAG
- **Modern UI/UX**: Clean, responsive design with animations
- **Better Search**: Fuzzy search with visual previews
- **More Export Options**: PNG, SVG, code snippets, not just text
diff --git a/app/page.tsx b/app/page.tsx
index d45248e..fd3d310 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,5 +1,6 @@
import { FigletConverter } from '@/components/converter/FigletConverter';
import { ThemeToggle } from '@/components/layout/ThemeToggle';
+import { KeyboardShortcutsHelp } from '@/components/ui/KeyboardShortcutsHelp';
export default function Home() {
return (
@@ -41,6 +42,8 @@ export default function Home() {
+
+
);
}
diff --git a/components/converter/FontSelector.tsx b/components/converter/FontSelector.tsx
index 8c14179..36ead79 100644
--- a/components/converter/FontSelector.tsx
+++ b/components/converter/FontSelector.tsx
@@ -23,6 +23,7 @@ export function FontSelector({ fonts, selectedFont, onSelectFont, className }: F
const [filter, setFilter] = React.useState('all');
const [favorites, setFavorites] = React.useState([]);
const [recentFonts, setRecentFonts] = React.useState([]);
+ const searchInputRef = React.useRef(null);
// Load favorites and recent fonts
React.useEffect(() => {
@@ -30,6 +31,26 @@ export function FontSelector({ fonts, selectedFont, onSelectFont, className }: F
setRecentFonts(getRecentFonts());
}, []);
+ // Keyboard shortcuts
+ React.useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ // "/" to focus search
+ if (e.key === '/' && !e.ctrlKey && !e.metaKey) {
+ e.preventDefault();
+ searchInputRef.current?.focus();
+ }
+ // "Esc" to clear search
+ if (e.key === 'Escape' && searchQuery) {
+ e.preventDefault();
+ setSearchQuery('');
+ searchInputRef.current?.blur();
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [searchQuery]);
+
// Initialize Fuse.js for fuzzy search
const fuse = React.useMemo(() => {
return new Fuse(fonts, {
@@ -118,8 +139,9 @@ export function FontSelector({ fonts, selectedFont, onSelectFont, className }: F
setSearchQuery(e.target.value)}
className="pl-9 pr-9"
diff --git a/components/layout/ThemeToggle.tsx b/components/layout/ThemeToggle.tsx
index a11f7d2..8dcc5f0 100644
--- a/components/layout/ThemeToggle.tsx
+++ b/components/layout/ThemeToggle.tsx
@@ -7,6 +7,13 @@ import { Button } from '@/components/ui/Button';
export function ThemeToggle() {
const [theme, setTheme] = React.useState<'light' | 'dark'>('light');
+ const toggleTheme = React.useCallback(() => {
+ const newTheme = theme === 'light' ? 'dark' : 'light';
+ setTheme(newTheme);
+ localStorage.setItem('theme', newTheme);
+ document.documentElement.classList.toggle('dark', newTheme === 'dark');
+ }, [theme]);
+
React.useEffect(() => {
// Check for saved theme preference or default to light
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null;
@@ -17,12 +24,18 @@ export function ThemeToggle() {
document.documentElement.classList.toggle('dark', initialTheme === 'dark');
}, []);
- const toggleTheme = () => {
- const newTheme = theme === 'light' ? 'dark' : 'light';
- setTheme(newTheme);
- localStorage.setItem('theme', newTheme);
- document.documentElement.classList.toggle('dark', newTheme === 'dark');
- };
+ // Keyboard shortcut: Ctrl/Cmd + D
+ React.useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'd' && (e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ toggleTheme();
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [toggleTheme]);
return (