diff --git a/src/app/page.tsx b/src/app/page.tsx index 522e0f9..d89ff4f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import Link from 'next/link'; @@ -51,41 +51,19 @@ const flowSteps = [ { inputIcon: '\u{1F3B5}', inputLabel: '.FLAC', outputIcon: '\u{1F3A7}', outputLabel: '.MP3' }, ]; -/* ─── Format Marquee Data ─── */ +/* ─── Terminal Simulation Data ─── */ -const marqueeRows = [ - { - label: 'Images', - color: '#f472b6', - colorLight: 'rgba(244,114,182,0.08)', - formats: ['PNG', 'JPG', 'WebP', 'GIF', 'AVIF', 'SVG', 'PSD', 'HEIC', 'BMP', 'TIFF', 'ICO'], - direction: 'left' as const, - speed: '35s', - }, - { - label: 'Documents', - color: '#60a5fa', - colorLight: 'rgba(96,165,250,0.08)', - formats: ['PDF', 'DOCX', 'MD', 'HTML', 'TXT', 'RTF', 'PPTX', 'EPUB'], - direction: 'right' as const, - speed: '30s', - }, - { - label: 'Audio & Video', - color: '#a78bfa', - colorLight: 'rgba(167,139,250,0.08)', - formats: ['MP3', 'WAV', 'OGG', 'FLAC', 'AAC', 'M4A', 'MP4', 'WebM', 'AVI', 'MOV', 'MKV'], - direction: 'left' as const, - speed: '38s', - }, - { - label: 'Data & Fonts', - color: '#34d399', - colorLight: 'rgba(52,211,153,0.08)', - formats: ['CSV', 'JSON', 'XML', 'YAML', 'XLSX', 'TSV', 'TOML', 'INI', 'SQL', 'NDJSON', 'TTF', 'OTF', 'WOFF', 'WOFF2'], - direction: 'right' as const, - speed: '40s', - }, +const terminalCommands = [ + { cmd: 'transmute photo.heic --to webp', output: ' \u2713 photo.webp (2.4 MB \u2192 680 KB)', color: '#f472b6', time: 1800 }, + { cmd: 'transmute report.docx --to pdf', output: ' \u2713 report.pdf (formatting preserved)', color: '#60a5fa', time: 2200 }, + { cmd: 'transmute song.flac --to mp3 --quality 320k', output: ' \u2713 song.mp3 (48 MB \u2192 9.2 MB)', color: '#a78bfa', time: 2800 }, + { cmd: 'transmute data.csv --to json', output: ' \u2713 data.json (2,847 rows parsed)', color: '#34d399', time: 1400 }, + { cmd: 'transmute clip.mov --to mp4', output: ' \u2713 clip.mp4 (H.264, browser-native)', color: '#fb923c', time: 3200 }, + { cmd: 'transmute design.psd --to png', output: ' \u2713 design.png (composite layer)', color: '#f472b6', time: 1600 }, + { cmd: 'transmute book.epub --to pdf', output: ' \u2713 book.pdf (chapters preserved)', color: '#60a5fa', time: 2000 }, + { cmd: 'transmute font.ttf --to woff2', output: ' \u2713 font.woff2 (compressed 62%)', color: '#34d399', time: 1200 }, + { cmd: 'transmute slides.pptx --to html', output: ' \u2713 slides.html (12 slides)', color: '#a78bfa', time: 2400 }, + { cmd: 'transmute sheet.xlsx --to csv', output: ' \u2713 sheet.csv (3 sheets merged)', color: '#34d399', time: 1500 }, ]; /* ─── Animation Variants ─── */ @@ -332,6 +310,172 @@ function ConversionFlow() { ); } +/* ─── Terminal Simulation Component ─── */ + +interface TerminalLine { + type: 'prompt' | 'output' | 'blank'; + text: string; + color?: string; +} + +function TerminalSimulation() { + const [lines, setLines] = useState([]); + const [currentTyping, setCurrentTyping] = useState(''); + const [isTyping, setIsTyping] = useState(false); + const termRef = useRef(null); + const cmdIndexRef = useRef(0); + const runningRef = useRef(true); + + const scrollToBottom = useCallback(() => { + if (termRef.current) { + termRef.current.scrollTop = termRef.current.scrollHeight; + } + }, []); + + useEffect(() => { + runningRef.current = true; + + const typeCommand = async (cmdObj: typeof terminalCommands[0]) => { + if (!runningRef.current) return; + + // Type the command character by character + setIsTyping(true); + for (let i = 0; i <= cmdObj.cmd.length; i++) { + if (!runningRef.current) return; + setCurrentTyping(cmdObj.cmd.slice(0, i)); + await new Promise((r) => setTimeout(r, 30 + Math.random() * 40)); + } + + // Brief pause after typing + await new Promise((r) => setTimeout(r, 300)); + if (!runningRef.current) return; + + // "Execute" — move command to lines, show output + setIsTyping(false); + setCurrentTyping(''); + setLines((prev) => [ + ...prev, + { type: 'prompt', text: cmdObj.cmd }, + { type: 'output', text: cmdObj.output, color: cmdObj.color }, + ]); + scrollToBottom(); + + // Pause before next command + await new Promise((r) => setTimeout(r, 1200)); + }; + + const runLoop = async () => { + // Small initial delay + await new Promise((r) => setTimeout(r, 800)); + + while (runningRef.current) { + const cmd = terminalCommands[cmdIndexRef.current % terminalCommands.length]; + await typeCommand(cmd); + cmdIndexRef.current++; + + // After showing 6 commands, clear and start fresh to prevent infinite growth + if (cmdIndexRef.current % 6 === 0) { + await new Promise((r) => setTimeout(r, 600)); + if (!runningRef.current) return; + setLines([]); + } + } + }; + + runLoop(); + + return () => { + runningRef.current = false; + }; + }, [scrollToBottom]); + + // Auto-scroll on new lines + useEffect(() => { + scrollToBottom(); + }, [lines, currentTyping, scrollToBottom]); + + return ( +
+ {/* Terminal window */} +
+ {/* Title bar */} +
+
+
+
+
+
+ + transmute + +
+ + {/* Terminal body */} +
+ {/* Welcome message */} +
+ Transmute v1.0 {'\u2014'} 70+ formats, zero uploads +
+ + {/* Completed lines */} + {lines.map((line, i) => ( +
+ {line.type === 'prompt' ? ( +
+ {'>'} + {line.text} +
+ ) : line.type === 'output' ? ( +
+ {line.text} +
+ ) : null} +
+ ))} + + {/* Currently typing line */} + {(isTyping || currentTyping) && ( +
+ {'>'} + {currentTyping} + +
+ )} + + {/* Idle cursor */} + {!isTyping && !currentTyping && ( +
+ {'>'} + +
+ )} +
+
+ + {/* Format count below terminal */} +
+ {[ + { label: 'Images', count: 11, color: '#f472b6' }, + { label: 'Documents', count: 8, color: '#60a5fa' }, + { label: 'Audio/Video', count: 11, color: '#a78bfa' }, + { label: 'Data/Fonts', count: 14, color: '#34d399' }, + ].map((cat) => ( +
+
+ + {cat.count} {cat.label} + +
+ ))} +
+
+ ); +} + /* ─── Main Page ─── */ export default function LandingPage() { @@ -440,13 +584,13 @@ export default function LandingPage() { - {/* ──── FEATURES — SCROLLING MARQUEE ──── */} + {/* ──── FEATURES — TERMINAL SIMULATION ──── */}

- 70+ file formats across 5 categories, all converted instantly in your browser. + 70+ file formats. Drop anything in, get anything out.

- {/* Marquee rows */} -
- {marqueeRows.map((row, rowIndex) => ( - - {/* Category label — pinned left */} -
- - {row.label} - -
+ + + - {/* Fade edges */} -
-
- - {/* Scrolling track */} -
-
- {/* Duplicate the badges for seamless loop */} - {[...row.formats, ...row.formats].map((fmt, i) => ( - - .{fmt} - - ))} -
-
- - ))} -
- - {/* Total count callout */}