continue re-design

This commit is contained in:
Developers Digest
2025-09-05 13:06:17 -04:00
parent b96d048dbd
commit 836b085f75
270 changed files with 32269 additions and 5182 deletions
+19 -3
View File
@@ -59,10 +59,26 @@ export async function POST(request: NextRequest) {
case 'clear-old': case 'clear-old':
// Clear old conversation data but keep recent context // Clear old conversation data but keep recent context
if (!global.conversationState) { if (!global.conversationState) {
// Initialize conversation state if it doesn't exist
global.conversationState = {
conversationId: `conv-${Date.now()}`,
startedAt: Date.now(),
lastUpdated: Date.now(),
context: {
messages: [],
edits: [],
projectEvolution: { majorChanges: [] },
userPreferences: {}
}
};
console.log('[conversation-state] Initialized new conversation state for clear-old');
return NextResponse.json({ return NextResponse.json({
success: false, success: true,
error: 'No active conversation to clear' message: 'New conversation state initialized',
}, { status: 400 }); state: global.conversationState
});
} }
// Keep only recent data // Keep only recent data
+1 -1
View File
@@ -5,7 +5,7 @@ import type { SandboxState } from '@/types/sandbox';
// Store active sandbox globally // Store active sandbox globally
declare global { declare global {
var activeSandboxProvider: SandboxProvider | null; var activeSandboxProvider: any;
var sandboxData: any; var sandboxData: any;
var existingFiles: Set<string>; var existingFiles: Set<string>;
var sandboxState: SandboxState; var sandboxState: SandboxState;
+50 -6
View File
@@ -18,6 +18,12 @@ export const dynamic = 'force-dynamic';
const isUsingAIGateway = !!process.env.AI_GATEWAY_API_KEY; const isUsingAIGateway = !!process.env.AI_GATEWAY_API_KEY;
const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1'; const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1';
console.log('[generate-ai-code-stream] AI Gateway config:', {
isUsingAIGateway,
hasGroqKey: !!process.env.GROQ_API_KEY,
hasAIGatewayKey: !!process.env.AI_GATEWAY_API_KEY
});
const groq = createGroq({ const groq = createGroq({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GROQ_API_KEY, apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GROQ_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined, baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
@@ -152,10 +158,18 @@ export async function POST(request: NextRequest) {
const stream = new TransformStream(); const stream = new TransformStream();
const writer = stream.writable.getWriter(); const writer = stream.writable.getWriter();
// Function to send progress updates // Function to send progress updates with flushing
const sendProgress = async (data: any) => { const sendProgress = async (data: any) => {
const message = `data: ${JSON.stringify(data)}\n\n`; const message = `data: ${JSON.stringify(data)}\n\n`;
try {
await writer.write(encoder.encode(message)); await writer.write(encoder.encode(message));
// Force flush by writing a keep-alive comment
if (data.type === 'stream' || data.type === 'conversation') {
await writer.write(encoder.encode(': keepalive\n\n'));
}
} catch (error) {
console.error('[generate-ai-code-stream] Error writing to stream:', error);
}
}; };
// Start processing in background // Start processing in background
@@ -1169,15 +1183,22 @@ CRITICAL: When files are provided in the context:
// Determine which provider to use based on model // Determine which provider to use based on model
const isAnthropic = model.startsWith('anthropic/'); const isAnthropic = model.startsWith('anthropic/');
const isGoogle = model.startsWith('google/'); const isGoogle = model.startsWith('google/');
const isOpenAI = model.startsWith('openai/gpt-5'); const isOpenAI = model.startsWith('openai/');
const modelProvider = isAnthropic ? anthropic : (isOpenAI ? openai : (isGoogle ? googleGenerativeAI : groq)); const isKimiGroq = model === 'moonshotai/kimi-k2-instruct-0905';
const modelProvider = isAnthropic ? anthropic :
(isOpenAI ? openai :
(isGoogle ? googleGenerativeAI :
(isKimiGroq ? groq : groq)));
// Fix model name transformation for different providers // Fix model name transformation for different providers
let actualModel: string; let actualModel: string;
if (isAnthropic) { if (isAnthropic) {
actualModel = model.replace('anthropic/', ''); actualModel = model.replace('anthropic/', '');
} else if (model === 'openai/gpt-5') { } else if (isOpenAI) {
actualModel = 'gpt-5'; actualModel = model.replace('openai/', '');
} else if (isKimiGroq) {
// Kimi on Groq - use full model string
actualModel = 'moonshotai/kimi-k2-instruct-0905';
} else if (isGoogle) { } else if (isGoogle) {
// Google uses specific model names - convert our naming to theirs // Google uses specific model names - convert our naming to theirs
actualModel = model.replace('google/', ''); actualModel = model.replace('google/', '');
@@ -1186,6 +1207,8 @@ CRITICAL: When files are provided in the context:
} }
console.log(`[generate-ai-code-stream] Using provider: ${isAnthropic ? 'Anthropic' : isGoogle ? 'Google' : isOpenAI ? 'OpenAI' : 'Groq'}, model: ${actualModel}`); console.log(`[generate-ai-code-stream] Using provider: ${isAnthropic ? 'Anthropic' : isGoogle ? 'Google' : isOpenAI ? 'OpenAI' : 'Groq'}, model: ${actualModel}`);
console.log(`[generate-ai-code-stream] AI Gateway enabled: ${isUsingAIGateway}`);
console.log(`[generate-ai-code-stream] Model string: ${model}`);
// Make streaming API call with appropriate provider // Make streaming API call with appropriate provider
const streamOptions: any = { const streamOptions: any = {
@@ -1349,6 +1372,11 @@ It's better to have 3 complete files than 10 incomplete files.`
raw: true raw: true
}); });
// Debug: Log every 100 characters streamed
if (generatedCode.length % 100 < text.length) {
console.log(`[generate-ai-code-stream] Streamed ${generatedCode.length} chars`);
}
// Check for package tags in buffered text (ONLY for edits, not initial generation) // Check for package tags in buffered text (ONLY for edits, not initial generation)
let lastIndex = 0; let lastIndex = 0;
if (isEdit) { if (isEdit) {
@@ -1638,12 +1666,28 @@ Provide the complete file content without any truncation. Include all necessary
completionClient = openai; completionClient = openai;
} else if (model.includes('claude')) { } else if (model.includes('claude')) {
completionClient = anthropic; completionClient = anthropic;
} else if (model === 'moonshotai/kimi-k2-instruct-0905') {
completionClient = groq;
} else { } else {
completionClient = groq; completionClient = groq;
} }
// Determine the correct model name for the completion
let completionModelName: string;
if (model === 'moonshotai/kimi-k2-instruct-0905') {
completionModelName = 'moonshotai/kimi-k2-instruct-0905';
} else if (model.includes('openai')) {
completionModelName = model.replace('openai/', '');
} else if (model.includes('anthropic')) {
completionModelName = model.replace('anthropic/', '');
} else if (model.includes('google')) {
completionModelName = model.replace('google/', '');
} else {
completionModelName = model;
}
const completionResult = await streamText({ const completionResult = await streamText({
model: completionClient(modelMapping[model] || model), model: completionClient(completionModelName),
messages: [ messages: [
{ {
role: 'system', role: 'system',
+23 -21
View File
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import FirecrawlApp from '@mendable/firecrawl-js';
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
try { try {
@@ -8,43 +9,44 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: 'URL is required' }, { status: 400 }); return NextResponse.json({ error: 'URL is required' }, { status: 400 });
} }
// Use Firecrawl API to capture screenshot // Initialize Firecrawl with API key from environment
const firecrawlResponse = await fetch('https://api.firecrawl.dev/v1/scrape', { const apiKey = process.env.FIRECRAWL_API_KEY;
method: 'POST',
headers: { if (!apiKey) {
'Authorization': `Bearer ${process.env.FIRECRAWL_API_KEY}`, console.error("FIRECRAWL_API_KEY not configured");
'Content-Type': 'application/json' return NextResponse.json({
}, error: 'Firecrawl API key not configured'
body: JSON.stringify({ }, { status: 500 });
url, }
formats: ['screenshot'], // Regular viewport screenshot, not full page
const app = new FirecrawlApp({ apiKey });
// Use Firecrawl SDK to capture screenshot with the latest API
const scrapeResult = await app.scrapeUrl(url, {
formats: ['screenshot'], // Request screenshot format
waitFor: 3000, // Wait for page to fully load waitFor: 3000, // Wait for page to fully load
timeout: 30000, timeout: 30000,
blockAds: true, onlyMainContent: false, // Get full page for screenshot
actions: [ actions: [
{ {
type: 'wait', type: 'wait',
milliseconds: 2000 // Additional wait for dynamic content milliseconds: 2000 // Additional wait for dynamic content
} }
] ]
})
}); });
if (!firecrawlResponse.ok) { if (!scrapeResult.success) {
const error = await firecrawlResponse.text(); throw new Error(scrapeResult.error || 'Failed to capture screenshot');
throw new Error(`Firecrawl API error: ${error}`);
} }
const data = await firecrawlResponse.json(); if (!scrapeResult.data?.screenshot) {
throw new Error('Screenshot not available in response');
if (!data.success || !data.data?.screenshot) {
throw new Error('Failed to capture screenshot');
} }
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
screenshot: data.data.screenshot, screenshot: scrapeResult.data.screenshot,
metadata: data.data.metadata metadata: scrapeResult.data.metadata || {}
}); });
} catch (error: any) { } catch (error: any) {
+106
View File
@@ -0,0 +1,106 @@
import { NextRequest, NextResponse } from "next/server";
import FirecrawlApp from '@mendable/firecrawl-js';
export async function POST(request: NextRequest) {
try {
const { url, formats = ['markdown', 'html'], options = {} } = await request.json();
if (!url) {
return NextResponse.json(
{ error: "URL is required" },
{ status: 400 }
);
}
// Initialize Firecrawl with API key from environment
const apiKey = process.env.FIRECRAWL_API_KEY;
if (!apiKey) {
console.error("FIRECRAWL_API_KEY not configured");
// For demo purposes, return mock data if API key is not set
return NextResponse.json({
success: true,
data: {
title: "Example Website",
content: `This is a mock response for ${url}. Configure FIRECRAWL_API_KEY to enable real scraping.`,
description: "A sample website",
markdown: `# Example Website\n\nThis is mock content for demonstration purposes.`,
html: `<h1>Example Website</h1><p>This is mock content for demonstration purposes.</p>`,
metadata: {
title: "Example Website",
description: "A sample website",
sourceURL: url,
statusCode: 200
}
}
});
}
const app = new FirecrawlApp({ apiKey });
// Scrape the website using the latest SDK patterns
// Include screenshot if requested in formats
const scrapeResult = await app.scrapeUrl(url, {
formats: formats,
onlyMainContent: options.onlyMainContent !== false, // Default to true for cleaner content
waitFor: options.waitFor || 2000, // Wait for dynamic content
timeout: options.timeout || 30000,
...options // Pass through any additional options
});
// Handle the response according to the latest SDK structure
if (!scrapeResult.success) {
throw new Error(scrapeResult.error || "Failed to scrape website");
}
return NextResponse.json({
success: true,
data: {
title: scrapeResult.data?.metadata?.title || "Untitled",
content: scrapeResult.data?.markdown || scrapeResult.data?.html || "",
description: scrapeResult.data?.metadata?.description || "",
markdown: scrapeResult.data?.markdown || "",
html: scrapeResult.data?.html || "",
metadata: scrapeResult.data?.metadata || {},
screenshot: scrapeResult.data?.screenshot || null,
links: scrapeResult.data?.links || [],
// Include raw data for flexibility
raw: scrapeResult.data
}
});
} catch (error) {
console.error("Error scraping website:", error);
// Return a more detailed error response
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : "Failed to scrape website",
// Provide mock data as fallback for development
data: {
title: "Example Website",
content: "This is fallback content due to an error. Please check your configuration.",
description: "Error occurred while scraping",
markdown: `# Error\n\n${error instanceof Error ? error.message : 'Unknown error occurred'}`,
html: `<h1>Error</h1><p>${error instanceof Error ? error.message : 'Unknown error occurred'}</p>`,
metadata: {
title: "Error",
description: "Failed to scrape website",
statusCode: 500
}
}
}, { status: 500 });
}
}
// Optional: Add OPTIONS handler for CORS if needed
export async function OPTIONS(request: NextRequest) {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
+285
View File
@@ -0,0 +1,285 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
export default function BuilderPage() {
const [targetUrl, setTargetUrl] = useState<string>("");
const [selectedStyle, setSelectedStyle] = useState<string>("modern");
const [isLoading, setIsLoading] = useState(true);
const [previewUrl, setPreviewUrl] = useState<string>("");
const [progress, setProgress] = useState<string>("Initializing...");
const [generatedCode, setGeneratedCode] = useState<string>("");
const router = useRouter();
useEffect(() => {
// Get the URL and style from sessionStorage
const url = sessionStorage.getItem('targetUrl');
const style = sessionStorage.getItem('selectedStyle');
if (!url) {
router.push('/');
return;
}
setTargetUrl(url);
setSelectedStyle(style || "modern");
// Start the website generation process
generateWebsite(url, style || "modern");
}, [router]);
const generateWebsite = async (url: string, style: string) => {
try {
setProgress("Analyzing website...");
// For demo purposes, we'll generate a simple HTML template
// In production, this would call the actual scraping and generation APIs
const mockGeneratedCode = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${style} Website - Reimagined</title>
<style>
:root {
--primary: ${style === 'modern' ? '#FA5D19' : style === 'playful' ? '#9061ff' : style === 'professional' ? '#2a6dfb' : '#eb3424'};
--background: ${style === 'modern' ? '#ffffff' : style === 'playful' ? '#f9f9f9' : style === 'professional' ? '#f5f5f5' : '#fafafa'};
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--background);
color: #262626;
line-height: 1.6;
}
header {
background: white;
border-bottom: 1px solid #ededed;
padding: 2rem;
}
nav {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary);
}
main {
max-width: 1200px;
margin: 4rem auto;
padding: 0 2rem;
}
.hero {
text-align: center;
margin-bottom: 4rem;
}
h1 {
font-size: 3rem;
margin-bottom: 1rem;
background: linear-gradient(135deg, var(--primary), #262626);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 1.25rem;
color: #666;
}
.cta-button {
display: inline-block;
margin-top: 2rem;
padding: 1rem 2rem;
background: var(--primary);
color: white;
text-decoration: none;
border-radius: 0.5rem;
transition: transform 0.2s;
}
.cta-button:hover {
transform: scale(1.05);
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-top: 4rem;
}
.feature {
padding: 2rem;
background: white;
border-radius: 1rem;
border: 1px solid #ededed;
transition: box-shadow 0.2s;
}
.feature:hover {
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.feature h3 {
margin-bottom: 1rem;
color: var(--primary);
}
</style>
</head>
<body>
<header>
<nav>
<div class="logo">Reimagined</div>
<div>
<a href="#features" style="margin-right: 2rem; color: #666; text-decoration: none;">Features</a>
<a href="#about" style="margin-right: 2rem; color: #666; text-decoration: none;">About</a>
<a href="#contact" style="color: #666; text-decoration: none;">Contact</a>
</div>
</nav>
</header>
<main>
<div class="hero">
<h1>Welcome to Your ${style === 'modern' ? 'Modern' : style === 'playful' ? 'Playful' : style === 'professional' ? 'Professional' : 'Artistic'} Website</h1>
<p class="subtitle">Reimagined from ${url}</p>
<a href="#" class="cta-button">Get Started</a>
</div>
<div class="features" id="features">
<div class="feature">
<h3>Fast</h3>
<p>Lightning-fast performance optimized for modern web standards.</p>
</div>
<div class="feature">
<h3>Responsive</h3>
<p>Looks great on all devices, from mobile to desktop.</p>
</div>
<div class="feature">
<h3>Beautiful</h3>
<p>Stunning design that captures attention and drives engagement.</p>
</div>
</div>
</main>
</body>
</html>`;
setGeneratedCode(mockGeneratedCode);
// Create a blob URL for the preview
const blob = new Blob([mockGeneratedCode], { type: 'text/html' });
const blobUrl = URL.createObjectURL(blob);
setPreviewUrl(blobUrl);
setProgress("Website ready!");
setIsLoading(false);
// Show success message
toast.success("Website generated successfully!");
} catch (error) {
console.error("Error generating website:", error);
toast.error("Failed to generate website. Please try again.");
setProgress("Error occurred");
setTimeout(() => router.push('/'), 2000);
}
};
const downloadCode = () => {
const blob = new Blob([generatedCode], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'website.html';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast.success("Code downloaded!");
};
return (
<div className="min-h-screen bg-background-base">
<div className="flex h-screen">
{/* Sidebar */}
<div className="w-80 bg-white border-r border-border-faint p-24 flex flex-col">
<h2 className="text-title-small font-semibold mb-16">Building Your Website</h2>
<div className="space-y-12 flex-1">
<div>
<div className="text-label-small text-black-alpha-56 mb-4">Target URL</div>
<div className="text-body-medium text-accent-black truncate">{targetUrl}</div>
</div>
<div>
<div className="text-label-small text-black-alpha-56 mb-4">Style</div>
<div className="text-body-medium text-accent-black capitalize">{selectedStyle}</div>
</div>
<div>
<div className="text-label-small text-black-alpha-56 mb-4">Status</div>
<div className="text-body-medium text-heat-100">{progress}</div>
</div>
</div>
<div className="space-y-8">
{!isLoading && (
<button
onClick={downloadCode}
className="w-full py-12 px-16 bg-heat-100 hover:bg-heat-200 text-white rounded-10 text-label-medium transition-all"
>
Download Code
</button>
)}
<button
onClick={() => router.push('/')}
className="w-full py-12 px-16 bg-black-alpha-4 hover:bg-black-alpha-6 rounded-10 text-label-medium transition-all"
>
Start Over
</button>
</div>
</div>
{/* Preview */}
<div className="flex-1 bg-gray-50">
{isLoading ? (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="w-48 h-48 border-4 border-heat-100 border-t-transparent rounded-full animate-spin mb-16 mx-auto"></div>
<p className="text-body-large text-black-alpha-56">{progress}</p>
</div>
</div>
) : (
previewUrl && (
<iframe
src={previewUrl}
className="w-full h-full border-0"
title="Website Preview"
/>
)
)}
</div>
</div>
</div>
);
}
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
+1 -205
View File
@@ -1,205 +1 @@
@import "tailwindcss"; @import "../styles/main.css";
@keyframes slide {
0% { transform: translate(0, 0); }
100% { transform: translate(70px, 70px); }
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes sunPulse {
0%, 100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.5;
}
50% {
transform: translate(-50%, -50%) scale(1.05);
opacity: 0.7;
}
}
@keyframes orbShrink {
0% {
transform: translateX(-50%) translateY(45%) scale(1.5);
opacity: 0.2;
}
100% {
transform: translateX(-50%) translateY(45%) scale(1);
opacity: 1;
}
}
@keyframes gradient-shift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
@keyframes screenshot-pulse {
0%, 100% {
opacity: 0.2;
transform: scale(0.98);
}
50% {
opacity: 0.4;
transform: scale(1);
}
}
@keyframes camera-float {
0%, 100% {
transform: translateY(0px) rotate(0deg);
}
25% {
transform: translateY(-10px) rotate(-5deg);
}
75% {
transform: translateY(5px) rotate(5deg);
}
}
@keyframes lens-rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes pushUp {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInSmooth {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* Theme configuration for Tailwind CSS v4 */
@theme {
--color-background: hsl(0 0% 100%);
--color-foreground: hsl(240 10% 3.9%);
--color-card: hsl(0 0% 100%);
--color-card-foreground: hsl(240 10% 3.9%);
--color-popover: hsl(0 0% 100%);
--color-popover-foreground: hsl(240 10% 3.9%);
--color-primary: hsl(25 95% 53%);
--color-primary-foreground: hsl(0 0% 98%);
--color-secondary: hsl(240 4.8% 95.9%);
--color-secondary-foreground: hsl(240 5.9% 10%);
--color-muted: hsl(240 4.8% 95.9%);
--color-muted-foreground: hsl(240 3.8% 46.1%);
--color-accent: hsl(240 4.8% 95.9%);
--color-accent-foreground: hsl(240 5.9% 10%);
--color-destructive: hsl(0 84.2% 60.2%);
--color-destructive-foreground: hsl(0 0% 98%);
--color-border: hsl(240 5.9% 90%);
--color-input: hsl(240 5.9% 90%);
--color-ring: hsl(25 95% 53%);
--radius: 0.5rem;
}
@layer utilities {
/* Hide scrollbar for Chrome, Safari and Opera */
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
/* Radial gradient utilities */
.bg-gradient-radial {
background-image: radial-gradient(circle, var(--tw-gradient-stops));
}
/* Conic gradient utilities */
.bg-gradient-conic {
background-image: conic-gradient(var(--tw-gradient-stops));
}
}
@layer base {
* {
border-color: theme('colors.border');
}
body {
background-color: theme('colors.background');
color: theme('colors.foreground');
}
}
@layer utilities {
.animate-gradient-shift {
background-size: 400% 400%;
animation: gradient-shift 8s ease infinite;
}
.animate-camera-float {
animation: camera-float 3s ease-in-out infinite;
}
.animate-lens-rotate {
animation: lens-rotate 2s linear infinite;
}
.animation-delay-200 {
animation-delay: 200ms;
}
.animate-push-up {
animation: pushUp 0.4s ease-out forwards;
}
.animate-fade-in-smooth {
opacity: 0;
animation: fadeInSmooth 0.6s ease-out forwards;
}
.animate-fade-in-up {
opacity: 0;
animation: fadeInUp 0.5s ease-out forwards;
}
}
+90
View File
@@ -0,0 +1,90 @@
"use client";
import { useState } from "react";
import Link from "next/link";
// Import shared components
import { HeaderProvider } from "@/components/shared/header/HeaderContext";
import HeaderBrandKit from "@/components/shared/header/BrandKit/BrandKit";
import HeaderWrapper from "@/components/shared/header/Wrapper/Wrapper";
import HeaderDropdownWrapper from "@/components/shared/header/Dropdown/Wrapper/Wrapper";
import ButtonUI from "@/components/ui/shadcn/button";
// Import hero section components
import HomeHeroBackground from "@/components/app/(home)/sections/hero/Background/Background";
import { BackgroundOuterPiece } from "@/components/app/(home)/sections/hero/Background/BackgroundOuterPiece";
import HomeHeroBadge from "@/components/app/(home)/sections/hero/Badge/Badge";
import HomeHeroPixi from "@/components/app/(home)/sections/hero/Pixi/Pixi";
import HomeHeroTitle from "@/components/app/(home)/sections/hero/Title/Title";
import HeroInput from "@/components/app/(home)/sections/hero-input/HeroInput";
import { Connector } from "@/components/shared/layout/curvy-rect";
import HeroFlame from "@/components/shared/effects/flame/hero-flame";
import FirecrawlIcon from "@/components/FirecrawlIcon";
import FirecrawlLogo from "@/components/FirecrawlLogo";
export default function LandingPage() {
return (
<HeaderProvider>
<div className="min-h-screen bg-background-base">
{/* Header/Navigation Section */}
<HeaderDropdownWrapper />
<div className="sticky top-0 left-0 w-full z-[101] bg-background-base header">
<div className="absolute top-0 cmw-container border-x border-border-faint h-full pointer-events-none" />
<div className="h-1 bg-border-faint w-full left-0 -bottom-1 absolute" />
<div className="cmw-container absolute h-full pointer-events-none top-0">
<Connector className="absolute -left-[10.5px] -bottom-11" />
<Connector className="absolute -right-[10.5px] -bottom-11" />
</div>
<HeaderWrapper>
<div className="max-w-[900px] mx-auto w-full flex justify-between items-center">
<div className="flex gap-24 items-center">
<Link href="/" className="flex items-center gap-2">
<FirecrawlIcon className="w-7 h-7 text-accent-black" />
<FirecrawlLogo />
</Link>
</div>
<div className="flex gap-8">
<Link
href="https://github.com/mendableai/open-lovable"
target="_blank"
className="contents"
>
<ButtonUI variant="tertiary">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
Use this Template
</ButtonUI>
</Link>
</div>
</div>
</HeaderWrapper>
</div>
{/* Hero Section */}
<section className="overflow-x-clip" id="home-hero">
<div className="pt-28 lg:pt-254 lg:-mt-100 pb-115 relative" id="hero-content">
<HomeHeroPixi />
<HeroFlame />
<BackgroundOuterPiece />
<HomeHeroBackground />
<div className="relative container px-16">
<HomeHeroBadge />
<HomeHeroTitle />
{/* Hero Input */}
<div className="mt-24">
<HeroInput />
</div>
</div>
</div>
</section>
</div>
</HeaderProvider>
);
}
+25 -4
View File
@@ -1,11 +1,32 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter } from "next/font/google"; import { Inter, Roboto_Mono } from "next/font/google";
import localFont from "next/font/local";
import "./globals.css"; import "./globals.css";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({
subsets: ["latin"],
variable: "--font-inter"
});
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
const robotoMono = Roboto_Mono({
subsets: ["latin"],
variable: "--font-roboto-mono",
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Open Lovable", title: "Open Lovable v2",
description: "Re-imagine any website in seconds with AI-powered website builder.", description: "Re-imagine any website in seconds with AI-powered website builder.",
}; };
@@ -16,7 +37,7 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body className={inter.className}> <body className={`${inter.variable} ${geistSans.variable} ${geistMono.variable} ${robotoMono.variable} font-sans`}>
{children} {children}
</body> </body>
</html> </html>
+236
View File
@@ -0,0 +1,236 @@
"use client";
import Link from "next/link";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { appConfig } from '@/config/app.config';
import { toast } from "sonner";
// Import shared components
import { Connector } from "@/components/shared/layout/curvy-rect";
import HeroFlame from "@/components/shared/effects/flame/hero-flame";
import AsciiExplosion from "@/components/shared/effects/flame/ascii-explosion";
import { HeaderProvider } from "@/components/shared/header/HeaderContext";
// Import hero section components
import HomeHeroBackground from "@/components/app/(home)/sections/hero/Background/Background";
import { BackgroundOuterPiece } from "@/components/app/(home)/sections/hero/Background/BackgroundOuterPiece";
import HomeHeroBadge from "@/components/app/(home)/sections/hero/Badge/Badge";
import HomeHeroPixi from "@/components/app/(home)/sections/hero/Pixi/Pixi";
import HomeHeroTitle from "@/components/app/(home)/sections/hero/Title/Title";
import HeroInputSubmitButton from "@/components/app/(home)/sections/hero-input/Button/Button";
import Globe from "@/components/app/(home)/sections/hero-input/_svg/Globe";
// Import header components
import HeaderBrandKit from "@/components/shared/header/BrandKit/BrandKit";
import HeaderWrapper from "@/components/shared/header/Wrapper/Wrapper";
import HeaderDropdownWrapper from "@/components/shared/header/Dropdown/Wrapper/Wrapper";
import GithubIcon from "@/components/shared/header/Github/_svg/GithubIcon";
import ButtonUI from "@/components/ui/shadcn/button"
export default function HomePage() {
const [url, setUrl] = useState<string>("");
const [selectedStyle, setSelectedStyle] = useState<string>("modern");
const [selectedModel, setSelectedModel] = useState<string>(appConfig.ai.defaultModel);
const router = useRouter();
const styles = [
{ id: "modern", name: "Modern", description: "Clean and minimalist" },
{ id: "playful", name: "Playful", description: "Fun and colorful" },
{ id: "professional", name: "Professional", description: "Corporate and sleek" },
{ id: "artistic", name: "Artistic", description: "Creative and unique" },
];
const models = appConfig.ai.availableModels.map(model => ({
id: model,
name: model.includes('claude') ? `Claude ${model.split('-')[2]}` :
model.includes('gpt') ? `GPT-${model.split('-')[1]}` : model,
}));
const handleSubmit = () => {
if (!url.trim()) {
toast.error("Please enter a URL");
return;
}
// Store the configuration in sessionStorage
sessionStorage.setItem('targetUrl', url);
sessionStorage.setItem('selectedStyle', selectedStyle);
sessionStorage.setItem('selectedModel', selectedModel);
// Redirect to the generation interface
router.push('/generation');
};
return (
<HeaderProvider>
<div className="min-h-screen bg-background-base">
{/* Header/Navigation Section */}
<HeaderDropdownWrapper />
<div className="sticky top-0 left-0 w-full z-[101] bg-background-base header">
<div className="absolute top-0 cmw-container border-x border-border-faint h-full pointer-events-none" />
<div className="h-1 bg-border-faint w-full left-0 -bottom-1 absolute" />
<div className="cmw-container absolute h-full pointer-events-none top-0">
<Connector className="absolute -left-[10.5px] -bottom-11" />
<Connector className="absolute -right-[10.5px] -bottom-11" />
</div>
<HeaderWrapper>
<div className="max-w-[900px] mx-auto w-full flex justify-between items-center">
<div className="flex gap-24 items-center">
<HeaderBrandKit />
</div>
<div className="flex gap-8">
<a
className="contents"
href="https://github.com/mendableai/open-lovable"
target="_blank"
>
<ButtonUI variant="tertiary">
<GithubIcon />
Use this Template
</ButtonUI>
</a>
</div>
</div>
</HeaderWrapper>
</div>
{/* Hero Section */}
<section className="overflow-x-clip" id="home-hero">
<div className="pt-28 lg:pt-254 lg:-mt-100 pb-115 relative" id="hero-content">
<HomeHeroPixi />
<HeroFlame />
<BackgroundOuterPiece />
<HomeHeroBackground />
<div className="relative container px-16">
<HomeHeroBadge />
<HomeHeroTitle />
<p className="text-center text-body-large">
Re-imagine any website, in seconds.
</p>
<Link
className="bg-black-alpha-4 hover:bg-black-alpha-6 rounded-6 px-8 lg:px-6 text-label-large h-30 lg:h-24 block mt-8 mx-auto w-max gap-4 transition-all"
href="#"
onClick={(e) => e.preventDefault()}
>
Powered by Firecrawl.
</Link>
</div>
</div>
{/* Mini Playground Input */}
<div className="container lg:contents !p-16 relative -mt-90">
<div className="absolute top-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint lg:hidden" />
<div className="absolute bottom-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint lg:hidden" />
<Connector className="-top-10 -left-[10.5px] lg:hidden" />
<Connector className="-top-10 -right-[10.5px] lg:hidden" />
<Connector className="-bottom-10 -left-[10.5px] lg:hidden" />
<Connector className="-bottom-10 -right-[10.5px] lg:hidden" />
{/* Hero Input Component */}
<div className="max-w-552 mx-auto w-full z-[11] lg:z-[2]">
<div className="rounded-20 -mt-30 lg:-mt-30">
<div
className="overlay bg-accent-white rounded-20"
style={{
boxShadow:
"0px 0px 44px 0px rgba(0, 0, 0, 0.02), 0px 88px 56px -20px rgba(0, 0, 0, 0.03), 0px 56px 56px -20px rgba(0, 0, 0, 0.02), 0px 32px 32px -20px rgba(0, 0, 0, 0.03), 0px 16px 24px -12px rgba(0, 0, 0, 0.03), 0px 0px 0px 1px rgba(0, 0, 0, 0.05), 0px 0px 0px 10px #F9F9F9",
}}
/>
<div className="p-16 flex gap-12 items-center w-full relative">
<Globe />
<input
className="flex-1 bg-transparent text-body-input text-accent-black placeholder:text-black-alpha-48 focus:outline-none focus:ring-0 focus:border-transparent"
placeholder="example.com"
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleSubmit();
}
}}
/>
<div
onClick={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<HeroInputSubmitButton dirty={url.length > 0} />
</div>
</div>
{/* Options Section */}
{url.length > 0 && (
<div className="px-16 pb-16">
{/* Model Selector */}
<div className="mb-12">
<label className="text-label-small text-black-alpha-56 mb-6 block">AI Model</label>
<div className="grid grid-cols-2 lg:grid-cols-3 gap-8">
{models.map((model) => (
<button
key={model.id}
onClick={() => setSelectedModel(model.id)}
className={`
p-8 rounded-8 border transition-all text-left
${selectedModel === model.id
? 'border-heat-100 bg-heat-4'
: 'border-border-faint hover:border-black-alpha-24 bg-white'
}
`}
>
<div className="text-label-small font-medium text-accent-black">
{model.name}
</div>
</button>
))}
</div>
</div>
{/* Style Selector */}
<div>
<label className="text-label-small text-black-alpha-56 mb-6 block">Style</label>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
{styles.map((style) => (
<button
key={style.id}
onClick={() => setSelectedStyle(style.id)}
className={`
p-12 rounded-10 border transition-all text-left
${selectedStyle === style.id
? 'border-heat-100 bg-heat-4'
: 'border-border-faint hover:border-black-alpha-24 bg-white'
}
`}
>
<div className="text-label-medium font-medium text-accent-black">
{style.name}
</div>
<div className="text-caption text-black-alpha-56 mt-2">
{style.description}
</div>
</button>
))}
</div>
</div>
</div>
)}
<div className="h-248 top-84 cw-768 pointer-events-none absolute overflow-clip -z-10">
<AsciiExplosion className="-top-200" />
</div>
</div>
</div>
</div>
</section>
</div>
</HeaderProvider>
);
}
+3659
View File
File diff suppressed because it is too large Load Diff
+189 -3373
View File
File diff suppressed because it is too large Load Diff
+182
View File
@@ -0,0 +1,182 @@
{
"heat-4": {
"hex": "fa5d190a",
"p3": "0.980392 0.364706 0.098039 / 0.039216"
},
"heat-8": {
"hex": "fa5d1914",
"p3": "0.980392 0.364706 0.098039 / 0.078431"
},
"heat-12": {
"hex": "fa5d191f",
"p3": "0.980392 0.364706 0.098039 / 0.121569"
},
"heat-16": {
"hex": "fa5d1929",
"p3": "0.980392 0.364706 0.098039 / 0.160784"
},
"heat-20": {
"hex": "fa5d1933",
"p3": "0.980392 0.364706 0.098039 / 0.200000"
},
"heat-40": {
"hex": "fa5d1966",
"p3": "0.980392 0.364706 0.098039 / 0.400000"
},
"heat-90": {
"hex": "fa5d19e6",
"p3": "0.980392 0.364706 0.098039 / 0.900000"
},
"heat-100": {
"hex": "fa5d19ff",
"p3": "0.980392 0.364706 0.098039 / 1.000000"
},
"accent-black": {
"hex": "262626ff",
"p3": "0.149020 0.149020 0.149020 / 1.000000"
},
"accent-white": {
"hex": "ffffffff",
"p3": "1.000000 1.000000 1.000000 / 1.000000"
},
"accent-amethyst": {
"hex": "9061ffff",
"p3": "0.564706 0.380392 1.000000 / 1.000000"
},
"accent-bluetron": {
"hex": "2a6dfbff",
"p3": "0.164706 0.427451 0.984314 / 1.000000"
},
"accent-crimson": {
"hex": "eb3424ff",
"p3": "0.921569 0.203922 0.141176 / 1.000000"
},
"accent-forest": {
"hex": "42c366ff",
"p3": "0.258824 0.764706 0.400000 / 1.000000"
},
"accent-honey": {
"hex": "ecb730ff",
"p3": "0.925490 0.717647 0.188235 / 1.000000"
},
"black-alpha-1": {
"hex": "00000003",
"p3": "0.000000 0.000000 0.000000 / 0.011765"
},
"black-alpha-2": {
"hex": "00000005",
"p3": "0.000000 0.000000 0.000000 / 0.019608"
},
"black-alpha-3": {
"hex": "00000008",
"p3": "0.000000 0.000000 0.000000 / 0.031373"
},
"black-alpha-4": {
"hex": "0000000a",
"p3": "0.000000 0.000000 0.000000 / 0.039216"
},
"black-alpha-5": {
"hex": "0000000d",
"p3": "0.000000 0.000000 0.000000 / 0.050980"
},
"black-alpha-6": {
"hex": "0000000f",
"p3": "0.000000 0.000000 0.000000 / 0.058824"
},
"black-alpha-7": {
"hex": "00000012",
"p3": "0.000000 0.000000 0.000000 / 0.070588"
},
"black-alpha-8": {
"hex": "00000014",
"p3": "0.000000 0.000000 0.000000 / 0.078431"
},
"black-alpha-10": {
"hex": "0000001a",
"p3": "0.000000 0.000000 0.000000 / 0.101961"
},
"black-alpha-12": {
"hex": "0000001f",
"p3": "0.000000 0.000000 0.000000 / 0.121569"
},
"black-alpha-16": {
"hex": "00000029",
"p3": "0.000000 0.000000 0.000000 / 0.160784"
},
"black-alpha-20": {
"hex": "00000033",
"p3": "0.000000 0.000000 0.000000 / 0.200000"
},
"black-alpha-24": {
"hex": "0000003d",
"p3": "0.000000 0.000000 0.000000 / 0.239216"
},
"black-alpha-32": {
"hex": "26262652",
"p3": "0.149020 0.149020 0.149020 / 0.321569"
},
"black-alpha-40": {
"hex": "26262666",
"p3": "0.149020 0.149020 0.149020 / 0.400000"
},
"black-alpha-48": {
"hex": "2626267a",
"p3": "0.149020 0.149020 0.149020 / 0.478431"
},
"black-alpha-56": {
"hex": "2626268f",
"p3": "0.149020 0.149020 0.149020 / 0.560784"
},
"black-alpha-64": {
"hex": "262626a3",
"p3": "0.149020 0.149020 0.149020 / 0.639216"
},
"black-alpha-72": {
"hex": "262626b8",
"p3": "0.149020 0.149020 0.149020 / 0.721569"
},
"black-alpha-88": {
"hex": "262626e0",
"p3": "0.149020 0.149020 0.149020 / 0.878431"
},
"white-alpha-56": {
"hex": "ffffff8f",
"p3": "1.000000 1.000000 1.000000 / 0.560784"
},
"white-alpha-72": {
"hex": "ffffffb8",
"p3": "1.000000 1.000000 1.000000 / 0.721569"
},
"border-faint": {
"hex": "edededff",
"p3": "0.929412 0.929412 0.929412 / 1.000000"
},
"border-muted": {
"hex": "e8e8e8ff",
"p3": "0.909804 0.909804 0.909804 / 1.000000"
},
"border-loud": {
"hex": "e6e6e6ff",
"p3": "0.901961 0.901961 0.901961 / 1.000000"
},
"illustrations-faint": {
"hex": "edededff",
"p3": "0.929412 0.929412 0.929412 / 1.000000"
},
"illustrations-muted": {
"hex": "e6e6e6ff",
"p3": "0.901961 0.901961 0.901961 / 1.000000"
},
"illustrations-default": {
"hex": "dbdbdbff",
"p3": "0.858824 0.858824 0.858824 / 1.000000"
},
"background-lighter": {
"hex": "fbfbfbff",
"p3": "0.984314 0.984314 0.984314 / 1.000000"
},
"background-base": {
"hex": "f9f9f9ff",
"p3": "0.976471 0.976471 0.976471 / 1.000000"
}
}
+15
View File
@@ -0,0 +1,15 @@
export default function FirecrawlIcon({ className = "w-5 h-5" }: { className?: string }) {
return (
<svg
className={className}
fill="none"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.7605 6.61389C13.138 6.79867 12.6687 7.21667 12.3251 7.67073C12.2513 7.76819 12.0975 7.69495 12.1268 7.57552C12.7848 4.86978 11.9155 2.6209 9.20582 1.51393C9.06836 1.4576 8.92527 1.58097 8.96132 1.72519C10.1939 6.67417 5.00941 6.25673 5.66459 11.8671C5.67585 11.9634 5.56769 12.0293 5.48882 11.973C5.2432 11.7967 4.96885 11.4288 4.78069 11.1702C4.72548 11.0942 4.60605 11.1156 4.5807 11.2063C4.43085 11.7482 4.35986 12.2586 4.35986 12.7656C4.35986 14.7373 5.37333 16.473 6.90734 17.4791C6.99522 17.5366 7.10789 17.4543 7.07804 17.3535C6.99917 17.0887 6.95466 16.8093 6.95128 16.5203C6.95128 16.3429 6.96255 16.1615 6.99015 15.9925C7.05438 15.5677 7.20197 15.1632 7.44985 14.7948C8.29995 13.5188 10.0041 12.2862 9.73199 10.6125C9.71453 10.5066 9.83959 10.4368 9.91846 10.5094C11.119 11.6063 11.3567 13.0817 11.1595 14.405C11.1426 14.5199 11.2868 14.5813 11.3595 14.4912C11.5432 14.2613 11.7674 14.0596 12.0113 13.9081C12.0722 13.8703 12.1533 13.8991 12.1764 13.9667C12.3121 14.3616 12.5138 14.7323 12.7042 15.1029C12.9318 15.5485 13.0529 16.0573 13.0338 16.5958C13.0242 16.8578 12.9808 17.1113 12.9082 17.3524C12.8772 17.4543 12.9887 17.5394 13.0783 17.4808C14.6134 16.4747 15.6275 14.739 15.6275 12.7662C15.6275 12.0806 15.5075 11.4085 15.2804 10.7787C14.8044 9.45766 13.5966 8.46561 13.9019 6.74403C13.9166 6.66178 13.8405 6.59023 13.7605 6.61389Z"
fill="currentColor"
/>
</svg>
);
}
+48
View File
@@ -0,0 +1,48 @@
export default function FirecrawlLogo() {
return (
<svg
fill="none"
height="15"
viewBox="0 0 79 15"
width="79"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.599609 14.4311V0.576888H9.45474V2.59564H2.87778V6.61335H8.30575V8.57272H2.87778V14.4311H0.599609Z"
fill="#262626"
/>
<path
d="M11.9737 2.87272C11.2407 2.87272 10.6663 2.33835 10.6663 1.58626C10.6663 0.83418 11.2407 0.299805 11.9737 0.299805C12.7067 0.299805 13.2812 0.83418 13.2812 1.58626C13.2812 2.33835 12.7067 2.87272 11.9737 2.87272ZM10.8842 14.4311V4.29772H13.0237V14.4311H10.8842Z"
fill="#262626"
/>
<path
d="M20.1527 4.29772H20.6281V6.29668H19.6772C17.7755 6.29668 17.1613 7.78105 17.1613 9.3446V14.4311H15.0219V4.29772H16.9236L17.1613 5.82168C17.6764 4.97064 18.4886 4.29772 20.1527 4.29772Z"
fill="#262626"
/>
<path
d="M26.1788 14.5498C22.9894 14.5498 20.9886 12.4915 20.9886 9.38418C20.9886 6.2571 22.9894 4.17897 25.9807 4.17897C28.9126 4.17897 30.8738 6.03939 30.9333 9.00814C30.9333 9.26543 30.9135 9.54251 30.8738 9.8196H23.2271V9.95814C23.2865 11.68 24.3761 12.8081 26.06 12.8081C27.3674 12.8081 28.3183 12.155 28.6155 11.0269H30.755C30.3984 13.0258 28.6947 14.5498 26.1788 14.5498ZM23.3064 8.25605H28.7145C28.5362 6.75189 27.4863 5.90085 26.0005 5.90085C24.6336 5.90085 23.4648 6.81126 23.3064 8.25605Z"
fill="#262626"
/>
<path
d="M37.2193 14.5498C34.1487 14.5498 32.1875 12.5508 32.1875 9.38418C32.1875 6.2571 34.2081 4.17897 37.2787 4.17897C39.8936 4.17897 41.5181 5.62376 41.9341 7.9196H39.6955C39.4182 6.7321 38.5664 5.9998 37.2391 5.9998C35.5156 5.9998 34.3864 7.38522 34.3864 9.38418C34.3864 11.3633 35.5156 12.729 37.2391 12.729C38.5465 12.729 39.4182 11.9769 39.6757 10.8092H41.9341C41.5379 13.105 39.8144 14.5498 37.2193 14.5498Z"
fill="#262626"
/>
<path
d="M48.6034 4.29772H49.0789V6.29668H48.128C46.2262 6.29668 45.6121 7.78105 45.6121 9.3446V14.4311H43.4726V4.29772H45.3744L45.6121 5.82168C46.1272 4.97064 46.9394 4.29772 48.6034 4.29772Z"
fill="#262626"
/>
<path
d="M54.3679 4.17897C57.0621 4.17897 58.6073 5.46543 58.6073 7.86022V14.4311H56.7451L56.5668 12.9863C55.8735 13.8967 54.9028 14.5498 53.2981 14.5498C51.0794 14.5498 49.5936 13.4613 49.5936 11.5811C49.5936 9.50293 51.0992 8.33522 53.9519 8.33522H56.4876V7.72168C56.4876 6.59355 55.6754 5.90085 54.2688 5.90085C53.001 5.90085 52.1491 6.4946 51.9907 7.38522H49.8908C50.1087 5.40605 51.8124 4.17897 54.3679 4.17897ZM53.6547 12.8873C55.4376 12.8873 56.4678 11.8383 56.4876 10.2748V9.91855H53.833C52.5057 9.91855 51.7728 10.4133 51.7728 11.4425C51.7728 12.2936 52.4859 12.8873 53.6547 12.8873Z"
fill="#262626"
/>
<path
d="M62.7912 14.4311L59.4829 4.29772H61.7413L64.0591 12.0561L66.3768 4.29772H68.3381L70.5568 12.0561L72.9538 4.29772H75.1329L71.7652 14.4311H69.4672L67.3277 7.54355L65.109 14.4311H62.7912Z"
fill="#262626"
/>
<path
d="M76.1601 14.4311V0.576888H78.2996V14.4311H76.1601Z"
fill="#262626"
/>
</svg>
);
}
+110
View File
@@ -0,0 +1,110 @@
"use client";
import { useState, KeyboardEvent } from "react";
interface HeroInputProps {
value: string;
onChange: (value: string) => void;
onSubmit: () => void;
placeholder?: string;
className?: string;
}
export default function HeroInput({
value,
onChange,
onSubmit,
placeholder = "Describe what you want to build...",
className = ""
}: HeroInputProps) {
const [isFocused, setIsFocused] = useState(false);
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
onSubmit();
}
};
return (
<div className={`max-w-552 mx-auto w-full relative z-[11] rounded-20 ${className}`}>
<div
className=""
/>
<div className="relative">
<label className="p-16 flex gap-8 items-start w-full relative border-b border-black-alpha-5">
<div className="mt-2 flex-shrink-0">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="opacity-40"
>
<circle cx="10" cy="10" r="9.5" stroke="currentColor"/>
<path d="M10 2C10 5.5 10 14.5 10 18" stroke="currentColor" strokeLinecap="round"/>
<path d="M2 10C5.5 10 14.5 10 18 10" stroke="currentColor" strokeLinecap="round"/>
<ellipse cx="10" cy="10" rx="3.5" ry="9.5" stroke="currentColor"/>
<ellipse cx="10" cy="10" rx="6" ry="9.5" stroke="currentColor"/>
</svg>
</div>
<textarea
className="w-full bg-transparent text-body-input text-accent-black placeholder:text-black-alpha-48 resize-none outline-none min-h-[24px] leading-6"
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={handleKeyDown}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
rows={1}
style={{
height: 'auto',
overflow: 'hidden'
}}
onInput={(e) => {
const target = e.target as HTMLTextAreaElement;
target.style.height = 'auto';
target.style.height = target.scrollHeight + 'px';
}}
/>
</label>
<div className="p-10 flex justify-end items-center relative">
<button
onClick={onSubmit}
disabled={!value.trim()}
className={`
button relative rounded-10 px-8 py-8 text-label-medium font-medium
flex items-center justify-center gap-6
${value.trim()
? 'button-primary text-accent-white active:scale-[0.995]'
: 'bg-black-alpha-4 text-black-alpha-24 cursor-not-allowed'
}
`}
>
{value.trim() && <div className="button-background absolute inset-0 rounded-10 pointer-events-none" />}
{value.trim() ? (
<>
<span className="px-6 relative">Re-imagine Site</span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8H13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M8.5 3.5L13 8L8.5 12.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</>
) : (
<div className="w-60 flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8H13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M8.5 3.5L13 8L8.5 12.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
)}
</button>
</div>
</div>
</div>
);
}
@@ -0,0 +1,842 @@
"use client";
import { motion, AnimatePresence } from "framer-motion";
import {
Globe,
FileText,
Code,
Shield,
Search,
Zap,
Database,
Lock,
CheckCircle2,
XCircle,
Loader2,
AlertCircle,
Bot,
Sparkles,
FileCode,
Network,
Info,
Eye
} from "lucide-react";
import { useEffect, useState } from "react";
import ScoreChart from "./ScoreChart";
import RadarChart from "./RadarChart";
import MetricBars from "./MetricBars";
interface ControlPanelProps {
isAnalyzing: boolean;
showResults: boolean;
url: string;
analysisData?: any;
onReset: () => void;
}
interface CheckItem {
id: string;
label: string;
description: string;
icon: any;
status: 'pending' | 'checking' | 'pass' | 'fail' | 'warning';
score?: number;
details?: string;
recommendation?: string;
actionItems?: string[];
tooltip?: string;
}
export default function ControlPanel({
isAnalyzing,
showResults,
url,
analysisData,
onReset,
}: ControlPanelProps) {
const [showAIAnalysis, setShowAIAnalysis] = useState(false);
const [aiInsights, setAiInsights] = useState<CheckItem[]>([]);
const [isAnalyzingAI, setIsAnalyzingAI] = useState(false);
const [combinedChecks, setCombinedChecks] = useState<CheckItem[]>([]);
const [checks, setChecks] = useState<CheckItem[]>([
{
id: 'heading-structure',
label: 'Heading Hierarchy',
description: 'H1-H6 structure',
icon: FileText,
status: 'pending',
},
{
id: 'readability',
label: 'Readability',
description: 'Content clarity',
icon: Globe,
status: 'pending',
},
{
id: 'meta-tags',
label: 'Metadata Quality',
description: 'Title, desc, author',
icon: FileCode,
status: 'pending',
},
{
id: 'semantic-html',
label: 'Semantic HTML',
description: 'Proper HTML5 tags',
icon: Code,
status: 'pending',
},
{
id: 'accessibility',
label: 'Accessibility',
description: 'Alt text & ARIA',
icon: Eye,
status: 'pending',
},
{
id: 'llms-txt',
label: 'LLMs.txt',
description: 'AI permissions',
icon: Bot,
status: 'pending',
},
{
id: 'robots-txt',
label: 'Robots.txt',
description: 'Crawler rules',
icon: Shield,
status: 'pending',
},
{
id: 'sitemap',
label: 'Sitemap',
description: 'Site structure',
icon: Network,
status: 'pending',
},
]);
const [overallScore, setOverallScore] = useState(0);
const [currentCheckIndex, setCurrentCheckIndex] = useState(-1);
const [selectedCheck, setSelectedCheck] = useState<string | null>(null);
const [hoveredCheck, setHoveredCheck] = useState<string | null>(null);
const [enhancedScore, setEnhancedScore] = useState(0);
const [viewMode, setViewMode] = useState<'grid' | 'chart' | 'bars'>('grid');
useEffect(() => {
if (analysisData && analysisData.checks && showResults) {
// Use real data from API
const mappedChecks = analysisData.checks.map((check: any) => ({
...check,
icon: checks.find(c => c.id === check.id)?.icon || FileText,
description: check.details || checks.find(c => c.id === check.id)?.description,
}));
setChecks(mappedChecks);
setCombinedChecks(mappedChecks); // Initialize with basic checks
setOverallScore(analysisData.overallScore || 0);
setCurrentCheckIndex(-1);
// If AI analysis should auto-start, handle the promise
if (analysisData.autoStartAI && analysisData.aiAnalysisPromise) {
console.log('Auto-starting AI analysis with promise');
setIsAnalyzingAI(true);
setShowAIAnalysis(true);
// Add placeholder AI tiles immediately with actual titles
const placeholderAIChecks = [
{
id: 'ai-loading-0',
label: 'Content Quality for AI',
description: 'Analyzing content signal ratio...',
icon: Sparkles,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-1',
label: 'Information Architecture',
description: 'Evaluating page structure...',
icon: Bot,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-2',
label: 'Crawlability Patterns',
description: 'Checking JavaScript usage...',
icon: Database,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-3',
label: 'AI Training Value',
description: 'Assessing training potential...',
icon: Network,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-4',
label: 'Knowledge Extraction',
description: 'Analyzing entity definitions...',
icon: FileCode,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-5',
label: 'Template Quality',
description: 'Reviewing semantic structure...',
icon: Shield,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-6',
label: 'Content Depth',
description: 'Measuring content richness...',
icon: Zap,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-7',
label: 'Machine Readability',
description: 'Testing extraction reliability...',
icon: Globe,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
}
];
// Add loading AI tiles with staggered animation
placeholderAIChecks.forEach((check, idx) => {
setTimeout(() => {
setCombinedChecks(prev => [...prev, check]);
}, 100 * (idx + 1));
});
// Handle the AI analysis promise
analysisData.aiAnalysisPromise
.then(async (aiResponse: any) => {
if (aiResponse) {
const data = await aiResponse.json();
if (data.success && data.insights) {
// Convert AI insights to CheckItem format
const aiChecks: CheckItem[] = data.insights.map((insight: any, idx: number) => ({
...insight,
icon: [Sparkles, Bot, Database, Network, FileCode, Shield, Zap, Globe][idx % 8],
description: insight.details?.substring(0, 60) + '...' || 'AI Analysis',
isAI: true,
}));
setAiInsights(aiChecks);
// Replace loading tiles with real AI tiles
setCombinedChecks(prev => {
// Remove loading tiles
const withoutLoading = prev.filter(c => !(c as any).isLoading);
// Add real AI tiles
return [...withoutLoading, ...aiChecks];
});
// Calculate enhanced score
if (data.insights.length > 0) {
const aiScores = data.insights.map((i: any) => i.score || 0);
const avgAiScore = aiScores.reduce((a: number, b: number) => a + b, 0) / aiScores.length;
const combinedScore = Math.round((overallScore * 0.6) + (avgAiScore * 0.4));
setEnhancedScore(combinedScore);
}
}
}
})
.catch(error => {
console.error('AI analysis error:', error);
// Remove loading tiles on error
setCombinedChecks(prev => prev.filter(c => !(c as any).isLoading));
})
.finally(() => {
setIsAnalyzingAI(false);
});
}
} else if (isAnalyzing) {
// Reset all checks when starting analysis
const resetChecks = checks.map(check => ({ ...check, status: 'pending' as const }));
setChecks(resetChecks);
setCombinedChecks(resetChecks); // Reset combined checks too
setCurrentCheckIndex(0);
setOverallScore(0);
// Visual animation while waiting for real results
const checkInterval = setInterval(() => {
setCurrentCheckIndex(prev => {
if (prev >= checks.length - 1) {
clearInterval(checkInterval);
return prev;
}
return prev + 1;
});
}, 200);
return () => clearInterval(checkInterval);
}
}, [isAnalyzing, showResults, analysisData]);
useEffect(() => {
if (currentCheckIndex >= 0 && currentCheckIndex < checks.length && isAnalyzing) {
// Mark current as checking during animation
setChecks(prev => prev.map((check, index) => {
if (index === currentCheckIndex) {
return { ...check, status: 'checking' };
}
if (index < currentCheckIndex) {
return { ...check, status: 'checking' };
}
return check;
}));
// Update combinedChecks to show the animation
setCombinedChecks(prev => prev.map((check, index) => {
if (index === currentCheckIndex) {
return { ...check, status: 'checking' };
}
if (index < currentCheckIndex) {
return { ...check, status: 'checking' };
}
return check;
}));
}
}, [currentCheckIndex, checks.length, isAnalyzing]);
const getStatusIcon = (status: CheckItem['status']) => {
switch (status) {
case 'checking':
return <Loader2 className="w-16 h-16 text-heat-100 animate-spin" />;
case 'pass':
return <CheckCircle2 className="w-16 h-16 text-accent-black" />;
case 'fail':
return <XCircle className="w-16 h-16 text-heat-200" />;
case 'warning':
return <AlertCircle className="w-16 h-16 text-heat-100" />;
default:
return <div className="w-16 h-16 rounded-full border border-black-alpha-8" />;
}
};
const getScoreColor = (score: number) => {
if (score >= 80) return "text-accent-black";
if (score >= 60) return "text-accent-black";
return "text-accent-black";
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.5 }}
className="w-full max-w-[1200px] mx-auto"
>
{/* Header */}
<motion.div
className="text-center mb-48 pt-24 md:pt-0"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<h2 className="text-title-h2 text-accent-black mb-12">AI Readiness Analysis</h2>
<p className="text-body-large text-black-alpha-64">Single-page snapshot of {url}</p>
{showResults && (
<>
{/* View Mode Toggle - Moved above score */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
className="mt-24 mb-20 flex justify-center gap-4"
>
<button
onClick={() => setViewMode('grid')}
className={`px-16 py-8 rounded-8 text-label-medium font-medium transition-all ${
viewMode === 'grid'
? 'bg-accent-black text-white shadow-md'
: 'bg-black-alpha-4 text-black-alpha-64 hover:bg-black-alpha-8'
}`}
>
Grid View
</button>
<button
onClick={() => setViewMode('chart')}
className={`px-16 py-8 rounded-8 text-label-medium font-medium transition-all ${
viewMode === 'chart'
? 'bg-accent-black text-white shadow-md'
: 'bg-black-alpha-4 text-black-alpha-64 hover:bg-black-alpha-8'
}`}
>
Radar Chart
</button>
<button
onClick={() => setViewMode('bars')}
className={`px-16 py-8 rounded-8 text-label-medium font-medium transition-all ${
viewMode === 'bars'
? 'bg-accent-black text-white shadow-md'
: 'bg-black-alpha-4 text-black-alpha-64 hover:bg-black-alpha-8'
}`}
>
Bar Chart
</button>
</motion.div>
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring", delay: 0.5 }}
className="flex justify-center"
>
<ScoreChart
score={enhancedScore > 0 ? enhancedScore : overallScore}
enhanced={enhancedScore > 0}
size={180}
/>
</motion.div>
</>
)}
</motion.div>
{/* Conditional rendering based on view mode */}
{viewMode === 'grid' && (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-12 mb-40 px-40 relative">
{combinedChecks.map((check, index) => {
const isActive = index === currentCheckIndex;
return (
<motion.div
key={check.id}
initial={(check as any).isAI ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 0.9 }}
animate={{
opacity: 1,
scale: isActive ? 1.05 : 1,
}}
transition={{
delay: (check as any).isAI ? 0 : index * 0.1,
scale: { type: "spring", stiffness: 300 }
}}
className={`
relative p-16 rounded-8 transition-all bg-accent-white border
${(check as any).isAI ? 'border-heat-100 border-opacity-40 bg-gradient-to-br from-accent-white to-heat-4' : 'border-black-alpha-8'}
${isActive ? 'border-heat-100 shadow-lg' : ''}
${check.status !== 'pending' && check.status !== 'checking' ? 'cursor-pointer hover:shadow-md' : ''}
${(check as any).isLoading ? 'animate-pulse' : ''}
`}
onClick={() => {
if (check.status !== 'pending' && check.status !== 'checking') {
setSelectedCheck(selectedCheck === check.id ? null : check.id);
}
}}
onMouseEnter={() => setHoveredCheck(check.id)}
onMouseLeave={() => setHoveredCheck(null)}
>
<div className="relative">
<div className="flex items-start justify-end mb-12">
{getStatusIcon(check.status)}
</div>
<h3 className="text-label-large mb-4 text-accent-black font-medium flex items-center gap-6">
{check.label}
{check.tooltip && !aiInsights.some(ai => ai.id === check.id) && (
<div className="relative inline-block">
<Info className="w-14 h-14 text-black-alpha-32 hover:text-black-alpha-64 transition-colors" />
<AnimatePresence>
{hoveredCheck === check.id && (
<motion.div
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 5 }}
className="absolute bottom-full left-1/2 -translate-x-1/2 mb-8 w-200 p-8 bg-accent-black text-white text-body-x-small rounded-6 shadow-lg z-50 pointer-events-none"
>
{check.tooltip}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-accent-black" />
</motion.div>
)}
</AnimatePresence>
</div>
)}
</h3>
<p className="text-body-small text-black-alpha-64">
{check.description}
</p>
{check.status !== 'pending' && check.status !== 'checking' && (
<>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-8"
>
<div className="h-2 bg-black-alpha-4 rounded-full overflow-hidden">
<motion.div
className={`
h-full rounded-full
${check.status === 'pass' ? 'bg-accent-black' : ''}
${check.status === 'warning' ? 'bg-heat-100' : ''}
${check.status === 'fail' ? 'bg-heat-200' : ''}
`}
initial={{ width: 0 }}
animate={{ width: `${check.score}%` }}
transition={{ duration: 0.5 }}
/>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="text-label-x-small text-black-alpha-32 mt-4 text-center"
>
Click for details
</motion.div>
</>
)}
</div>
{/* Expanded Details */}
<AnimatePresence>
{selectedCheck === check.id && check.details && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
className="mt-12 pt-12 border-t border-black-alpha-8"
>
<div className="space-y-6">
<div>
<div className="text-label-small text-black-alpha-48 mb-2">Status</div>
<div className="text-body-small text-accent-black">{check.details}</div>
</div>
<div>
<div className="text-label-small text-black-alpha-48 mb-2">Recommendation</div>
<div className="text-body-small text-black-alpha-64">{check.recommendation}</div>
{check.actionItems && check.actionItems.length > 0 && (
<ul className="mt-4 space-y-2">
{check.actionItems.map((item: string, i: number) => (
<li key={i} className="flex items-start gap-6 text-body-small text-black-alpha-64">
<span className="text-heat-100 mt-1"></span>
<span>{item}</span>
</li>
))}
</ul>
)}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
})}
</div>
)}
{/* Radar Chart View */}
{viewMode === 'chart' && showResults && (
<div>
<motion.div
className="flex justify-center gap-40 mb-40"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.3 }}
>
{/* Basic Analysis Chart */}
<div className="flex flex-col items-center">
<h3 className="text-label-large text-accent-black mb-16 font-medium">Basic Analysis</h3>
<RadarChart
data={checks
.filter(check => check.status !== 'pending' && check.status !== 'checking')
.slice(0, 8)
.map(check => ({
label: check.label.length > 12 ? check.label.substring(0, 12) + '...' : check.label,
score: check.score || 0
}))}
size={350}
/>
<div className="mt-16 text-center">
<div className="text-title-h3 text-accent-black">{overallScore}%</div>
<div className="text-label-small text-black-alpha-48">Overall Score</div>
</div>
</div>
{/* VS Indicator */}
{aiInsights.length > 0 && (
<motion.div
className="flex items-center"
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.2, type: "spring" }}
>
<div className="text-label-large text-black-alpha-32 font-medium">VS</div>
</motion.div>
)}
{/* AI Analysis Chart - Only show if AI insights exist */}
{aiInsights.length > 0 && (
<motion.div
className="flex flex-col items-center"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 }}
>
<h3 className="text-label-large text-heat-100 mb-16 font-medium">AI Enhanced Analysis</h3>
<RadarChart
data={aiInsights
.filter(check => check.status !== 'pending' && check.status !== 'checking')
.slice(0, 8)
.map(check => ({
label: check.label.length > 12 ? check.label.substring(0, 12) + '...' : check.label,
score: check.score || 0
}))}
size={350}
/>
<div className="mt-16 text-center">
<div className="text-title-h3 text-heat-100">
{Math.round(aiInsights.reduce((sum, check) => sum + (check.score || 0), 0) / aiInsights.length)}%
</div>
<div className="text-label-small text-heat-100 opacity-60">AI Score</div>
</div>
</motion.div>
)}
</motion.div>
{/* Comparison Summary */}
{aiInsights.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
className="text-center mb-20"
>
<div className="inline-flex items-center gap-8 px-16 py-8 bg-heat-4 rounded-8">
<span className="text-label-medium text-accent-black">
AI analysis found {aiInsights.filter(i => i.score && i.score < 50).length} additional areas for improvement
</span>
</div>
</motion.div>
)}
</div>
)}
{/* Bar Chart View */}
{viewMode === 'bars' && showResults && (
<motion.div
className="px-40 mb-40"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<MetricBars
metrics={combinedChecks
.filter(check => check.status !== 'pending' && check.status !== 'checking')
.map(check => ({
label: check.label,
score: check.score || 0,
status: check.status as 'pass' | 'warning' | 'fail',
category: (check as any).isAI ? 'ai' :
['robots-txt', 'sitemap', 'llms-txt'].includes(check.id) ? 'domain' : 'page',
details: check.details,
recommendation: check.recommendation,
actionItems: check.actionItems
}))}
/>
</motion.div>
)}
{/* Action Buttons */}
{showResults && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.8 }}
className="flex gap-12 justify-center"
>
<button
onClick={onReset}
className="px-20 py-10 bg-accent-white border border-black-alpha-8 hover:bg-black-alpha-4 rounded-8 text-label-medium transition-all"
>
Analyze Another Site
</button>
{true && (
<button
onClick={async () => {
setIsAnalyzingAI(true);
setShowAIAnalysis(true);
// Add placeholder AI tiles immediately with actual titles
const placeholderAIChecks = [
{
id: 'ai-loading-0',
label: 'Content Quality for AI',
description: 'Analyzing content signal ratio...',
icon: Sparkles,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-1',
label: 'Information Architecture',
description: 'Evaluating page structure...',
icon: Bot,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-2',
label: 'Crawlability Patterns',
description: 'Checking JavaScript usage...',
icon: Database,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-3',
label: 'AI Training Value',
description: 'Assessing training potential...',
icon: Network,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-4',
label: 'Knowledge Extraction',
description: 'Analyzing entity definitions...',
icon: FileCode,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-5',
label: 'Template Quality',
description: 'Reviewing semantic structure...',
icon: Shield,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-6',
label: 'Content Depth',
description: 'Measuring content richness...',
icon: Zap,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
},
{
id: 'ai-loading-7',
label: 'Machine Readability',
description: 'Testing extraction reliability...',
icon: Globe,
status: 'checking' as const,
score: 0,
isAI: true,
isLoading: true
}
];
// Add loading AI tiles with staggered animation immediately
placeholderAIChecks.forEach((check, idx) => {
setTimeout(() => {
setCombinedChecks(prev => [...prev, check]);
}, 100 * (idx + 1));
});
try {
const response = await fetch('/api/ai-analysis', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url,
htmlContent: analysisData?.htmlContent || '',
currentChecks: checks
})
});
const data = await response.json();
if (data.success && data.insights) {
// Convert AI insights to CheckItem format with AI flag
const aiChecks: CheckItem[] = data.insights.map((insight: any, idx: number) => ({
...insight,
icon: [Sparkles, Bot, Database, Network, FileCode, Shield, Zap, Globe][idx % 8],
description: insight.details?.substring(0, 60) + '...' || 'AI Analysis',
isAI: true, // Mark as AI-generated
}));
setAiInsights(aiChecks);
// Replace loading tiles with real AI tiles
setCombinedChecks(prev => {
// Remove loading tiles
const withoutLoading = prev.filter(c => !(c as any).isLoading);
// Add real AI tiles
return [...withoutLoading, ...aiChecks];
});
// Calculate enhanced score
if (data.insights.length > 0) {
const aiScores = data.insights.map((i: any) => i.score || 0);
const avgAiScore = aiScores.reduce((a: number, b: number) => a + b, 0) / aiScores.length;
const combinedScore = Math.round((overallScore * 0.6) + (avgAiScore * 0.4));
setEnhancedScore(combinedScore);
}
}
} catch (error) {
console.error('AI analysis error:', error);
// Remove loading tiles on error
setCombinedChecks(prev => prev.filter(c => !(c as any).isLoading));
} finally {
setIsAnalyzingAI(false);
}
}}
disabled={isAnalyzingAI}
className="px-20 py-10 bg-accent-black hover:bg-black-alpha-80 text-white rounded-8 text-label-medium transition-all disabled:opacity-50"
>
{isAnalyzingAI ? 'Analyzing...' : 'Analyze with AI'}
</button>
)}
</motion.div>
)}
</motion.div>
);
}
@@ -0,0 +1,341 @@
"use client";
import { motion, AnimatePresence } from "framer-motion";
import { Check, X, Zap, FileText, Shield, Globe, Code, Sparkles, AlertCircle } from "lucide-react";
import { useEffect, useState } from "react";
interface InlineResultsProps {
isAnalyzing: boolean;
showResults: boolean;
analysisStep: number;
url: string;
onReset: () => void;
}
const analysisSteps = [
"Fetching website content...",
"Checking for LLMs.txt...",
"Analyzing HTML structure...",
"Calculating AI readiness...",
];
// Placeholder data for the results
const mockResults = {
score: 78,
grade: "B+",
llmsTxt: true,
robotsTxt: true,
structuredData: true,
semanticHTML: false,
metaTags: true,
accessibility: true,
};
export default function InlineResults({
isAnalyzing,
showResults,
analysisStep,
url,
onReset,
}: InlineResultsProps) {
const [displayScore, setDisplayScore] = useState(0);
useEffect(() => {
if (showResults) {
// Animate score counting up
const target = mockResults.score;
const duration = 1500;
const increment = target / (duration / 16);
let current = 0;
const timer = setInterval(() => {
current += increment;
if (current >= target) {
setDisplayScore(target);
clearInterval(timer);
} else {
setDisplayScore(Math.floor(current));
}
}, 16);
return () => clearInterval(timer);
}
}, [showResults]);
const getScoreColor = (score: number) => {
if (score >= 80) return "#22c55e";
if (score >= 60) return "#eab308";
return "#ef4444";
};
return (
<AnimatePresence mode="wait">
{/* Analyzing State */}
{isAnalyzing && (
<motion.div
key="analyzing"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
className="space-y-20"
>
{/* Progress Bar */}
<div className="relative">
<div className="h-2 bg-black-alpha-4 rounded-full overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-heat-100 to-heat-200"
initial={{ width: "0%" }}
animate={{ width: `${((analysisStep + 1) / 4) * 100}%` }}
transition={{ duration: 0.5, ease: "easeOut" }}
/>
</div>
{/* Glowing dot at the end of progress */}
<motion.div
className="absolute top-1/2 -translate-y-1/2 w-3 h-3 bg-heat-100 rounded-full"
style={{
left: `${((analysisStep + 1) / 4) * 100}%`,
boxShadow: "0 0 20px rgba(255, 77, 0, 0.8)",
}}
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.8, 1],
}}
transition={{
duration: 0.8,
repeat: Infinity,
}}
/>
</div>
{/* Status Text */}
<motion.div
key={analysisStep}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
className="flex items-center gap-8 text-body-medium text-black-alpha-64"
>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
>
<Sparkles className="w-16 h-16 text-heat-100" />
</motion.div>
{analysisSteps[analysisStep]}
</motion.div>
{/* ASCII Animation */}
<motion.div
className="font-mono text-xs text-black-alpha-16 overflow-hidden h-32 relative"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
>
<motion.div
animate={{ y: [0, -10, 0] }}
transition={{ duration: 2, repeat: Infinity }}
className="absolute inset-0 flex items-center justify-center"
>
{'< analyzing />'}
</motion.div>
</motion.div>
</motion.div>
)}
{/* Results State */}
{showResults && (
<motion.div
key="results"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.4, ease: "easeOut" }}
className="space-y-24"
>
{/* Score Display */}
<div className="text-center">
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{
type: "spring",
stiffness: 200,
delay: 0.2
}}
className="relative inline-block"
>
{/* Background glow */}
<motion.div
className="absolute inset-0 rounded-full blur-xl"
style={{ background: getScoreColor(mockResults.score) }}
animate={{
opacity: [0.3, 0.6, 0.3],
scale: [0.8, 1.2, 0.8],
}}
transition={{
duration: 2,
repeat: Infinity,
}}
/>
{/* Score circle */}
<div
className="relative w-120 h-120 rounded-full flex flex-col items-center justify-center"
style={{
background: `conic-gradient(from 0deg, ${getScoreColor(mockResults.score)} ${displayScore * 3.6}deg, #f0f0f0 ${displayScore * 3.6}deg)`,
padding: "4px",
}}
>
<div className="w-full h-full bg-white rounded-full flex flex-col items-center justify-center">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="text-4xl font-bold"
style={{ color: getScoreColor(mockResults.score) }}
>
{displayScore}
</motion.div>
<motion.div
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.7 }}
className="text-label-medium text-black-alpha-48"
>
AI Ready
</motion.div>
</div>
</div>
</motion.div>
</div>
{/* Quick Checks Grid */}
<motion.div
className="grid grid-cols-3 gap-12"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}
>
{[
{
label: "LLMs.txt",
value: mockResults.llmsTxt,
icon: FileText,
description: "AI instructions",
detail: mockResults.llmsTxt ? "Found" : "Missing"
},
{
label: "Structured Data",
value: mockResults.structuredData,
icon: Code,
description: "Schema markup",
detail: mockResults.structuredData ? "Detected" : "Not found"
},
{
label: "Semantic HTML",
value: mockResults.semanticHTML,
icon: Globe,
description: "HTML5 tags",
detail: mockResults.semanticHTML ? "Good" : "Needs work"
},
].map((item, index) => (
<motion.div
key={item.label}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.9 + index * 0.1 }}
className={`
relative p-16 rounded-12 transition-all hover:shadow-md cursor-pointer
${item.value
? 'bg-gradient-to-br from-green-50 to-green-100/50 border-green-200'
: 'bg-gradient-to-br from-red-50 to-red-100/50 border-red-200'}
border
`}
>
{/* Status indicator */}
<div className="absolute top-12 right-12">
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 1 + index * 0.1, type: "spring" }}
className={`
w-24 h-24 rounded-full flex items-center justify-center
${item.value ? 'bg-green-500' : 'bg-red-500'}
`}
>
{item.value ? (
<Check className="w-14 h-14 text-white" strokeWidth={3} />
) : (
<X className="w-14 h-14 text-white" strokeWidth={3} />
)}
</motion.div>
</div>
{/* Icon */}
<div className="mb-12">
<item.icon className={`
w-24 h-24
${item.value ? 'text-green-600' : 'text-red-600'}
`} />
</div>
{/* Content */}
<div className="space-y-4">
<div className="text-label-medium text-accent-black">
{item.label}
</div>
<div className="text-body-small text-black-alpha-48">
{item.description}
</div>
<div className={`
text-label-small font-semibold
${item.value ? 'text-green-600' : 'text-red-600'}
`}>
{item.detail}
</div>
</div>
</motion.div>
))}
</motion.div>
{/* Quick Tip */}
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.2 }}
className="p-12 bg-heat-4 rounded-8 border border-heat-100 flex items-start gap-8"
>
<AlertCircle className="w-16 h-16 text-heat-100 mt-2" />
<div className="flex-1">
<div className="text-label-medium text-accent-black mb-4">Quick Tip</div>
<div className="text-body-small text-black-alpha-64">
{mockResults.semanticHTML
? "Your site has good semantic HTML structure for AI understanding."
: "Add semantic HTML5 elements to improve AI comprehension of your content."}
</div>
</div>
</motion.div>
{/* Action Buttons */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.4 }}
className="flex gap-8"
>
<button
onClick={onReset}
className="flex-1 px-16 py-10 bg-black-alpha-4 hover:bg-black-alpha-6 rounded-8 text-label-medium transition-all"
>
Try Another
</button>
<button className="flex-1 px-16 py-10 bg-heat-100 hover:bg-heat-200 text-white rounded-8 text-label-medium transition-all shadow-lg hover:shadow-xl">
View Details
</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}
@@ -0,0 +1,189 @@
"use client";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import { ChevronDown } from "lucide-react";
interface MetricBarsProps {
metrics: {
label: string;
score: number;
status: 'pass' | 'warning' | 'fail';
category?: 'page' | 'domain' | 'ai';
details?: string;
recommendation?: string;
actionItems?: string[];
}[];
}
export default function MetricBars({ metrics }: MetricBarsProps) {
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
const getBarColor = (score: number) => {
// Use brand orange colors with opacity for gradient effect
if (score >= 80) return 'bg-heat-100';
if (score >= 60) return 'bg-heat-90';
if (score >= 40) return 'bg-heat-40 opacity-80';
return 'bg-heat-20';
};
const getBulletColor = (score: number) => {
// Always use heat-100 for all bullets for consistency
return 'bg-heat-100';
};
const toggleExpanded = (label: string) => {
const newExpanded = new Set(expandedItems);
if (newExpanded.has(label)) {
newExpanded.delete(label);
} else {
newExpanded.add(label);
}
setExpandedItems(newExpanded);
};
// Sort metrics by score descending
const sortedMetrics = [...metrics].sort((a, b) => b.score - a.score);
return (
<div className="space-y-8 max-w-[800px] mx-auto">
{sortedMetrics.map((metric, index) => {
const isExpanded = expandedItems.has(metric.label);
return (
<motion.div
key={metric.label}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05, duration: 0.3 }}
className="space-y-0"
>
<div
className={`grid grid-cols-12 gap-4 items-center p-8 -m-8 rounded-8 cursor-pointer transition-all hover:bg-black-alpha-2 ${
isExpanded ? 'bg-black-alpha-4' : ''
}`}
onClick={() => toggleExpanded(metric.label)}
>
{/* Bullet and Label - fixed width */}
<div className="col-span-4 flex items-center gap-8">
<div className={`w-6 h-6 rounded-full ${getBulletColor(metric.score)}`} />
<span className="text-label-medium text-accent-black truncate">{metric.label}</span>
<motion.div
animate={{ rotate: isExpanded ? 180 : 0 }}
transition={{ duration: 0.2 }}
className="ml-auto"
>
<ChevronDown className="w-16 h-16 text-black-alpha-32" />
</motion.div>
</div>
{/* Bar container - flexible width */}
<div className="col-span-7 relative">
<div className="relative h-8 bg-black-alpha-8 rounded-full overflow-hidden">
{/* Animated bar */}
<motion.div
className={`absolute inset-y-0 left-0 ${getBarColor(metric.score)} rounded-full`}
initial={{ width: 0 }}
animate={{ width: `${Math.max(metric.score, 2)}%` }}
transition={{
delay: 0.2 + index * 0.05,
duration: 0.8,
ease: "easeOut"
}}
>
{/* Subtle inner glow */}
<div className="absolute inset-0 bg-gradient-to-t from-transparent to-white opacity-10 rounded-full" />
</motion.div>
{/* Score indicator lines at key thresholds */}
{[40, 60, 80].map(threshold => (
<div
key={threshold}
className="absolute top-0 bottom-0 w-px bg-black-alpha-8 opacity-30"
style={{ left: `${threshold}%` }}
/>
))}
</div>
</div>
{/* Score value - fixed width */}
<div className="col-span-1 text-right">
<span className="text-label-medium font-medium text-heat-100">
{metric.score}%
</span>
</div>
</div>
{/* Expanded Details */}
<AnimatePresence>
{isExpanded && metric.details && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
className="overflow-hidden"
>
<div className="pl-54 pr-12 py-12 space-y-8">
<div>
<div className="text-label-small text-black-alpha-48 mb-4">Status</div>
<div className="text-body-small text-accent-black">{metric.details}</div>
</div>
{metric.recommendation && (
<div>
<div className="text-label-small text-black-alpha-48 mb-4">Recommendation</div>
<div className="text-body-small text-black-alpha-64">{metric.recommendation}</div>
</div>
)}
{metric.actionItems && metric.actionItems.length > 0 && (
<div>
<div className="text-label-small text-black-alpha-48 mb-4">Action Items</div>
<ul className="space-y-4">
{metric.actionItems.map((item: string, i: number) => (
<li key={i} className="flex items-start gap-6 text-body-small text-black-alpha-64">
<span className="text-heat-100 mt-1"></span>
<span>{item}</span>
</li>
))}
</ul>
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
})}
{/* Summary stats */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="mt-20 pt-12 border-t border-black-alpha-8"
>
<div className="grid grid-cols-3 gap-16 text-center">
<div>
<div className="text-title-h3 text-heat-150">
{metrics.filter(m => m.status === 'pass').length}
</div>
<div className="text-label-small text-black-alpha-48">Passing</div>
</div>
<div>
<div className="text-title-h3 text-heat-100">
{metrics.filter(m => m.status === 'warning').length}
</div>
<div className="text-label-small text-black-alpha-48">Warning</div>
</div>
<div>
<div className="text-title-h3 text-heat-50">
{metrics.filter(m => m.status === 'fail').length}
</div>
<div className="text-label-small text-black-alpha-48">Failing</div>
</div>
</div>
</motion.div>
</div>
);
}
@@ -0,0 +1,220 @@
"use client";
import { motion } from "framer-motion";
import { useEffect, useState } from "react";
interface RadarChartProps {
data: {
label: string;
score: number;
maxScore?: number;
}[];
size?: number;
}
export default function RadarChart({ data, size = 300 }: RadarChartProps) {
const [isAnimated, setIsAnimated] = useState(false);
const center = size / 2;
const radius = (size / 2) - 60; // Increased padding for labels
const angleStep = (Math.PI * 2) / data.length;
useEffect(() => {
setIsAnimated(true);
}, []);
// Calculate points for the polygon
const getPoint = (value: number, index: number) => {
const angle = index * angleStep - Math.PI / 2;
const r = (value / 100) * radius;
return {
x: center + r * Math.cos(angle),
y: center + r * Math.sin(angle)
};
};
// Create polygon points string
const polygonPoints = data
.map((item, i) => {
const point = getPoint(isAnimated ? item.score : 0, i);
return `${point.x},${point.y}`;
})
.join(' ');
// Grid levels
const gridLevels = [20, 40, 60, 80, 100];
return (
<div className="relative">
<svg width={size} height={size} className="overflow-visible">
<defs>
<linearGradient id="radar-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#FF4A00" stopOpacity="0.8" />
<stop offset="100%" stopColor="#FF8533" stopOpacity="0.3" />
</linearGradient>
<filter id="radar-glow">
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
{/* Grid circles */}
{gridLevels.map((level) => (
<circle
key={level}
cx={center}
cy={center}
r={(level / 100) * radius}
fill="none"
stroke="rgba(0,0,0,0.05)"
strokeWidth="1"
/>
))}
{/* Axis lines */}
{data.map((_, i) => {
const angle = i * angleStep - Math.PI / 2;
const x2 = center + radius * Math.cos(angle);
const y2 = center + radius * Math.sin(angle);
return (
<line
key={i}
x1={center}
y1={center}
x2={x2}
y2={y2}
stroke="rgba(0,0,0,0.05)"
strokeWidth="1"
/>
);
})}
{/* Data polygon */}
<motion.polygon
points={polygonPoints}
fill="url(#radar-gradient)"
stroke="#FF4A00"
strokeWidth="2"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
filter="url(#radar-glow)"
/>
{/* Data points */}
{data.map((item, i) => {
const point = getPoint(item.score, i);
return (
<motion.circle
key={i}
cx={point.x}
cy={point.y}
r="4"
fill="#FF4A00"
stroke="white"
strokeWidth="2"
initial={{ scale: 0 }}
animate={{ scale: isAnimated ? 1 : 0 }}
transition={{ delay: 0.8 + i * 0.1, duration: 0.3 }}
/>
);
})}
{/* Labels */}
{data.map((item, i) => {
const angle = i * angleStep - Math.PI / 2;
const labelRadius = radius + 40; // Increased label distance
const x = center + labelRadius * Math.cos(angle);
const y = center + labelRadius * Math.sin(angle);
// Better text anchor logic based on quadrant
let textAnchor = "middle";
let dy = 0;
// Left side
if (x < center - 20) {
textAnchor = "end";
}
// Right side
else if (x > center + 20) {
textAnchor = "start";
}
// Top
if (y < center - 20) {
dy = -5;
}
// Bottom
else if (y > center + 20) {
dy = 5;
}
return (
<motion.g key={i}>
{/* Background for better readability */}
<motion.rect
x={x - (textAnchor === "middle" ? 30 : textAnchor === "end" ? 60 : 0)}
y={y - 10}
width={60}
height={20}
fill="white"
fillOpacity={0.9}
rx={4}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.9 + i * 0.05 }}
/>
<motion.text
x={x}
y={y + dy}
textAnchor={textAnchor as any}
dominantBaseline="middle"
className="text-xs fill-black-alpha-80 font-medium"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 + i * 0.05 }}
style={{ pointerEvents: 'none' }}
>
{item.label}
</motion.text>
{/* Score value */}
<motion.text
x={x}
y={y + dy + 12}
textAnchor={textAnchor as any}
dominantBaseline="middle"
className="text-[10px] fill-heat-100 font-bold"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.1 + i * 0.05 }}
style={{ pointerEvents: 'none' }}
>
{item.score}%
</motion.text>
</motion.g>
);
})}
</svg>
{/* Legend */}
<div className="mt-16 flex justify-center">
<div className="inline-flex flex-row gap-16 text-xs text-black-alpha-48 bg-white px-16 py-8 rounded-6 shadow-sm">
<div className="flex items-center gap-8">
<div className="w-12 h-12 rounded-full bg-heat-200" />
<span className="whitespace-nowrap">80-100%</span>
</div>
<div className="flex items-center gap-8">
<div className="w-12 h-12 rounded-full bg-heat-100" />
<span className="whitespace-nowrap">60-79%</span>
</div>
<div className="flex items-center gap-8">
<div className="w-12 h-12 rounded-full bg-heat-50" />
<span className="whitespace-nowrap">&lt;60%</span>
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,104 @@
"use client";
import { motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
interface ScoreChartProps {
score: number;
enhanced?: boolean;
size?: number;
}
export default function ScoreChart({ score, enhanced = false, size = 200 }: ScoreChartProps) {
const [animatedScore, setAnimatedScore] = useState(0);
const radius = size / 2 - 20;
const circumference = 2 * Math.PI * radius;
useEffect(() => {
const timer = setTimeout(() => {
setAnimatedScore(score);
}, 300);
return () => clearTimeout(timer);
}, [score]);
// Calculate stroke dash offset for the progress
const strokeDashoffset = circumference - (animatedScore / 100) * circumference;
// Determine color based on score
const getColor = () => {
if (score >= 80) return "#FF4A00"; // heat-200 - Excellent
if (score >= 60) return "#FF6500"; // heat-150 - Good
if (score >= 40) return "#FF8533"; // heat-100 - Warning
return "#FFA566"; // heat-50 - Poor
};
const getGradientId = enhanced ? "enhanced-gradient" : "normal-gradient";
return (
<div className="relative inline-flex items-center justify-center">
<svg width={size} height={size} className="transform -rotate-90">
<defs>
<linearGradient id={getGradientId} x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor={getColor()} stopOpacity="1" />
<stop offset="100%" stopColor={enhanced ? "#FF8533" : getColor()} stopOpacity="0.6" />
</linearGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
{/* Background circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="rgba(0,0,0,0.05)"
strokeWidth="12"
/>
{/* Progress circle */}
<motion.circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={`url(#${getGradientId})`}
strokeWidth="12"
strokeLinecap="round"
strokeDasharray={circumference}
initial={{ strokeDashoffset: circumference }}
animate={{ strokeDashoffset }}
transition={{ duration: 1.5, ease: "easeOut" }}
filter="url(#glow)"
/>
</svg>
{/* Center content */}
<div className="absolute inset-0 flex flex-col items-center justify-center">
<motion.div
className="text-4xl font-bold text-heat-150"
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.5, duration: 0.5 }}
>
{animatedScore}%
</motion.div>
{enhanced && (
<motion.div
className="text-xs text-heat-100 mt-1"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}
>
AI Enhanced
</motion.div>
)}
</div>
</div>
);
}
@@ -0,0 +1,188 @@
"use client";
import { animate } from "motion";
import { useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import initCanvas from "@/utils/init-canvas";
export default function EndpointsCrawl({
active,
alwaysHeat = false,
triggerOnHover = false,
size = 20,
}: {
active?: boolean;
alwaysHeat?: boolean;
triggerOnHover?: boolean;
size?: number;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const fnRefs = useRef<{
activate: () => void;
deactivate: () => void;
}>({ activate: () => {}, deactivate: () => {} });
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = initCanvas(canvas);
let isRunning = false;
let isActive = false;
let activeGroup = 0;
const rowAlphas = [0.2, 0.4, 1, 0.04];
const grid = [
[24],
[16, 18, 30, 32],
[8, 12, 36, 40],
[0, 3, 6, 21, 27, 42, 45, 48],
];
const scaler = size / 20;
const render = () => {
ctx.fillStyle = "#FF4C00";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const group of grid.slice(0, 4)) {
const groupIndex = grid.indexOf(group);
ctx.globalAlpha = rowAlphas[groupIndex];
for (const index of group) {
ctx.fillRect(
(3 + (index % 7) * 2) * scaler,
(3 + Math.floor(index / 7) * 2) * scaler,
2 * scaler,
2 * scaler,
);
}
}
if (isRunning) {
requestAnimationFrame(render);
}
};
const timeouts: number[] = [];
let runCount = 0;
const cycle = () => {
isRunning = true;
activeGroup = (activeGroup + 1) % 5;
rowAlphas.forEach((alpha, index) => {
let targetAlpha = alpha;
if (index === activeGroup) targetAlpha = 1;
else if (index === (activeGroup + 1) % 4) targetAlpha = 0.12;
else if (index === (activeGroup + 2) % 4) targetAlpha = 0.2;
else if (index === (activeGroup + 3) % 4) targetAlpha = 0.4;
animate(alpha, targetAlpha, {
duration: 0.05,
onUpdate: (value) => {
rowAlphas[index] = value;
},
});
});
timeouts.forEach((timeout) => {
window.clearTimeout(timeout);
});
timeouts.push(
window.setTimeout(() => {
isRunning = false;
}, 300),
);
if (activeGroup === 3) runCount += 1;
if ((runCount === 2 || !isActive) && activeGroup === 2) return;
timeouts.push(
window.setTimeout(() => {
cycle();
}, 50),
);
};
fnRefs.current = {
activate: () => {
if (isActive) return;
isActive = true;
runCount = 0;
cycle();
render();
},
deactivate: () => {
if (!isActive) return;
isActive = false;
},
};
render();
canvas.addEventListener("resize", render);
if (triggerOnHover) {
const group = canvasRef.current!.closest(".group");
if (group) {
group.addEventListener("mouseenter", fnRefs.current.activate);
group.addEventListener("mouseleave", fnRefs.current.deactivate);
return () => {
group.removeEventListener("mouseenter", fnRefs.current.activate);
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
};
}
}
}, [triggerOnHover, size]);
useEffect(() => {
if (triggerOnHover) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && active) {
fnRefs.current.activate();
} else {
fnRefs.current.deactivate();
}
},
{ threshold: 0.5 },
);
observer.observe(canvasRef.current!);
return () => {
observer.disconnect();
};
}, [active, triggerOnHover]);
return (
<canvas
className={cn(
alwaysHeat
? ""
: [
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
!active && "grayscale",
],
)}
ref={canvasRef}
style={{ width: size, height: size }}
/>
);
}
@@ -0,0 +1,187 @@
"use client";
import { animate } from "motion";
import { useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import initCanvas from "@/utils/init-canvas";
export default function EndpointsExtract({
active,
disabledCells,
alwaysHeat = false,
triggerOnHover = false,
size = 20,
}: {
active?: boolean;
disabledCells?: number[];
alwaysHeat?: boolean;
triggerOnHover?: boolean;
size?: number;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const fnRefs = useRef<{
activate: () => void;
deactivate: () => void;
}>({ activate: () => {}, deactivate: () => {} });
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = initCanvas(canvas);
let isRunning = false;
let isActive = false;
let activeCol = 0;
const colAlphas = [1, 0.4, 0.2, 0.12];
const scaler = size / 20;
const render = () => {
ctx.fillStyle = "#FF4C00";
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw Extract pattern - represents structured data extraction
// Draw columns to represent data fields
for (let col = 0; col < 4; col++) {
ctx.globalAlpha = colAlphas[col];
// Draw vertical bars of different heights to represent extracted data
const heights = [3, 2, 3, 1];
const startY = [1, 2, 1, 3];
for (let row = 0; row < heights[col]; row++) {
ctx.fillRect(
(3 + col * 4) * scaler,
(3 + startY[col] * 2 + row * 4) * scaler,
2 * scaler,
2 * scaler,
);
}
}
if (isRunning) {
requestAnimationFrame(render);
}
};
const timeouts: number[] = [];
let runCount = 0;
const cycle = () => {
isRunning = true;
activeCol = (activeCol + 1) % 4;
colAlphas.forEach((alpha, index) => {
let targetAlpha = alpha;
if (index === activeCol) targetAlpha = 1;
else if (index === (activeCol + 1) % 4) targetAlpha = 0.12;
else if (index === (activeCol + 2) % 4) targetAlpha = 0.2;
else if (index === (activeCol + 3) % 4) targetAlpha = 0.4;
animate(alpha, targetAlpha, {
duration: 0.05,
onUpdate: (value) => {
colAlphas[index] = value;
},
});
});
timeouts.forEach((timeout) => {
window.clearTimeout(timeout);
});
timeouts.push(
window.setTimeout(() => {
isRunning = false;
}, 400),
);
if (activeCol === 3) runCount += 1;
if ((runCount === 2 || !isActive) && activeCol === 0) return;
timeouts.push(
window.setTimeout(() => {
cycle();
}, 50),
);
};
fnRefs.current = {
activate: () => {
if (isActive) return;
isActive = true;
runCount = 0;
cycle();
render();
},
deactivate: () => {
if (!isActive) return;
isActive = false;
},
};
render();
canvas.addEventListener("resize", render);
if (triggerOnHover) {
const group = canvasRef.current!.closest(".group");
if (group) {
group.addEventListener("mouseenter", fnRefs.current.activate);
group.addEventListener("mouseleave", fnRefs.current.deactivate);
return () => {
group.removeEventListener("mouseenter", fnRefs.current.activate);
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
};
}
}
}, [disabledCells, size, triggerOnHover]);
useEffect(() => {
if (triggerOnHover) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && active) {
fnRefs.current.activate();
} else {
fnRefs.current.deactivate();
}
},
{ threshold: 0.5 },
);
observer.observe(canvasRef.current!);
return () => {
observer.disconnect();
};
}, [active, triggerOnHover]);
return (
<canvas
className={cn(
alwaysHeat
? ""
: [
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
!active && "grayscale",
],
)}
ref={canvasRef}
style={{ width: size, height: size }}
/>
);
}
@@ -0,0 +1,11 @@
"use client";
import { ComponentProps } from "react";
import EndpointsScrape from "@/components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape";
export default function EndpointsMap(
props: ComponentProps<typeof EndpointsScrape>,
) {
return <EndpointsScrape {...props} disabledCells={[1, 2, 3, 7, 9, 12, 15]} />;
}
@@ -0,0 +1,181 @@
"use client";
import { animate } from "motion";
import { useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import initCanvas from "@/utils/init-canvas";
export default function EndpointsScrape({
active,
disabledCells,
alwaysHeat = false,
triggerOnHover = false,
size = 20,
}: {
active?: boolean;
disabledCells?: number[];
alwaysHeat?: boolean;
triggerOnHover?: boolean;
size?: number;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const fnRefs = useRef<{
activate: () => void;
deactivate: () => void;
}>({ activate: () => {}, deactivate: () => {} });
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = initCanvas(canvas);
let isRunning = false;
let isActive = false;
let activeRow = 2;
const rowAlphas = [0.2, 0.4, 1, 0.12];
const scaler = size / 20;
const render = () => {
ctx.fillStyle = "#FF4C00";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 16; i++) {
if (disabledCells && disabledCells.includes(i)) continue;
ctx.globalAlpha = rowAlphas[Math.floor(i / 4)];
ctx.fillRect(
(3 + (i % 4) * 4) * scaler,
(3 + Math.floor(i / 4) * 4) * scaler,
2 * scaler,
2 * scaler,
);
}
if (isRunning) {
requestAnimationFrame(render);
}
};
const timeouts: number[] = [];
let runCount = 0;
const cycle = () => {
isRunning = true;
activeRow = (activeRow + 1) % 5;
rowAlphas.forEach((alpha, index) => {
let targetAlpha = alpha;
if (index === activeRow) targetAlpha = 1;
else if (index === (activeRow + 1) % 4) targetAlpha = 0.12;
else if (index === (activeRow + 2) % 4) targetAlpha = 0.2;
else if (index === (activeRow + 3) % 4) targetAlpha = 0.4;
animate(alpha, targetAlpha, {
duration: 0.05,
onUpdate: (value) => {
rowAlphas[index] = value;
},
});
});
timeouts.forEach((timeout) => {
window.clearTimeout(timeout);
});
timeouts.push(
window.setTimeout(() => {
isRunning = false;
}, 400),
);
if (activeRow === 3) runCount += 1;
if ((runCount === 2 || !isActive) && activeRow === 2) return;
timeouts.push(
window.setTimeout(() => {
cycle();
}, 50),
);
};
fnRefs.current = {
activate: () => {
if (isActive) return;
isActive = true;
runCount = 0;
cycle();
render();
},
deactivate: () => {
if (!isActive) return;
isActive = false;
},
};
render();
canvas.addEventListener("resize", render);
if (triggerOnHover) {
const group = canvasRef.current!.closest(".group");
if (group) {
group.addEventListener("mouseenter", fnRefs.current.activate);
group.addEventListener("mouseleave", fnRefs.current.deactivate);
return () => {
group.removeEventListener("mouseenter", fnRefs.current.activate);
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
};
}
}
}, [disabledCells, size, triggerOnHover]);
useEffect(() => {
if (triggerOnHover) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && active) {
fnRefs.current.activate();
} else {
fnRefs.current.deactivate();
}
},
{ threshold: 0.5 },
);
observer.observe(canvasRef.current!);
return () => {
observer.disconnect();
};
}, [active, triggerOnHover]);
return (
<canvas
className={cn(
alwaysHeat
? ""
: [
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
!active && "grayscale",
],
)}
ref={canvasRef}
style={{ width: size, height: size }}
/>
);
}
@@ -0,0 +1,142 @@
/* eslint-disable @stylistic/array-element-newline */
"use client";
import initCanvas from "@/utils/init-canvas";
import { animate } from "motion";
import { useEffect, useRef } from "react";
export default function EndpointsSearch({
alwaysHeat,
size = 20,
}: {
alwaysHeat?: boolean;
size?: number;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = initCanvas(canvas);
let isRunning = false;
let isActive = false;
let diff = 0;
const defaultRowAlphas = [
0, 0.2, 0.4, 0, 0.4, 1, 0.4, 0.2, 0.2, 0.4, 1, 0.4, 0, 0.4, 0.2, 0,
];
const differs = Array.from({ length: 16 }, () => 0.2 + Math.random() * 0.2);
differs[5] = 0.6;
differs[6] = 0.6;
differs[9] = 0.6;
differs[10] = 0.6;
const scaler = size / 20;
const render = () => {
ctx.fillStyle = "#FF4C00";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 16; i++) {
if ([0, 3, 12, 15].includes(i)) continue;
const maxAlpha = [5, 6, 9, 10].includes(i) ? 1 : 0.4;
const alpha = defaultRowAlphas[i] + diff * differs[i];
ctx.globalAlpha = Math.min(
Math.min(alpha, maxAlpha) - Math.max(alpha - maxAlpha, 0),
1,
);
ctx.fillRect(
(3 + (i % 4) * 4) * scaler,
(3 + Math.floor(i / 4) * 4) * scaler,
2 * scaler,
2 * scaler,
);
}
if (isRunning) {
requestAnimationFrame(render);
}
};
const timeouts: number[] = [];
let runCount = 0;
const duration = 300;
const cycle = () => {
isRunning = true;
animate(diff, 1, {
duration: duration / 1000,
onUpdate: (value) => {
diff = value < 0.5 ? value * 2 : 1 - (value - 0.5) * 2;
},
});
timeouts.forEach((timeout) => {
window.clearTimeout(timeout);
});
timeouts.push(
window.setTimeout(
() => {
isRunning = false;
},
Math.max(duration, 300),
),
);
runCount += 1;
if (runCount === 3 || !isActive) return;
timeouts.push(
window.setTimeout(() => {
cycle();
}, duration),
);
};
const activate = () => {
if (isActive) return;
isActive = true;
runCount = 0;
cycle();
render();
};
const deactivate = () => {
if (!isActive) return;
isActive = false;
};
render();
canvas.addEventListener("resize", render);
const group = canvasRef.current!.closest(".group");
if (group) {
group.addEventListener("mouseenter", activate);
group.addEventListener("mouseleave", deactivate);
return () => {
group.removeEventListener("mouseenter", activate);
group.removeEventListener("mouseleave", deactivate);
};
}
}, [size]);
return <canvas ref={canvasRef} style={{ width: size, height: size }} />;
}
@@ -0,0 +1,134 @@
/* eslint-disable @stylistic/array-element-newline */
"use client";
import { animate } from "motion";
import { useEffect, useRef } from "react";
import initCanvas from "@/utils/init-canvas";
export default function EndpointsExtract({ size = 20 }: { size?: number }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = initCanvas(canvas);
let isRunning = false;
let isActive = false;
let diff = 0;
const defaultRowAlphas = [
0.4, 0.04, 0.2, 0.4, 0.2, 0, 0, 0.04, 0.04, 0, 0, 0.2, 0.4, 0.2, 0.04,
0.4,
];
const differs = Array.from({ length: 16 }, () => 0.2 + Math.random() * 0.2);
const scaler = size / 20;
const render = () => {
ctx.fillStyle = "#FF4C00";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 16; i++) {
if ([5, 6, 9, 10].includes(i)) continue;
ctx.globalAlpha = defaultRowAlphas[i] + diff * differs[i];
ctx.globalAlpha =
Math.min(ctx.globalAlpha, 0.4) - Math.max(ctx.globalAlpha - 0.4, 0);
ctx.fillRect(
(3 + (i % 4) * 4) * scaler,
(3 + Math.floor(i / 4) * 4) * scaler,
2 * scaler,
2 * scaler,
);
}
ctx.globalAlpha = 1;
ctx.fillRect(7, 7, 6, 2);
ctx.globalAlpha = 0.4;
ctx.fillRect(7, 11, 2 + diff * 4, 2);
if (isRunning) {
requestAnimationFrame(render);
}
};
const timeouts: number[] = [];
let runCount = 0;
const duration = 300;
const cycle = () => {
isRunning = true;
animate(diff, 1, {
duration: duration / 1000,
onUpdate: (value) => {
diff = value < 0.5 ? value * 2 : 1 - (value - 0.5) * 2;
},
});
timeouts.forEach((timeout) => {
window.clearTimeout(timeout);
});
timeouts.push(
window.setTimeout(
() => {
isRunning = false;
},
Math.max(duration, 300),
),
);
runCount += 1;
if (runCount === 3 || !isActive) return;
timeouts.push(
window.setTimeout(() => {
cycle();
}, duration),
);
};
const activate = () => {
if (isActive) return;
isActive = true;
runCount = 0;
cycle();
render();
};
const deactivate = () => {
if (!isActive) return;
isActive = false;
};
render();
canvas.addEventListener("resize", render);
const group = canvasRef.current!.closest(".group");
if (group) {
group.addEventListener("mouseenter", activate);
group.addEventListener("mouseleave", deactivate);
return () => {
group.removeEventListener("mouseenter", activate);
group.removeEventListener("mouseleave", deactivate);
};
}
}, [size]);
return <canvas ref={canvasRef} style={{ width: size, height: size }} />;
}
@@ -0,0 +1,183 @@
"use client";
import { animate } from "motion";
import { useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import initCanvas from "@/utils/init-canvas";
export default function EndpointsMcp({
active,
alwaysHeat = false,
triggerOnHover = false,
size = 20,
}: {
active?: boolean;
alwaysHeat?: boolean;
triggerOnHover?: boolean;
size?: number;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const fnRefs = useRef<{
activate: () => void;
deactivate: () => void;
}>({ activate: () => {}, deactivate: () => {} });
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = initCanvas(canvas);
let isRunning = false;
let isActive = false;
let activeIndex = 5;
const rowAlphas = [0.12, 0.2, 0.4, 0.4, 1, 1, 1, 0.4, 0.2];
const scaler = size / 20;
const render = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#FF4C00";
for (let i = 0; i < 9; i++) {
ctx.globalAlpha = rowAlphas[i];
ctx.fillRect(
(5 + (i % 3) * 4) * scaler,
(5 + Math.floor(i / 3) * 4) * scaler,
2 * scaler,
2 * scaler,
);
}
if (isRunning) {
requestAnimationFrame(render);
}
};
const timeouts: number[] = [];
let runCount = 0;
const cycle = () => {
isRunning = true;
activeIndex = (activeIndex + 1) % 9;
rowAlphas.forEach((alpha, index) => {
let targetAlpha = alpha;
if (index === activeIndex) targetAlpha = 1;
else if (index === (activeIndex - 1 + 9) % 9) targetAlpha = 1;
else if (index === (activeIndex - 2 + 9) % 9) targetAlpha = 1;
else if (index === (activeIndex - 3 + 9) % 9) targetAlpha = 0.4;
else if (index === (activeIndex - 4 + 9) % 9) targetAlpha = 0.2;
else if (index === (activeIndex - 5 + 9) % 9) targetAlpha = 0.2;
else if (index === (activeIndex - 6 + 9) % 9) targetAlpha = 0.12;
else if (index === (activeIndex - 7 + 9) % 9) targetAlpha = 0.12;
else if (index === (activeIndex - 8 + 9) % 9) targetAlpha = 0.4;
animate(alpha, targetAlpha, {
duration: 30 / 1000,
onUpdate: (value) => {
rowAlphas[index] = value;
},
});
});
timeouts.forEach((timeout) => {
window.clearTimeout(timeout);
});
timeouts.push(
window.setTimeout(() => {
isRunning = false;
}, 300),
);
if (activeIndex === 7) runCount += 1;
if ((runCount === 2 || !isActive) && activeIndex === 6) return;
timeouts.push(
window.setTimeout(() => {
cycle();
}, 30),
);
};
fnRefs.current = {
activate: () => {
if (isActive) return;
isActive = true;
runCount = 0;
cycle();
render();
},
deactivate: () => {
if (!isActive) return;
isActive = false;
},
};
render();
canvas.addEventListener("resize", render);
if (triggerOnHover) {
const group = canvasRef.current!.closest(".group");
if (group) {
group.addEventListener("mouseenter", fnRefs.current.activate);
group.addEventListener("mouseleave", fnRefs.current.deactivate);
return () => {
group.removeEventListener("mouseenter", fnRefs.current.activate);
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
};
}
}
}, [size, triggerOnHover]);
useEffect(() => {
if (triggerOnHover) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && active) {
fnRefs.current.activate();
} else {
fnRefs.current.deactivate();
}
},
{ threshold: 0.5 },
);
observer.observe(canvasRef.current!);
return () => {
observer.disconnect();
};
}, [active, triggerOnHover]);
return (
<canvas
className={cn(
alwaysHeat
? ""
: [
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
!active && "grayscale",
],
)}
ref={canvasRef}
style={{ width: size, height: size }}
/>
);
}
@@ -0,0 +1,64 @@
"use client";
import { useEffect, useRef } from "react";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./data.json";
export default function HeroFlame() {
const ref = useRef<HTMLDivElement>(null);
const ref2 = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let index = 0;
const interval = setIntervalOnVisible({
element: wrapperRef.current,
callback: () => {
index++;
if (index >= data.length) index = 0;
ref.current!.innerHTML = data[index];
ref2.current!.innerHTML = data[index];
},
interval: 85,
});
return () => interval?.();
}, []);
return (
<div
className="cw-686 h-190 top-408 absolute flex gap-16 pointer-events-none select-none"
ref={wrapperRef}
>
<div className="flex-1 overflow-clip relative">
<div
className="text-black-alpha-20 font-ascii absolute bottom-0 -left-380 fc-decoration"
dangerouslySetInnerHTML={{ __html: data[0] }}
ref={ref}
style={{
whiteSpace: "pre",
fontSize: "9px",
lineHeight: "11px",
}}
/>
</div>
<div className="flex-1 overflow-clip relative">
<div
className="text-black-alpha-20 font-ascii absolute bottom-0 -right-380 -scale-x-100 fc-decoration"
dangerouslySetInnerHTML={{ __html: data[0] }}
ref={ref2}
style={{
whiteSpace: "pre",
fontSize: "9px",
lineHeight: "11px",
}}
/>
</div>
</div>
);
}
@@ -0,0 +1,50 @@
[
" \n \n \n \n \n . . \n .. ..+ \n .:. \n .. .. .:: \n +.. ..: :. \n .:..::. .. .. \n .--:::. .. ... .:. .. \n .. .:+=-::.:. . ...-.::. .. \n ::.... .:--+::..: ......:+....:. :.. .. \n ....... ::-=:::: ..:-:-...: .--..:: ......... \n .. . . . ..::-:-.. .-+-:::.. ...::::. .: ...::.:.. \n . -... ....: . . .--=+-::. :-=-:.... . .:..:: .:---:::::-::.... \n ..::........::=..... ...:-.. .:-=--+=-:. ..--:..=::.... . .:.. ..:---::::---=:::..:... \n ..........::::.:::::::-::.-.. ...::--==:. ..-::-+==-:... .-::....... ..--:. ..:=+==.---=-+-:::::::-.. \n . .....::......:: ::::-::.---=+-:..::-+==++X=-:. ..:-::-=-== ---.. .:.--::.. .:-==::=--X==-----====--::+:::+... \n ..-....-:..::-::=-=-:-::--===++=-==-----== X+=-:.::-==----+==+XX+=-::.:+--==--::. .:-+X=----+X=-=------===--::-:...:. .... \n ....::::...:-:-==+++=++==+++XX++==++--+-+==++++=-===+=---:-==+X:XXX+=-:-=-==++=-:. .:-=+=- -=X+X+===+---==--==--:..::...+....+ \n ..:::---.::.---=+==XXXXXXXX+XX++==++===--+===:+X+====+=--::--=+XXXXXXX+==++==+XX+=: ::::--=+++X++X+XXXX+=----==++.+=--::+::::+. ::.=... \n .:::-==-------=X+++XXXXXXXXXXX++==++.==-==-:-==+X++==+=-=--=++++X++:X:X+++X+-+X X+=---=-==+=+++XXXXX+XX=+=--=X++XXX==---::-+-::::.:..-..\n",
" \n \n \n \n .. \n . .+. \n \n .: \n : .. :. \n .. ... .. .. \n :...+. . .. :. . \n .=-::... . . ... .. \n .. .--=-::... .....=+.:. . . \n -:.... .:-=:...: .::...... .:. .. . ... \n .. .. . .:: :.:: .:-::.. . .-..:.: ........... \n ..= . .. .::-==.. .-=-::... ..:.. . ..:::.::.:... \n .+.:.:. ..-.:: . . ..:. .:=-==-::. .:--.. .. .. ... .:---:::::--::..... \n .. ..+::.......-::..: . ::--.. ..:::-==-:. ..::.. .:... ..-.. =..:=== ::::-+-=:...+.=.. \n .....= ....:::..:::::- ::=:. ..:=--==+:. .:-::-=-=--:... .:-..... .:-=:...-+==--:+:-+-=-:-:.:+... \n .....:...::.:: ::::-::----=+-::--:---+=XX=-:. ..-=-=::--==X==-:....-.:--::.. .::++-::--+=---:-:---=-=::...-.. \n ....:..:....:=:- ==--=-:--===++====---- -==+==-::--==-:-::--=XX ++=-:::---+===-:. .:-X+ ----=X==----: :=--.--::........... \n .+.:::::-..:-:-===++=++=++++X++=====-.--=X==++==----=--::+:-=+XXXXX++---=-==+++=:. ..:-+++---=+XX+++=-::-===-+=--:...:.......... \n ...::--- ::::--=+=+XXXXXXXX+++=====+===--=---==++==-=+=--::-==++XX+XXX++=+X==+XX+=-::-::--==++X++XXXXXXX=-::-+=++X+=-:::::::::-:=+..... \n ..: :-===----=-=+++++XX+XX-XX++X+==++==+=--:--==.XX+==++.===+++XX.++++XX++=X+=++XX+==--=--===+ +XXXXXXXXX+=--=X++.XX==--=-:---:::::::..:.\n",
" \n \n . \n . \n \n . : \n . .. \n . .. . \n . \n :.. . . \n .::... . \n ==-:: : . \n . . .-.=::. .:: ..+ . .=. \n . :.. ..+:..:-. .:.. .: .+. . .. \n . . .:.:-:. .-=-... ..-. ...:.. .. .. \n . . .. .. :. ..::--:.. .::-. . .: .:::-:....-...:. \n .. ......:: ::. .::+:::: .:=::==:.. ...... ... . . .::----:...::::::.... \n . .:.....:::::=:-. ..::--=-.. :-::--==.::.. . .: .. .::-:..:==--::..:::::.:.....: . \n +.. ...:-+...:.: :=---::..:::::-=+=:.. .::.:::--.++--:.. ..-:..+. .:=-::::==-:::::::-=-:.::... . \n -... .. .. .:---:+:-:-::-=+==--=-::.::-X=--::..::::.:.:--=XX+==--::::.:-+=-:. ..-=-::-::=++-::-::.:---::.. . \n ......... .:::-=+== -==+= =====--=-:::::-+=+=--:.:::..::.:-===XXX+=--::--+==+=-. ..-==-:.:-=XX=----:.:-=-=--:.. .+..... \n ...:::+:...:=:-+++XX++++.=-=-==-- ===-:.:=---=+=-:::--::..:-==++X+X+==--=+==+X+-:.......:-==++==++X:X++=-:::-==+=--:.......::::..+. \n ...:---::+:::--==++-XX++XX+=:=++=--====-:::+--==+=---== ---==++XXX++XX+.+XX===+X+=-:::-:--=====XX+-++XXX+-:--++=+X+--::::.:::::::..... \n ..::.-==---+- ++++.+++XXX-XX++:X+=-=X+==:-::.=+X+XXX+=+X++++XXXXXXXX=XX=- +++-==++X+==-==-=:=++XXXXXXXXXX++==+ ++X X+=---.--.-:.:::.:... \n",
" \n \n . \n : \n . \n . \n . \n \n \n :. . \n :-....:. .- \n ....... .. \n . .. .. :. \n .. ..:.:. .-.. . ...:... \n . .. ..... .:--:.. .::... . . .. ........ .:. . \n .. ..... ...: ..=..::-. .:.:=-:. . ... .. .:.:..:-...-:......... \n . .:: ..::.:.:.. . .:.::-.. ..=.:--==:.. . .::..+==::::+........ . \n .:.......:.:::.....:::..::==:.. :.....::-+X-:::. .-.:... ..-:..::=::-:-...:.:::... \n .. :::.:.-.:::-=---::=::...:--==-:. ..= ....:-++=--=-::.+...==-::. .--:....:=-:-:::.... ::.. \n +.... .::-==+-----------------:::..-==--::. ... ..::-=+:+++=-:::.-- =+=-:. ..=--:::::=++-=::-::.:=-::... .. \n ..:-.... :..:-+++=+=-===-------::--.::..:------:... :....::-=++XX+=--::----+X-:. ..-==-----+X+==-=-:::-=+=-::.. .....:...- \n ...:::::....:-- ==+X+====-----=+::-==-:::::----=-:.::-::::--=+XXXXXX+=-=+=-==++--:-:::.::=--=-==X++++X+=-:=:===++--::......:...... \n ...:----:::--.====+=++X+++======---=+=--:-::=+++X++=-====== ++XXXX.XXX +++-=--=+X+--::--:---==+XX+++XXXX+=--=+=++X+=-:::-::-:::....... \n .. ...:::--==:---++=+=====++ XXXXX++===+X+===+==-+X++++++++X+X++.XXXXXXXXXXXX ++======X+==-=--===+XXXXXXXXXXXX+=+++++X+XX=.--==--::-....:....\n",
" . \n . \n \n \n \n \n \n \n . ..: \n :. . .. \n ::.. \n . .. \n .: :::. :. .. \n .:..:. .:-. ::. . .: .. ..... \n ... . ... . . ... ..::-:. . . ... .:........:. \n .. =.... . ... .. .:.:-+-.. ....-=:....:.=... .. \n ... -..=::... . ......:::-: .::=+-:::+. ::-.. .:..::=:...:.......... \n ....:. ...:....:::=:.:-.. .::=--: ..:==:-::::. . .:---:. .:=:::. :::-.-...:...=. \n ..:.--::::::::::::::--:-:....--:::... ..:-==++---:.=.-::-=+-:. .:::.....--=-:.-.::.--:... \n ....... ..-=---==-=-==-::.-::+:--::..-:---:::....... ..---=+XXX+=-+:::--+X-:.. :--:.::::-++-::::::::==-:..: +.... \n ..:...:....:-=-=+:+=--==::--::-=::--:...:--.:-::....::::---+=+XXX.+=-:-----+=-::..... .::--:---=+++==--:..:-==+-:::.. ......+-. \n ..:-::::..::---=====+=== -:--:--::-==-::-::-==-==----=--::-==+:XXXXXX+==+=--==+=::..:::.::---++.+=+++X+=-:=:===+=--::...:..::...:. \n ....:-------==-=+=====XX+X+==------+=---=:==+=.==+=+++++X+==+XXXXXXXXXXX+++==--==+=-:-:-:---=X XX++XXXXX++===+==+++=------::: :....... \n .....-.:+:--==+======= ++=++X:XXXX+=:==XX==:++=-=++==+X+=+++:+XXXXX:-X.XXXXX+X++==-++++X+======++XXXXX-XXXX--XXX+==+++++X++==.---::::..=:. ..\n",
" \n \n \n \n \n \n \n . \n . . \n :. \n .. . .. \n . .. ::. . . \n . . .:+.. : .. . \n . . . ..-- .. .. ..: \n . .. .. . ...-:... . ...:-.. ... \n . ....-.-. ..:.. .. :: ...-::...... .. .. .:.:::=....:. .. \n -... .....:...:.:-. ...=.:: .:=-::::::. .::--. ....:.:. ..:.. ..: .. \n .::::::.:..+:....:..-=:-:. ..:..::. ..=------::...:..:=-.. ........::-:.. ..::.:. \n . . :::----:::::=::....+:=-:-..-::-::-... . .:-==++++++-:.-::-+-... .::.... .:==::.....:--+:.. . . \n .... .. .:-=-=+==---:-:=:::..::::-:..:-:::::::....=.:..::--=+XXX-=-:-:-:=--::.. :.::..:::-++--::...::==-:.:.. ....+. \n ...-.:.. ...:-==--=+--:-::::..::.:--:..:=:-+-:---::--:::+:-==++X:XX+-==--:--==-::.... ..::::-++==++:-::..:-==-:::.. .....+=. . \n -..:::::::--::--==--++=+==--::::::-=-:: =-=-- --=+=+++=++-==++XXXXX- X+==+-----==::..:-.::--=+.++=++XX++=-:-======:--::.:.-...:... \n ......:=:--=----=- -=-===XX+XX+=----.-+=-:-==-+==-.-=+=====-+ X+XXXXXXX:XXX+========++=------+++XXX+.+X=XXX.XX+======+++---:::-.:.=..:.... \n .......::::-=:===++ ==++=++XXXXXX:++++++=====++=+X++:=++=====++XXXXXXXXXXXXX+X++-===++XXX++=+==+X++XX++X-XXXX-XXX+==+++=+X:===-+-:::=..::....\n",
" \n \n \n \n \n \n \n \n \n . .. \n ...... \n ..::. \n : . . . \n ... : ....+: . . .. \n . .:. .... . .:. .... . ...:. ..-:.. .. \n . . ..- ...=.: ..:. .:-::::..=.. .:-. ...... .:. :.. \n ....::. .. -.......=: :::. ...-.. .- --+::::---.. ..:.. . .. +..::. ...:. \n . .:::::-:....::-........---:-...::..:. ....:--=--===+=:....:-.... .. .. .:-:.. ..::. . \n . .:::--:--::=:::::. ..::-: ..::-....: ..:......:--===+=X=-::.---::-.. ..:....::.==-::.. :---... . .-. \n .. ...::-:-:--::-::-::. ....::-...----:::::.:=:::-:::-====+XX+=--:::=--::. .::::-=== =+=::....:=-:..-. .... \n .+...... :::::-----=.--:-::...=..:=:...:---::.:--==+--=+:--+:+XXXXXXX=-==:+:---:::...:...-:--++===+XX=-::::=----:::...:...... . \n .....:=:- :::-:::-+--++===+=-::-.::-=:::-=+=--:.::-==+-:=++ +XXXXXXXXX++==+=-=+--=--::::::---+++==++XX-X+=+ ==---===--::.+....=.... \n .=....:.::.==--:-==--====XX XXX++=++=== ==--==+X+==---------==+XXXXXXXXXXXX++==== =+==++-=.=-=+++:=++=++XXXXXXXXX+=====++---:-::..:...:.... \n ......:::--=++= ====-===++XX-X XXXXX:X+====-==+XX:+++========+XXXXXXXXXXXXXXX++=++XX +++++=+++X:X+XX.++XXXXX .XX+++=:+++X+=.---::::...:=:...\n",
" \n \n \n \n \n \n \n \n .: . \n . .... \n \n :. ... \n . . ..:. . \n . ..:.. .. .:: .. . \n . .. .:. ..: .:...... .:.. .. :.. .. \n ..-::: .:... .:=:. .. :. ..::=.:: :::=:. . .=. .. . \n ......:.. ..:-.. .. .:-:.. :.... . . .::=-------+-:......:. .. .+.:. .. \n ...::......-:.::. ..:-:....:::.. .: ....:--=--=-=+-:.::::.... .. .::-=-::. :.... \n ..:::.:.::..:..::.. ....--=....=::=:.:...-..:-:.::==--=+++--::---:.... ...::-:--=+:.. .:.:.. . \n .....:: ..:-::--:+:.:-.. .:=..:-..=.--:....::-+-::=-:-.-=++==+XX+=-=-::-::... ... :.::-=-=++X=-:..:::-::.:....... \n ....::.........---====--=-::.:=:.:--=::-=.::....::--::==== XX +-++XX++==.-=-:. :::-..:...:-- =-.==+X++------------:..... .. .. \n .....+.-:::::::-:::+--=X++ ++=====+==---:--=X+=-::.::::--:--=+XXXXXX++X++-====--==:--==--=:-=-=+==+-==+X.XX++++:=----=-::..:.. ....=.. \n . .. ...::+-==------:--==XXXXXXXXXXXX+=------+++X+===--=-----==+XX-XXXXXX:-++==+=++=--==++=++XX++==++==++XXXX:X++++=-===+=--:+::.....=-.... \n ......::--==.+++=+===.=++XXXXXXXXXXXX=++=.=--=+++X+++==+=====+X-X-XXXXXXX X+++++++++++===:==:++XX+XXX++XXXXXXXX.+++++XX+XX+=-=-:::+:::.:.:..\n",
" \n \n \n \n \n \n \n . .. \n . \n .. \n . \n .. .:. ... \n ... ...... .. . \n . .. :. . ..:. .:: :. \n +... . ..:. . . .. .:.:..:=:. . . . \n .. ..: .:: ... . ..::-::::.:.:=-. ... .. ...::... \n .:.... ..+.: . ::. .::.. . ..::.::::==::--:..:.. . . .::-:-:. . \n . ........... .-.. .. .:. .-:...... .. ....::-=-::--=-::.:::. .. ....:+:-==.. .. .. \n :...... .:.-:.+....-....:. ..:: .::-.. ..::-:..=::::--+=--==+==-=-:.:...=. ...::---+=::... :::......... \n .. ...- ......::==----:-::.::=::+:-:.::-:.... ...-:::---=+==+:====+++++=:: . ...:: .-. .=.::--=-+=--::::::=-.:::. \n .......:......:..:--X+==:=+=:--=-=-:::--==+=--:....::-:.::-=XXXX++++X+===-:-::-:::.-:::-:=::-=------==++==-=+==-::+::.. .. .. \n +:......:-- ::-::::--+XXXXX++===--=-=-::-+++++==--::::::::-=+XXXXXXXX++==-==-----::--=++++++===--=---==+X++++====----=-:..... ......... \n .....:::::-=+==-+=+--==+X-XXXXX+=X+==--=---==+XX+==+--------=++X+XXXXX+XX++=-==++==------=++++X+=+++==++XXXX=X++==+===:+== :--:............\n .......:--.-====++.X+=++XXXXXXXXXXXXX+==---.===X+.+X+X++=.++++=XXX.XXXX+XX=X++++ +XX+X+==--====+XX+XXX XXXX XXX X+=+X++++++===-=-: :::=::....\n",
" \n \n \n \n \n \n . \n \n \n . \n .. \n .. ..... .... \n . .. ..: .::. . \n . .: . .... .:-. .. \n . ::. .=.:..::.. .-=. .. .. -.. \n ..: .: :..= .: :-::+:::.:-::-... .. ..:-:-:. \n . . ..:: .. .: .-:.. . ..+..::--:::--:::=:.. . .. ..=::=-. . \n .......::... ..:.=. .. .: .:.......... .-...::-+-:--=--:-:::. .. .=..: :=-.. ..:.. \n . .:.:=::=:..::....::..::: ...:.. ..+::.::.:-:--==:--== ++==:.:...... :... ...:=:-=-::.:.:::-:.-... \n .... .. ...:=+--=--=--:::---:::::::--.... ..:::::::-+X+===--==+===:-:: :...:-:::... ..::::-:-===---:::-- ::::. . \n .......:....::..::-XX+:+++=---------::=++===--:..-::.:.::-=XXXX+.++++=----.::::::+--===---:---:::::==+=======--::+:::.. .. .. \n =.....+::--:::=::+:-=+XXXXXX+=-=::-:-=:--==+X+==--::.:=:::-=+X=XXXX+X+==--=--=- -.::--==XX+==+===---+==+++:-=====--.-=-::.:.. ...-....= \n ....=.::+::---===X==-===XXXXXXXX+=X+---------=++++==++---==+=++XXXXXXXXXXX++====++==-------===+X+==+++++XXXXXX+=+=++====+=-=-=-::..-.::.... \n .... .:.---=:====+X++.+XXXXXXX+.+++++=-- -==++XX++X+XX+++XXXXXXXXXXXX+=+XX.++++=XXXXX+==--=-==++X.XXXXX XX= XXXX++.X+++ += =--=-::::::::.-..\n",
" \n \n \n \n \n \n . \n . \n \n . . \n . . . ..: \n . .:. :: \n .: .+. :: \n .. ... .. . .. ...= .:.:: .. .:. \n .: .. .:.. ....:..::..-.:..:.. ..:::+ \n ..: .. .:. ... .. :-..::..::. . . ...:. . \n .::. :. . : . .. . ...+::--:.:----+.... . . ...:: .. \n ..-:..... .-.....-..::... .::. .:..::..::----.:.:--+-=+:.::. .. .: ...-::... ....:+.. \n . .. ..:=-:--: :-:....:----....:::.. :.:.....:-=++=-:::----:=:.. :...::..-.. . ...::- -+::::::=:.....+ \n ...........:=X=++++==-:..::::=:-+=--:-::.....+..=.:-X.++X+=-===-:--:....:.:: :-=-:-:.-::..::---------.::.:..... .. . \n ......-:::=-:.::-=+XXXXX++--::..::-:==-==+=--:::...:..:-++XXXX++++=--::-:::::.:..::-++=---+---::::--==.==-----::::+:..::. ..... \n ......-::::--==-:-=-=+XX=XX+=--=-:::::::-=:=++=--:=:.:-====+XX:X-XXXX+==----+=.--: ::::====++==-----+=+++++++==------.:----:. . ...... \n ...:..:::::-----==+===++XX XXXX+=+==--:::---=++X+X++==--=++XX X++XXXXXXXXX+++=+XXX++=--:::--==-= ++++ XXXXXXXX:+++X+ ===----=--.:::..::..: \n .....:::--===--==+XX++XXXXXXXXX+++===------==+X+++XXXX:++XXXXXX+++++++=+XXXXX+++XXX-+X+= ----=.=+XXXXXXXXXXXXXX++XXXXX+++=.---==-:--:::-... \n",
" \n \n \n \n . \n . \n \n . . \n . \n : \n . . . \n .. . .: : \n .. . . . .. ... . . \n .. . . ....+ .. ... . . \n .. . . ... . ..::. :...:-. . . \n ... .. .. .:. :.. . .::.. ::.:::.::-. . \n ......... ..: ::..:-. .:. ... .. :-::.....::-.-:.... .: .... .. \n .:--::::..::. .::-:.=....:. ........:.:-=-:...:::::: .:.-....:. . ..... ....+.=...: \n .:.. ..:++=-=X=--:.......::=:---:::+.. ..=.:-==:-==++=:::::::. ....::::-::::.... ..:::=.::-::.... .. \n ...::-: ..:-+X+XX +==--.. ..:-==-----:-:.: .:---++++++++++=-::..:.. .......:====--:::::...::-:--:-::.::+..::..... +.. \n ..+....::--:::.:-=+XXXX++=--:.....::-=---=+--:.:....:-==+++X++=++=-::::::--:::+....-==---=--=:.:::--===+==-:-:::::::::::.. ..... \n :.. ......::+::-=--- ==+XX:XX+=----:-:.:::-==+=++=-:::+:-==+X:X+++X++X++==-==+X++==-:...:-=---=========++X-XXX+=+==----:+:::::......==.. \n ...+.::::---.::-=+XX=+X=:XXXXX+===--=--:=:-- ====X+===-=++=+XX+X++++XXXXX+X.+++++X++==-::=:-+-==++=+++XXXXXXXXX. :XX++++=-:- -==:::::::... \n .....::--=====--=+XXXXXXXXXXXX-+++++X+=---=++++-+XX++++XX++XXXX+++++++=+XX=XXX+XXXXXX++=------==+XXXXXXXXXXXXXXX+XXXXXXX+=---==----::=::... \n",
" \n \n \n \n \n \n \n . \n . \n . \n . . \n . .. \n . . .. .. .. \n : .. ....... . ..:. \n . .-. .. .:. ....:.::. \n . . .. .. .. .. +..:.. ..-::...- . ..= \n .::....:+..: .:. ....... ...........:::::...:.:.. ..- . . ..:... \n . . .-=-:-===--:. .:+::--=.... .:-==---:------::..... .....-.+.:::. . .......... . \n ...... .=.-++==+X++=-.. .:.--:::::.:.. ..-X==-===+==-- ::.... ...-------...... :..:.::::-.+... ..... \n .+..::....:-=++++X++---.. ...--.:-:--:-: .. ..-== +X++====--:..::::........:--:::::::-:.. :::--==++-.::.......... . \n .............::-::: ==+=+XX++--::::....::--==:=--:.=...:-==++:++++====-::-=X+=-=-.-:..+--+:.:-::----=--==++XXX ==-::::::...::.. ...... \n ....::::::-=::=-===-=+:XXXXX+=-=-:::: :..::-=+=+==-.::========++.+XXX:+X++==+==X+==--....:-:---=-=--=+:++X=XXXXXX.+====+::::-::..:+:.... \n .+..::::-==----:+X++++XXXXXX+== ==++=-::--=++X++X+==++X++-++==+++++XXXXXXXX++++X.X++=--::.:::-=++++++=X=XXXXX+XXX+X++XX+--::=--::::::... \n ....:: --=====---=+++XX.X=XXX++=+++X+=====+XX XX+XXXXXXXXXXX++= ++++X+=+XXXXXXXXX+X-XX..=-- =-==XXXXXXXXXXXXXXX+.XXXX-XXX=--------:::::.-. \n",
" \n \n \n \n \n \n \n \n \n \n . . \n . . .. .. \n .: . . .. \n . . .:.. .. .. \n .. . .. .+... . \n .. .::..: .. ... ..............:.+. . .. . \n .:..::::---=. ..:---:. . .=--=-:.-::..=:-:. . .. ..... . . .-. \n . .:==-----+=-:. :--:.=.-..... . .-===::---:-:::::. ..::-:--:. . .:.=.::--.. . \n ..... ..:-=== ===.-:: .::::..:: .:.... ..:==-=+=+=-=-=::.+.:.... .:-:::.. ...+... .:.:::-=++::.. .=.. \n . .. ..-...:..::+====.++=-:+::.... .::::---=::.. ..:::-=+==-+X+===-:.:-+=--::=:: ..::.:. .:..:+::=:::--=+XX+---:...::.... .. \n ..:.+....:--.::-:---=+X++++==-:::::......::==++--:..:==--=-=--==+X==++=---+-=+==--::. .:::: :-::::--+=--=+XXXXX++=---:=-:.:....=.... \n .. ..::::-=-::=++==+XX.XX+X+----==.--::.:-=+X+X+===-=X+===----== XXXX++++==++++++==-:....:::.==-=--==XXX=XXXX-X:++==-++=:::--:::::....= \n ...:.-::-====-.-=++:+XXXXXX+==-==++=----- =XXXXXXXXXXXXX++=---:==+X.XX.+XXXXXX+X+XX++++-:.::--=X++:X =XXXXX++.XX++X++X+++--::--:-::::.:. \n .-..::.-=====-=-=+++XXXXXXXX+:+++XX+=+===+XXXXXXXXXXXXXXX+======++XXX++XX XX.XXXXXX=++X=--====+XXXXXX+XX+X X++++XXXXXX++=--- ---- :::.: .. \n",
" \n \n \n \n \n \n \n \n \n . \n \n .. \n . \n . . .. \n ... : .. ......... .:.:. \n .:..+....-:. .:::-:. .:-:--=:.+.... .. . .:... \n .:==-::::-=:: .:::..... . .:==--:--::.-::.... .-.-:::. ... .::-:. \n . ..--=-------:.. ..::....:::.-. ......==--:--=-:=-+.+....... .:-:....- . .....:-==-:. \n .:......::-==--==--:..........::::=---.. ..:==--==---=-----...:=-+--:......:.... .. ........::--=++=-:.....:.. \n .....-.:::::::::-==+==--:-:.:.:... ...::-++=--:...:=---.-=---+=-==-::+==----.:-::....::..-:....::==::--=+X++=-::::+:-:.... -.. \n .......::-=-:=+=--++X++==-=-::-==::::.:.:-+XX+==---+==--=::::--+X=====--====-=+:--... ....:-=-:-::-=++++++XX++==.----==-::-:...=... \n ..:.::::------====+XX+X.++=---.==-:-::.:-=XXXXXXXX+++=- --:::--+XXXX+++.==++++++==-=-:....--++:===+XXX++XX+++X++==-===--:::::::::.... \n ....::---------==+=+X+++XX+=====+++==-:--=+XXXX+XXXX-++==--::--=+XXXXXXXXXXXXXXXXX+= =-:=:-- +XXXXXXXXXXXXX++=++++++++=--::-- :::....+ \n ....::-=====--=-++++X++++XXXXXXXXXX+++==+XXX:X+XXXXXXXXX=--.-==+XX-+XXXXXXXX+XX+XXX+++++=+++=+XXXXXXXXXXXX+++==+.++XX++=-=--=--- ::..:. . \n",
" \n \n \n \n \n \n \n \n \n . \n \n \n .. .. \n .. . ...:. \n . .:. ...... .:=:-=-:. . . \n .:--::..-.::. ........ .. :-=-::::::.-..... ....:. .....:. \n .::--:=:::::.. ..- ...:.:. .::---:::::::--+.... .... .. .... ::-.. \n .... ...:--:-::-::.::.. .......--::.. ..:=.---:::--:::....:--:-=. . ..... .. ...:..:.:+:--- .. .. \n +...::.=..::-==--:::::::::.... .::=+=-::.....:::--.---=----:..:--::::.:.::...-+...=-.:. ..:==:. ::- ===-::....-:.. \n . .-.:-:--=--:=+X+=--=:::::---:....::-=+X+=--::.--:+:..:+:=+==---:--=--::-::-:... ..-=:::::::-===--=+++==-:::::::::::....... \n .......:: ------==++X+== ---::==-+::.:.:=+XX=X+++==--::....-:-XX+=++++========---:-:. .:-+=---=++++ +=+X+=====-------:::........ \n ... .:::-:---==:===++XX+==---+++X==-:.:-=+XX=X+XXX+==-::.:+:-=XXXXXXXXXXXXXX-++==--:....:--X+XXX-XXXX-XXX+==+====-=---::::::..-=.... \n ..::=.-------=+-===+++XX++==+X.XX+:+---=+XX:XXXXX:++:=-:::-:==+XXXXXXXXXXXX++ X+=====--===:+XXXXXXXXXXXX+==--=++X++.=--::--=:::...... \n .... ::-==--=-=+X+XX+== ++XXXXXXXXXX+++++++X++:XXXXXXX+X+==---=++=XXXXXX:XXX++ ==XXXXXXXXXXX++XXXXXXXXXXXXX++====++XX:+==-= ===--:.....:.:.\n",
" \n \n \n \n \n \n \n \n \n \n \n . . \n . .....: \n . .:-:--::. . \n .::=-... .. .......... .:-::....=. . . . . \n ..:::::....... . ... . :.:-: :.::. .... . . ... . . ... ...... \n .. ..::=:::.......... .-=..:. .:::-:::.::..... .::---:. :. .:.+.:....:::. \n .-....::-=--::.:....:.:. .-.---==-.. ...:..:::::=-:.::..:--::..... =.. ...=-. ..::.....::--+-:... .. \n ..:---::--=X=--:.:.:.:-:-:.....:--=+X=::..=:::.=...:-+=---------::-::..::.......:-:....=.:----::-==-=--:......:::.. . \n .-..:--:::-=--++=-:+::::---=::...:--=+XX+=++=--::.....::+X+++=+X+=====-.-:::::. .:--:::-==-+-=:==+.+---:.::.::::.....- .. \n .......::-::------=+++==-.--=-+X+==:..:-=+XXXX++X==-::..:::-+XXX-XX+XXXX+===+-=-:.. ..:::=X++.XX++++++++++--=--::::::-:-:. ...:... \n ..:::::--::-+==-==-=+++=====++XX=++::.:-++XXX.++++==-:...:--=+XXXXXX X+XX===X+==-+--:--:-=+XXXXXXXXXX+++= ----+==--=-:::--:........ \n ..:---=---==X+++=-===+XX++==+XXX===.===+++ +XX++====+--::--=+XXXXXXXXXX++.==+X-XXX+++===-=XXXXXXXXXXXXX+=----=+X++=-----==-::........ \n ....=:--======+XX+X+=----=+.XX+.XXXXX+=+=++X+=+XX XX+==--=---=+XXXX-X=XXXX++===.=+XXXXX.X.++++XXXXXXXXXXXXX-+++=++X XX++==++===-:..........\n",
" \n \n \n \n \n \n \n \n \n \n \n .. \n :::::.. \n .... . -=...... .::..:...+ \n ..::.... .. .: .. .--:::.+... .. .. . .. . \n . ...:..-... .. .. .=.. .. .:=:::::.:. ... .:::.. .: .. . .... \n . ..:--::...:...::..: .:-:..-:... ....:::::-:....+..:-:.::. .=. .. .:....::::. \n ..:.:..::-=--:..+....::::. ...: --+-:.-. .......::+-::::: ---::::... ... ..:::. . ..::::.. ::--- -.. .. \n ..::-:::---+=-:..::..:::-:....::--=++=-:-:-:::-....:-+=-=+==++=-.-+::..:::..= ..::.....:::-:-=-----=-:......+:...= . \n .-.:::::::--.+===--:::-=:.==-:.+.::-+XX+++++-- :.. ..:-XX++++XX++++==-=-::.. ..=:=+--:++=:=-=====:=-:::...=.::......+ .. \n ......::-:--=----=++==---==-=+XX=-:..:-=+XXX+++:==--:...:--+XX:XXX+XX++=-==+-+-:=...::: +X++XX++=+++ +=+=----=:.::::-::-..... .. \n .:::::-::--+=--=---+X+===-=-=+XX++=:::-=+XXX.+++===-:-..:-=+X:X:XXXXX XX===XX-++=--+-::-=+XX++:XXXXXX++=-::--====---:::--:...+ ... \n ..:-----:-==X++==-.--+XX++==+X=X++==+==+XXXXX+++==----: :-=+XXXXXXXXXX+=====+XXXXX+++=---+XXXXXXXXXXXXX+=----=+X++==--=-=--:.+..=.... \n .+.::--==-==++XXX+=----+=+XXXXXXXXX++==+++++=XXX=XXX==------=+XXXXXXX XX.+===== +X-XXX+XX===+XXX=XX=XXX=XX+.++++XXXXX:+++++==-:.... .....-\n",
" \n \n \n \n \n \n \n \n \n \n \n . ...... \n . .. .. ...=::.. \n .+.. . .:. . .:.. .=... : \n ...:.-.. .: . .:. =.. ..:.....:. . . . \n ...:::..:... ..... .:.....-: ........-+:... ..:.:::. .: .... \n ......-:--::. +.. ... . ..:::::=:...... -..:==:.:::..:=-:::. . .. ... ......: ::... . \n ..:...:-=--::...:.:+:..-::..::.---==-=-:.:.:.. ..:==----:-=+==--::.. ... .::. ...+:::---=:::-:.. .... \n ...:::=::-=--:--=::--::-==::..:.-+++===+=-::.. ..:-X++++++++++=--+-:.. ...:-:::-==------==----::.-... ..... \n .. -..::-:: ::-=------------X=-:....-+XX++====:--: .:=X =XXXXXX+==--=+-::::..:....-+====+===-= ===---::-::....+.....= \n ..+:..::::-=--::::-++==--:::--+X+=-:..:-=+XX+=------:. ..-=+XXXXXXXX+=+=--=X==:=-=::..::+++==+++++++++=--:::==---::::.::.... .. \n .:::::::--=== -:-:-=X:X==::-=++++=-=====+-XX++==-::-:..+:-=+.X+XXXXX+=-=--+XXX-X+==-:::-=XXXXXXXXXXX++=-=:::-++===-:::--::.. ..+ \n ..:---===-==+++=-.:::-++XX+==+XX++==-====+XXXXX+++=---::::-=+XXXXXXXX++=-:-==++XX+++=+=--=X:XXXXXXXX=X+++=-=-=+XXXXX+===--+:... ...= \n ...::.--+==++X+ +++=--.:=++XXXXXXXX+.+==++====++XXXXX+==+=-==+X:X-XXXXXXX+==--+==+XX.+=-=+==++XXX ++XXX-XXX: XX X-XXXXXX+ +=--+:...:......=\n",
" \n \n \n \n \n \n \n \n \n \n . \n . .. . ........ \n .. . . .. ..:.. :. \n .... . .. .. +...-=.. .. \n ....... .. . .. .-. .. ....--:.. . ..: :... \n .. . .:::.. . : .+...::. ..:-:.........::-........:-=-::-.. . .. -... :..... \n ..=...--::-=.....=+....-:..:.....:----:.:... ..:=-:::::--=-==--:. ... ......:.:--....... \n ...:...:--=:..:::-...:::+-...:.:--=-----:::. ..:+X+==++ ++== -=-:... ...:-..---:--:.:---:::=:.. . \n ...-::..::-=:+::=::::::=-+-:....-+X+===----:.. .-+ XXXXX++X==-:-=-::..... ..-=---=---::::---- :=::.:.. ..... \n . ......:::--:.::.-+=--+:..:::-=+--::.:-=X++==----:::...:-=+XXXXX+++=-=-:-==---:.+. .:=+X=:===-.-===--::: :-:::-:........ \n ...:::.::-=---::::-=X+=--::::-==+=--==--+XX++=--=:::....:-=+XXXXX+=+---:--XX-XX+=-::..:-=++++++--XX+=--:..-:-==---:-::-:=.. . \n ..::----:-=-== -::.:=+=++=---===+= --===+XX+++===X---:..:-=+XXXXXX+==--::--=+X++==-=-::-=+XX+XX+X XXX+==-:::-=X++++=----:::.. .+. \n ...:: -----=====+=-::::-=++X++++XXX+=--==+=+:++XX:+++ ==-:--=+=XX=XXXX++==-:-==+X++==--==--+XXXX+=++XXX XX+++++XXXX ++====-.--... +.... \n ...-.:: ----:===.+==X+=-===++X.XXXXX++X+XX+=-==-==+XXXX++====+XX=X XXXX+XXX+======XXX++=-=-===++XXXX++X XXXX+XXX=XXXXX ++++===-:--:..........\n",
" \n \n \n \n \n \n \n \n . \n . \n . ..:: \n . .. ::. . \n . . . .. .: ::. \n +... . .:.. .: .:::. .. ::...:.. . \n ::.. :.... ..::.:.. ..:-::.. .:: --:.:. . . \n =....-::.... . :-::. ..::.::-.::. .:--::.:-::-----+-.. .. .. .....:. . \n .... ::-.:....:.. . ..:=:.:..::::::..::-.. ..=+=--=+X+--=--==:... . ...... :::.. ... ::..... \n .-... ..:-=:...:::......--:=..:=++=--:::::. ..=XX++XX++===:----::.. ..-::.--:........::::-:.. . \n .....-::-...:-+:::::..-...:--::-::==++=--:.::......:-=+XX-++==-=-:::-+-::-:.:. ..:+X+==---:-::-::::.:::............ \n .. .:..---:::...:=+--::...:.:-=-::-==-=+++==-:-=::....:-=XX:X++=-=-::-:=X+=++=-:....:-=========++=-:. ...:-:::::::.:: .. \n .::-:.:::--=--::..:-=+=--:+:----=-=::-+XX.X+==---=-- ..:-+XXXXX++=--:..:--+X+==----:..:-+:+++++++=X+---:..::=X====-::.:::. \n ..:::-:::- ---+=:...:--=++=+=.==++=-:---++X+X+===-=--=:::-+XXX==XXX+=-- ::--=+:+==-:-=:-=+XXXX+=++++XX++== ==++.X+=----:::::. \n ..+...::-----------+=::--=+X+++++==:++====----===X+=+=+=+==-=XXXXXXXXXXXX+===+==+XX+==-:-=-=+++XXX+=:++XXXXXX+X-X+++++=+=---:::+:......... \n .....::::-----------=++==+:XXXX-X.XXXX+X+=-::-:--==XX+X+XX+ ++XX+:+XXXXX-X:+++++.+XXXX=--=.====+XXXX++XXXXXXXXXXXXXX+XX++==--::::--...::.....\n",
" \n \n \n \n \n \n \n . \n .: \n . :. \n . . .. . \n . . .. \n .. ... .. .:. .::- \n :.. .:. ......: .::. ...::-:. \n ::.. .. .-. . . ..... ..:::.. ---:: =---:. .. \n . ..:::. . . ..:-.. .:..:........ .:----=+-+=----::-:. . ..... . ..:. \n .. ....::-:. ..... ..::-:.-====-:.:.... .:+X+=:++=-----:--:.. .:.:.::.... .. .:.. \n .. :.. ....--:..: .. .::..:-=--===::..::: .=.:==X+X+==--- -:.:-:...-: .:-++==--:.:..::........ . . .. \n ..::-::...:.:--:.....:. ..: ::.:-=.===-=-::--:.=.:-=+XXXX+=- -+:::-+=--+-::. .:--=-=-----=+=-:.. .=.::..- .....+. \n ...::....::--:+....:--:::::.::+::::..:-+XX+=-:-::--::..:+XXXXX=+=--::..:-=++==-::.:...:-=+=+=====++=::.....:==-:-::....... \n ....:..::::::--:....:-=---===:--==-:+::=++X++-:-:-:=--::=XXXXX++++=-:::=-:-=++=--::-=::=+XX+X+=-==+X+==-----=++==-:::::..... \n ....-::::::::::::=-..:-==X++=====-==---.::-=+=X+--- -==--:-+ ++XX+X+X+=---==-=+XX==-::-=--=++XXX+==+=+XXXXXX++++==+=--::::...:. ... \n ...-..::::----:::-:-=---=+XX-XX++++====+-:..: ---++=-+=+====+X++=++XXXXXX+===+++X=++=-::--=-==-+X++=+-+XXXXXXXXX++++ +=+=-::.:..:.-...-... \n -.....::----==-------=== +XXXXXXXXXX++-++-::.::====XX++++==+++XX ++++XX X XXXXXX++++++=--==+==-+++XXXXXXXXXXXXXXX++-XXX+++=--:::: :.:-::::.:.\n",
" \n \n \n \n \n \n \n : \n . \n \n . . ... .. \n . ... . :. ::. \n .. .. . .... .::.:..:-.:.. \n .:... .:. . . +..=::===--::-:.. \n ..::. .::......... . . ..:--:--X+=-::::- .. \n .. ..:. . .-...:===--:.... .: ..-XX+====:-::::::.. ... :...:. \n .:.:. .::. .. . .....-==:- -:...:.:....:-+X++++=----::..-:...:.. .-=+=--:..::::... .. . \n .:::... .::. .. .. ::....--+=--::: ..-:...:=+:++X++=-+:::::-=-::.-... .---=----::-+=::.. .... .. \n .. ...::.. :: .=.::...........:=+X+=-:.:..--:::-+XXXX+===-::..:--+X=--::... ..:-+==+=---==--::.....:+:....+ .... \n ...... ..:.:.::. .:----::-=:.:=:::::.-=+X=-:. .::::..:-+=XXX+==-::.=.--:-++=--::.::.:-=++ X=----=+=--:::+-==-:::.+.. . \n ..............:-....:--=++=---==---:::::--=X=-:.:::---:::-X+++XX+==-::::==-=++==-:.:- --==+X++=---==XX++XX+==-==--=:::... . \n .-....:-:-:-::+::::-::--=+XX++=======-=::..:---+=--:==-----.+X+==+XX+X+=--===.XX:==::..:-=-===X+==-= =+XXXXXXXX+==+==+--:........ .... \n .... ::: . ------:=::--=+XXXXX-X++X==---:....:-=+++=-==-----++X+==++XXXXXXXXXXX++++=-::.:-=--==+XX+=++++XXXXXXXX++XX+:++=--:.:......:::.... \n ......::--======.----==+XXXX-XXXXX-====--:::::===+XX ++====+++X++XX++-+X=XX+XXX+:++X+=- -=.====+XX:XX+XXXXXXXXXX++++.XXX+==--::::::::::::.+.\n",
" \n \n \n \n \n . \n . \n . \n \n .. ..+ \n . . . .::.::..:: \n . . :. ..:--=-:::.. \n .. . . ..-..:--=-:::::. \n . .. .....-. . .-::+::--=:::.:.. \n . .. .:-=--:...... .:...-=XX+=-==-:.:+:.. .. :+...... \n .. .. . ..:-:==-:::. .. :..:-=++======-:.:. ....: :--=-::.-..::. . \n .... :. ..= .....:--==--:... ..:..::-+++=====-...:::--:.. .-:=--:-=-:--:::.. \n .... ::....+:. .. ...:-+==-..... ..:..:-++++==--:...:--=X+=-::. .. ..--=---=----.:::. -....:.. \n . .. ..::==::.::=........:=++=-... .:....-+XXX+=--:....:---== =-:....:..:-+===-::-:--.:.:::::-=::+.. \n ......... .:. ..::-===-:::-=--:..:.--++--:..:.::::.:=+X++X+---:...:=--===:--..::--:-=++=---::-=X=-=++======--:..:. \n .......::.:.::...::..:-===-++=-:===---:..:.:-=+-::.--:.:::-=++==+X===--::-==++X+=-....:----=.++-::.-==XX+X++XX+=====-::....: \n ....::::::-:::-:.::--+XXXXXX++==+=--:-...:.::-=X=-::-:::::-==+===++XX++==X++X+-++-:..=.------++=======+XX.XXXX++++==--=::.:. ....:..... \n ...:.:::-==- -=.::--=+XXXXXXXX+++:---:. .::=-:-+X+=-----:=.====++++XXXX+++.X++==++=:::-= --==+XXXX.+++XXX:XXX-XX++++=++=-:- .....::::.... \n ......::--==+.==+=--=++XXXXXXXXXXX==---::: ==X+===+XX++==+++.==++++XX+=+.XX++:+==++XX+=-=+=.===++XXXXX-X.XX=XX++==-++X+XX+==.-::: ::-:::=...\n",
" \n \n \n \n \n \n \n \n . .. ..- .... \n .::-:::. . \n . :..::-:.. \n .. .......::.::... \n .... .. ..::==--::-:.... \n .:---.-.. ...-=+:+==-::::..+.. .. .. \n . :::=-::... .=.---+==----+:..:. .. ::=-:....::.. . \n . . . . .:--=--:: .-..--=+===---:..::.::.. :::=-:.::-::.... \n . ....= . . ..-=--:. . .:-=X+==--:.. .:-=+++=-.. .:=:--:.:::::.. . . \n ..:--:=.....=. .-.== -:.. -... .-=+XX=--::. :-+===--:.. .-. ..-==--::.:=:...-.::::-::.. \n . . ...::-----.. :--. +..:====-..:..:.+..:-==+X=-:::..=.:=--==--:...::-:: +===-::..:--:: =---==--:.-.. \n ........... .=...::---==--:::=-:-:.....:-+=-:..::.....:==++.+=---::.:==-++X=-:. ..-::-:=++=-=:::-++======X= =--::..... \n +:........::::..-.:=XXX+XXX=--::--:=:. .=..:-++--..::...:---====+==+--:=:=+ +++=:. .-----=X+==----==X++++=+++==--:::::... ..... \n .=.:=::::=-:--::.:-=+XXXXX=+==--:::.. .:.:--=+X=-::::.------==+XXXX++==+ ====++-:...-=:-=--=XX++====+XXXX+++X+++==-==-:::.. ....::.... \n ....::.--==--=-----=+XXXXX-X++=+--:::::..--=X+-=+X+====--=--===++XXXXXX++=-++= =++=: :-----==++XX+=++XXXXXXX++ ++=+++++=-- ::...:::::.=. \n ......::-===+=+++===++XXXXXXXXX+++===-:::.-=+XX+==+XXX===:+X==+XX+XXXXX+XX++======++X+=---== ++-=+XXXXXXXXXXXX++==+.+XXXX+=+==--::--::::....\n",
" \n \n \n \n \n \n \n . .... . \n ..:--::.. \n ....::.. \n .. . ..-:... \n ... ..:--:--:.::: . \n .::::.. . ...:---+=--:..... . . \n .::-+-.... ...==++=----::.... .--:.. . .. \n .-::---:.. ..:--==.---::..::=..... .=.--:..=.::=.. \n .. . .::--::. . .:-=+==--::=.. ::--:-::. .:::-:....::.. \n ..::. .. . .:-=--... .:-=X+=--::. .:-:++=--.. . .::--:::..::. ...=... \n ..:::-:.:..::-. ..:+=--:.-. .. .-==X+=-::.. .::====--:... ::.::==--::..:::....::::-=-:.. \n .. ........::-=---:.:-:::. .:-=+--:..:+.....: ==+X=-:: ....=--==+=-:=....::--+==-::..::=--:-::-=+--::.... \n .....+.::... ..:-== =-==--:..:::-:. ...:=+-::..... :-=-=.=+=--+.::-=--++X=-:. .:----+X=-:+:---++====.++==--:..:::.. . \n ........:-:::...:-+X XXXXX+--: :::.. .-.::-=X=-:..:..---:--==+X++.=----:== ++-:. .-:-:--+X++=---=++++++==++++--: :-::.. ...... \n .=..:::-:------..:-+XXXXXXX+==-::.... ..:-=+=++==---::.--:-=:=+.XXX++==-===--++-:..:-::----=+X+==-==XXXXX+=++ ++==--==::::. ...+:..... \n ....::-:-== ==-=--==+XXXXXX-+++=--::.:..:--+X+.==++=======- -=+++XXXXXXX+=======++=-::-:--.==++.X+=++XXXXXX++=+++=X+-++=-=--::..:::::... \n .....::-=-=+=++++++++XXXXXXXX++X+ +=--::--=+XX+==+XX+===.=+=++XX.++++XXXXX+====:==+X+= --==-+X==++XXXXXXXXXX++==.++:XXXX++===--.:--::::-.. \n",
" \n \n \n \n \n ... \n ......::.. \n .:..= \n .. \n . ...... .. \n ....=..--:+:. . .. \n .:--:.. ..-=--=-:.... . . \n ..::--:. .---==--:: .. .: .:-::.. \n ..:--.: ..---=-=--::....-=.... . .:::....:.. \n .:--::. .:-===-:::=.. ..:-::-:.. ..:::- ..... \n ..- . . ..==-:... ..-=+=-:-... ..==++-::. . ::-::..-... ..... \n .:::: .. .:+=-::. .. .-=++=-:... .:---=-::+......:.:X--:..:.....= ....:--::. \n .........-::::..:+::.. ..-==::.-.. .:===++=- ... :-:--==-::... .::-+==::...::-:..-..:----:.. \n .:.=. ..:--+=-----:::....:. .::=+-.=. ::::--+=-:: ..:-::-==+-:. :.::-++= --::-+X=-=----=---:.. .... \n .-... .:::: ..:=+XX++==+=:.:..... ..:-++::::.-. .:::--=XX==-::::.:--+X-:. .:.: :-=X=--:::-=+= ==--==+--:.-..::.. ...=. \n ........::=-::..:-++XX++-+=--:.-. .:-++++-:.:--..::::--=+XXX+=--::-+--==-:. .::.::: :-=+--::-=+X+-+==:==+=-::::-::... . .... \n .+..::::-:---:-:::-+ XXXX++==+-::...=. .::-+X ===-::::-------==++X.XX+==--=---=+=:..:..::::--==+=--+XXXXX+=====++=-==----::...:.::.:. \n .:.::--=====++====XXXXXXX+==+==+=-:::::-==++==++==--=::--==++==+XXX-XX++.======X+-::.::=-=++==++++XXXXXXX+==-=+=++ ++==----::::::::.=. \n .. ..::-===+==++XX+XXXXXXXXX++=++X+X+=-=== +=++=++XX:==---=+++++++=+++XXXX++=====-+XX==-===-+X==++XXXXXXXXXX+=-==++X+X+-+===+=-----::.... \n",
" \n \n \n .:. \n \n ...=. \n \n \n .:.... \n .-..::... \n .:---::. .:-=--:=.... .. . \n .:=::. ::.:=-- ::.-. ..::-:.. \n ..-:.. .-::=--:.... ..:... ..-... . \n ..=::. .::---::.... :--::.. ...-:... . \n .. ..+-:.. .-=--::. :--+-:... .::+:...... ... \n :..:.-. ..==:.. .. :-++==-::. .---:=-:.=. .::+-:..=. .:::... \n .:::..:.... ... .-==-. . .::-=+=--:. .:: :-=--... ..::=+--::...:........:::-:. \n .......::::+-:.::.... . ::==:..: .::--=X=-::...::.::-==-:. ...:-=+=---::-+==-:::::--::.. ... \n . ..-:.=.:-++++=--+-:-.. .:-+-:...... ...::-=X+=--:...:.:=+=-:. ...:.::==-:::+-==:----::--=-:. ...... .. \n . . ...:: =...-==+X++===-:-.... .:=++=:...::. ..:---=+XX+=-::..::--==:. .:... ...:=-::.::=+=====--==+-:.....:+.. ..+.. \n .......::::::..:-=+XX+=.=-==:::... ..:-==+=-:.....:..:---==++XXX=-:---::-+=::. . ..=:+:-.-::.=+XXXX====-=+=-:-:::::.. ...... \n ..:::------=--- =+X:X++==-== =++=:.:.:::--=:===-::....:+:-= ==++XXXX+==--===.X+-:.. ...:-=---=--=+XXX.XX+=--====+=+=----:.......... \n . .=.:------==+X++++XXXX++.==-==+XX+-+-=-===-=.=+++==::::-=X++++=++XXXXXX+=--==-=+X=-::--:-=X===+=++XXXXX=X+=-=+=+XX+=====-=--:::::=... \n . . ...:--======+++XXXXXXXXXX++++++XXX++===:+++==+XXX.++=---=+XXXXX+-+++XXXXX+=======+X+====++=+.++XXXXXXXXXXXX==+++XXXX++ ======---:.::..- .\n",
" . \n .. \n \n \n \n \n . \n .... \n . ..--....... . \n ..:==+::. .::--+.. . . .. \n .+.-.+. ...:--.:. .. =..-:... \n ..-:. .:-:---.. . . . .:... \n :+-. ..:--+... .:-::.. ..:-:. \n .==:.. ..-+--:... .::-=-.. .:=-.. \n .:-.... .-+-:. ..:++--::.:. .::----:. ..==:::. ....: \n .::...... .--=:. .:-=+--::.. .....::--:.. ..-+-::.. .. .. ...+::.. \n .. ...:-::.... . .:-:.. . .. ..:-=XX-::. ......:=--.. ..:-=:-:..:==-:::.=.-:::.. \n +..:..:-=:=+--:::.... .:==:... .. ...--=X+--:.. ..:==--:. ..-:..::--=-- ::-:-::-:. . \n .......:--=+==-:-:::... .:--==-.. . ..:--=+XX+=-::...:-=+-:. .. . ..:-::..:--==-==::.:=-:... ..=. \n .........:....::=:=X+=-----:--:.:. ..:-=.=:.-. -...:--==+XXX=-::--::+=+-:. .:::+:...:-=X++X=----==-:::::..-. ..+. \n .+..:::::-::::.:-=+X+==-:----+==+=:+..::=--==---::. ..:-=:--==:++XX-=-:--==-=X=:. .:--::-:::-=+=XXX++=--=======- :::....=:... \n .. :--:-:--==+++=+X+X+==-- :--=+XX=-:---=--= ====--...::-+==+XX++XXX:X=--::- -=+=:..:-:::-==--=-==+XXX:++=+-===+XX==.-----::::=...: \n . ..+:----=--==+++=XXXXX+==++===-+++=---=+==-+XXXXX+=-::--==+ XX+XXXXXXX++=---=--=++-::--==-=X=:+++XXXXXXXX++-== +X-X++=---- -:::+::.. \n . ..:::--=++= =+=+=XX+XXX:X++XX++++++==++X++==+XXXX-X++=-==++XXXX:X X-XXXXXX+=+===+-++==+-===+X+X:XXXXXXXX.X+===+XX++XXX+==.--=--:::+:+....\n",
" \n \n \n \n \n \n . \n .-:. .:----:.. \n .:--:=.. ...::=-:.. .:. \n . ::.. .:--::. ..... \n .-=:. ..:--::. . ..::. \n :=:.. ..==::. .:--::. .:-:.. \n ... .-=:. :..++-:.. . .:.:-::. .-:.. \n ... :=:. ..=+-::... . ...::--: .--:+.. .. \n ...=... .::. ..--+==-.. .:. ..::-::. :-::.. . . .... \n .. ::-.. . .::. .:-=++-:.. ..==::. .::... :--::.....::.-.. \n . ...+:::--:::..= ..---.. ..:-=++-::. ..==-:.. ..::. .=::-:::::-.. ::.. \n . . ..::::----.::::.-. . .- --:. ..:--=++=-:....+.-=+-.. ...:. ..::-----+-:.:-:: .... \n -...... ..:---==-::.::-:::.::. +..:==--:.. .:=::--=+++X--:::::::=+-.. .:..:.-...:=+===+=-:.--:::...... \n ...::..:.:: ---=++=-::.::-++--:==:..::-:-+--:-.. ..:-::--++++ +=:..::--:=+-.. . ..:-..:::::-+X=++=--::-==-==-:=........=:. \n ..:::::::--:-=-++X+=--:::::--=====-::-= --=--=-:. ..:-=---=+.++XX+=::...-::-=:. ..:::.:-::=::--=+XX+====-=--=++=-+::::.. ....- \n ..:=------:-=-=+X+X+=--=-----=.=--:--X--=+X+++==-:..::-==++XXXXXXXX+=-::::-:--=-.. .:-:--+--=-=++X+XX+++==---=++==---=:::=:::...+ \n +..::--=---===+==+++++++=-++========-=XX+==+XXXXXX+=:::--=+XXXXXXXXXXXXX+-----====-::-:-==+XXX-XXXXX.XX X+=---==+++-=------:::::..=. \n ..: :..:::-==.=--=+X++++XX+-XXXXXX+:==++++XX+++=+XXXXXXXX=--=+XX-XXXX XXX.XX+++ ++==+XX++==-=-=+ X-XXXX+XXXXXX+==.++=++XXX=----=--::=:::: ...\n",
" \n \n \n \n \n .. ...... . \n .::.. ..::=+-::. \n .--... .. ...-:.. ..- \n ..:-. ..-:.+. .:. \n .-..- ..=-:. .. ..: \n -:. ..+-:.. .:---::. .:.. \n ..- ::. ..-+:::. .:=::. :... \n .-. ..-+=--:. . .+.-:.. ::.. \n . .. .:. .--====:.. ..=-:. .:+.. ... \n ...... ..-. .:-==-: : .+=:.. ... ::- .....:.... . \n ... ..... . ..:::. .:.:-===-:. ...-+-:. .. .:.:-:....:...:.. \n .......::........ .:=-:: .::.::==++=:=..:..--=-. ... . .::-:: :--:.:-.:. .. \n . . ..::::---:. ..:.:....--:.....::--:--: ..:+-:-:-==++=::...:::-=:. . ........:-+-:-=-:::::...=.. \n -...-......-::--+=-:....:---.-=--=:+:-=--:-::.. . .:-::::=====+-:. .: :--. .. . . ....+..-++===--::::--::-:..+. \n .-.::...:...::-=+=-::.-.:.:---.=--::-+-:---:-:.. . .:-=--== == ==:.. ..:--. ..:..:......:-+++===--::::-=-:-:... . :.. \n .::::::::.::--==+=--:::+ :::-----::-++==++=---:......:-+X++X+++X++=-:......--:. ..::=-:.:--=++++==--=: :-==---:::......... \n ..:::-::+:--=--====+===+==--------:-=X++ XX+++X++=-:..:-=+ XXXXXXX X++-:::: -.--:..:.:-=+X+++ +=+XX+X++==:::--= ==--.:::::..... . \n ....::---.::-++===.+XX:XXXX+++--=====+++==++.XXXX++==-::-==+XXXXXXXXXXXX+=.==-==+==+--:--=+XXXXX-XX.XXXX=+=--==-==++=-:.:--:: ..:.:... \n .+...::-:--===--=++=+++++XXXXXXXXX+===+X+++++=+=+XX=XX++====++XXXXXXXXXXXX=++==+=+XX-XX++=-:==++XXXXXXXXXXXXXX+==+X=++XX+=-:-=-:::: :::=..:.\n",
" \n \n \n \n ... ..+-:.... \n ::. ...::-::.. \n .::. . ..-... . \n .. .-=:: .. \n : .:=-:.. ... .. \n :. .-+:::. .::-=::. . \n . .:X-::-. .. ...:... . \n .. .:=--:-.. ..-:. . \n . ::=-=--.. .+-: . . \n .... ...:---=-:.. . .-=:.. . ..::. .. \n . ..:... .-:..:--==-.. ..+.:+-:. ... ...::.. ..... \n ..... . .:::=-:. .::...-=-==:. ..::-=:. ... .:.::.:..:...:. \n ....--:..... .. ..-:.. :=::.::. . :--:.:----=-:. ...-:. . . . ..-::.:.::..::-.. . \n .. ..::-::. ........-:-=::==::..:..: .:--::--=----.. ..::. .. . .-=----::....::.::.. \n .... .. .:::-=-:.. ...::-:-=-:-::=-:=::.. .:-=---=====-. . .-.. . .. ..-==---:.:-....::::... \n ...........::--=-:::..-::.:-::--:.:-+==--:..-... .:-++XX++++==-:. . .::.. ..:.=. ...:-=++- -::::..:--:-:=.. . . . \n ....::.:..:-::--=:=----:+:-=--:-:.:-++XXX+=-=+=---:...:-=+XXXX=XX+==:....+:::::. ....:=+==-::--==+XX==--:..::----::. ......=.. \n ....:-:::::+=----==++X++====+-:----.==+=+++X++=+==--=:.:--=+X=XXX=XXX++-:+::--=-:--:..:-+XXX+:++==+XX XX=-:-.----+=-::..::......... \n .. .. :::----:::-=--= =+XXXXXXXXX+=--===+-====++XX++=+=-----==+++XXXXXXXX+++==+=XXXX++=-::--=+X+X++X++XXXXXXX=-==+===++=-::::::::...::.... \n ......::-===+=-=-====++-+XXXXXXX..++==+X+======+XXX-+=+=--=+X++++XXXXXXXX++======XXXXXX++-===+++=XXXXXX=XXXXX++==-++XX+XX+= ---::::::::::. .\n",
" \n \n .. :+:. \n ... .--::. . \n . ..--:. \n .. .--:. \n .. .:=:+.. \n . .--...:. .-:.. \n :=:..:... .::-:. . \n .=:. .. . :-:. \n .::::::. .:=:. \n .. ::-::::--: .. .--.. .. \n .. .-. .:..::+:--.. . .:+:. . ..... \n . .:...: .. :.:=::: ..:=:. . .... . .. \n .. .. .--::.:. :.:. ::.:::.. .::. ..:.. ..... \n .:...+ ::...:=-.....:. ..-=..::-:=::. ..: .:-....... .-... \n . ..:.. ..:-:::..-:...=.. .:--:::::=-:. :. .-=::::... .:... .. \n .. ....:::. .+.:+:::-.:-::-:. .. .:-=--:::--.. ... .. .-=-:-:.... ........ \n ..... ..:..::---:+.. ..:==:::- ..:==-::.. .-.:. ..:-=+=====--:. . ..:.. ..:. :--++-::.... .::..- \n ..:..- :...::---=-::...:-+:::-:..:-++X++-::--:-::. ..-+XXXXXXXX+=-. . ....::. .:--=-:..::--=++-:::....:-:-:.. . \n :..::......:.::-=X++==--:-++-::-::---=+++X==-----:-:..:-++X=XXXXX:+=-.-..:::::::.....=+XX+=---=--=+X++=-:: :-:-=-:.. .. ..+. \n ..+::-::..:::=:--=+XXXXX+=+X=-:--:=:---==+XX+===--::-----==XXXXXXXXX+=-+-=+X++++--:..:=-++X+=====.=+XXXX+=---=--==-:....: ........ \n .. ..:=---==::-:--====+XXXXXXXX+==-=- ==+---=+XX.+==--:=--===++XXXXXXXXX+==-==+XXXXX+=--:--.++XX++++.X+XXXXX+=--=++++++=-:.:::::...+:..-. \n ....::--=++=+=---.==++.+XXXXXXXXX++=+==+-====:XXXX+++=--====+XX-XX:XXXXX+====:==+=XXXXX+==-=++XXX-X==XXXXXXXX++==+X=XXX++=-:::::::::.::...-\n",
" \n . .:: \n .+. .-::. \n .:--:. . \n ..--.. \n . .:-:.. \n .-:...::. .:. \n :-....... .--:.. \n .-.. .. ..:-.. \n .:+:=.. .:-:. \n . ::+::..:=: .-:.. \n . . ..:.:::-. :-.. . .:.. \n .:..-. . ..:.... .:.-. .... \n .. .:-:... . .. :.::... .-:. ..... . \n .. -.. ..-::.... ..:-..-.::+.. .:. .::.. . .. \n .. ..:..:..-::.. .. +:::.=.:.:::. . .--:. .... ... \n . +.... ....:: :-:.:..-:=.. .::-:::..:-:. :. .-=::::... .. .. \n .. ..:::=.. ..-=:::::.:.:--... . .:--------::. ..:. .. .--=--:. ....-. \n . .. ..+..=:::.... ..:+-:-::.+.:=+=--:. .:.:.. ..:=+++++++=--. ..... :...:. ..:-=+-::.-.. .:::.. \n ..::.. ....:-==--::.=.:-=-=:::..:-=++XX=---=:-::. .:-+X+XX.XXX+=-.:. ....... . .-++==:..=::--==--::..:..::-:.. \n ..-.::... ..::::-=XX X+=---+=-::::-- -=+++X++==---::-:::-++:XXXXXX-+==:..:-=--:-:.. .:==+X+=------== +++=:::--::--:.. .... ..-. \n . ...:::-:...::-- ==+X-XXX:+++=-:---.-::-==+X+X+=---: ----==+ XXXXXXX:==---+XXXX-X=-:..:-++ X+====-=++XXX+X=::--= =-=-:.............. \n .. .:.--====--::---==-++XXXXXXX+==-=--==----+XXXX+==--:==--=++XX.XXX-XX++=---=+XXXX-++=-:--+++X+++ ++XX=XXX++=--+XXX+++=-:.....::.+.:..+. \n ......:--==+===-:--=++XX+XXXXXXXXX++++=====++== XXX-++=---===++XX+:XXXXXX+=======+XXXX++=====+XXX++XXXXXXXXXXX++==+XXXXX++=-::::::-:::.....=\n",
" ::.. \n .-:. \n .::. \n ..: .. \n : .:.: .. \n :.. . .::. \n :. .--.. \n .::. . .::. \n . :. . .. .:. \n -...:...:: :.. \n ..: . .:. .... \n .:-::. . . ..-. .:. . \n .::. .. ..:.. .. .. \n ... .:.... ....:....... .. :.. \n .:..:...: .:. ..=:.. ..... . .:-...+. \n .. .:=::::... .:. .::::. ..... .. . .=:.. \n .=... .-=:....... ::.. . .::::::--:.. .. .:-=-:.. ... \n . ...::.+. .-::......::-.--:.......... .-==+==--=+=:. .... ..:--:.. . :..:. \n .. ..::-+=-:.. ..=-::-...-==+==+-:--::-.....:++XX+:+ XX=-:. . .:==-=:. ...::---.. ..:...::. \n ..:.. ..+:-+XX++=::::-=-:....:---==++===--:::::::-=+XXXXXXXX+=-:... :=:-.. . .--==+=-::..:::-===+=:.:::::+-:. ... \n . ....::.. .:.::=++XX++==-==- ::-::--::==++=+=--:-=: ::--=XX=XX-XX++=--:-XX+===+-....:=+=++--::----=++=.++::::---:-:. ..... \n . ..::-----:..-::--==+X-X+++==--::--:-::-:-+X+=+=--::-=:::-==XX X-XXX++=+--=+XX+ ++=--..:-=++ ==--==+XXX++-+=--+++++==-:.............. \n ....::--====--::==++XXXXXXXXX++==-=--::-=++=X++=+.==-::-:--==+X+XXX:XX+===--=++XXXX++==----+XX+====+XXXXX=X++= =+XX+XX+=-:..:::::::..-.. \n .....+::--======--= XXXXXX.X-XX+XXXX+=---===+XX+=+XX.++=--===-++XXXXXX=X.-++====++XXX.X++---=++X=X=++XXXXX-XX.XX++++XXXXX+==-::===-- :::.....\n",
" -.. \n .. . \n .. .. \n . ... \n . .:: \n .:. :. \n ..: .. \n ... : \n .:.. .: .. \n ..:.. .. . \n .:::.. . . . \n . .. .. . :. \n .. ... .. . .. .. . :. \n .:-:... .. :.. . .-. \n .-::.... ...-..:=:. .-:. \n . ..::.. . . ..-.. .. ..:....::-=:. .::.+.. . \n ..:-:. .-:. . .::=:--:..::.+.....:-=-----::-=:.. =.. ..::.. . . \n .. .:-++=-.. .-:.. ::-.-:-::=-:......:=+X:X+===+X+-:. .::--. . .:::-:.........: \n ... ...:=XX+==-:.:..-::.. .:--------=-:::...::-+XXXX++++X+--... ... .-----:......-: --+=:.......:. . \n .:... ..::-=+++=--:.:-::..:::::--==+=---:::-..=:=:=++XXX+.++==--::=+==-::.:. .:-+-=--:..:::.-==-==+=::.:::.:. . \n ....:::::....:::--=+X++====-::..::...:-:-+X=--=:-:-==:::--=+XXXX+X+==--:-++XX====-:....-==+=-:::--++++====-====-==--:. . ....... \n ..::::-:--:::=X=== =+X++:+++--:.::.-.:-==+-+=-==-::.::::--=+X-XX++++==--:-=++XX+====-:.:-=XX=--::-=+XXX++===.=XX+==+=-:..:-::.....-. \n ......::-----==--+X+=+X:XXX+++XXX==--:::.-==+XX++==++.-:: -- ==+X+XX+XX++=+=-==++.XX+=+=-:--=+++=--==+XXXXXXXX+===++X+++==:::-=-:::: :.... \n ...+..:::-=+=----===+++XXXX X+XXX+X+=---- ++=++X++XXXXX+=====+X+++++XX+XXX+++===+XXXXX+==---=+.++X++XXXXXXXXXXXX++++XXX++=--------::-- ::....\n",
" \n . \n . .-. \n . \n . . \n .. . \n .. . \n .:. . \n .:.... \n .. \n . . . \n .:::.-.. . .. \n .:.. .:.. . \n ...... . . ....::. .:. \n .. .:. . .:..= ..... ...:.+..:=+. . ... \n .::-:. ..: .:..=:..:--:......::-:::-+:::-:.. ..- ..= \n ..--=--. . . .::..:..::.. . .:=++XX+==-=-=:. ..... :..-:. . \n .:==== -::.. ....-. .::.:-::.+::.-....:-=++XX=====--:. .. .::..: . .:::---:. . \n . . . .:=====-::.::++......:--+-=-:.:::......::=++XX+=+==--::.:==--:. ..-.:-::. . ..:--------:..+:... \n .. .........-:.:::-====-=:--:.. .....:::-==-:.::::::::::--++XX+==-=+--::==+XX=--::.. .:--==-:+..::--==------=-::--:.. .. \n ......::::..--=-:---=+==-===-:.. .. ..:====+-::-::..=:+:::-+XXX===+==-::-=+++X+=---::..--=+=-:...:-=.+++===-=+++=--=-:..:::.-.. .. \n ...:::::---:-==--=++X+-++=+==-::.:..::-=+X++=-=--=-..::-::-=+XX+=++=+=--:-=++XX+=-=-::-:=+==-::::-=+XXXXX+===++X+=:=---::-::...:.... \n ......::-.---:----==++XXX XXX++:++=--:::----=++XX.++++=:::-:-==+==+XX+XX++-=-==+XX++===-::=== -======+XXXXXXXXX+==+XX++=---=::-::=-::::=.. \n .. ..::-=----::-:===++.XXXX-XXX=XXXX==--.====+=++XXX.X++====+.++++ +X++XXX:+=.=++XX+:++==--==+ +:XX-XX-XXXXXX:XX++XXXX++=- ::-:-::::::::..=.\n",
" \n \n . \n \n \n .: \n \n ... \n . \n -.... \n ::.. \n .. .... . \n . .. . ...:--: \n . . .. ..-.::.. ......:::-: .. \n .::=. .. . .::.. -...:-:--::..... . . ..: \n .::=:--:. . ...... ... .:-===+=:: :::. . . ....::. \n .::::::.. ... .. ...::=. .:. .:----+++--=::-.. ... . ...::::.. . \n . .:------....... . .::::-:...:. .:-==++==------::..-.--:. ...:::. . .:::::--:. ..-. \n .. :....:.:-----:.... .:-::--:.:. ... . .::-=+X+=------::+=+==+-::. .::--:.. ..:::-:::-:-:+..... \n ......:-..:::::::---=---=---:. . .:=+-=-:::....-.:.:::-+X+=----- -::-++=++--:::.+..::-.-:. .::-==++-=-====-::-::...... \n ...:...+::+::::::-+++ +=----::.......::-=+==-=-::-:. ...::-+++=- -=+=-:::-==++=-::-::=:.-=--::...:-++XXX++=-==++=--:::............ \n .. .+.:::::.::+----=XXXXXX++======:::..:--+-++.++-==--:.:---:-===++==++.=----=+X+==+--::-=-----------+XXXX:XX+====+-+=--::.:::..::::..= \n ......::---::.::-- -=+X=XXXX+:++++XX+-::-=====++XXXXX+=----==:====+XXX:XX X==-=++X==-+=-::=--=-=+X++++XX-XXX:XX+==+XX+==-::::::::::::::.. \n ......::--==---::-==+.+++XXXXXXXXXXXXX+==++X+ ==++XXXXXX+-==.+++XX ++XX+XXXXX++=+=+X++X+=-: --=++XXXXXXXXXXXXXXX+-+XXXXX+=--::-.-::-::::.....\n",
" \n \n \n . \n . \n \n \n .. \n ..... \n . \n .=. \n . .. ..::: \n .:.. . .::. .... .: . \n .... . .. .:::::.. .. . \n . ..... . . . .-::---:..+. .... \n ......... . .:+: . ..-::-----:::... .. . . ..+.:. \n .....:.. ...::...+ .::--=+=-:::::-:...:::-: ::. ...::-:.. . \n ....:::::::- . . .:=:.::.... .::-=X+--:-::-=-==:--:-:.. ...::.. ..:-:::::...... \n :.. .. -...:.:---::::.::. ..----:::: ........:-=X=-::::.::.:==-----:.+. .:-::. ...: ------.--:.::. . \n ...:...:.....:-=+X+=--::.:.. .- -=-.-:..-::. . ..:=== --::-=-:..:-==+=-:..::-...=::::.. ..-====+==-----=-:::::... . . \n .......::.::.::=++XX-X+=-+--=-::. ..:-=-=+===--:::.. ::-:-=====- ===-:.::-+X=-- ::::-=:::--.:..:-+XXXXXXX+= ===--::::............. \n .....:: :::..--::-==++XX:X====+++==:.=.:-==.=+XXX+=--::::-----==+XXXX-++=-::=++=.---::..::.--=++=--=+XXXXXXX++===++=--::.::........... \n ....:::----::-:--.===-++XXX++X++X+X++-:-=++:===+XX-X+==--===++++++XXXXXXXX+=--==+==++=-:.-::==+XX.-XX:XXXXXXX++=++X++X+-:::::::::::..+... \n .....::---===---=+XX:+++X+++XXXXXXXX++===+XX+-==+XXXXXX=+++XXX:XX.X+XX+:XXX++=++==+XXX+=--:--=+++XX:XX-+XXXXX:++=+XXX+XX+=-:-===-:::::.=....\n",
" \n \n \n \n \n .. \n \n \n \n :. .. \n :. . .... \n . . .... . \n .. ...:.. . . \n . .. ....=::.. . .. \n . .. . .:-:-:....... :.. .. ..:. \n ..: ... .:..... .::+:-=-::..:::+.....=:. .. .=.:. \n ...::.+... ..--:.. .. .::--=-::....::=-:--:.:. ... -.....:..-. .. \n .. ..:-=--:: .. .. ...--::+:. .. ..:-=--:.. ..:.:--::-::. . ..::.. ....::..::..-.... \n ..........:+==--.:.:..:. .:.:-+-::.. ... .:--=--+:::::. .:--+=-:...:. ..::.. ..::--==--::::::..... \n :......:..:...:-+=:==+--:::+=:.. ...-==+==-:.-.. ...:---===---=-:-..:-=+-:...=.:::::::::....-=+X++++.+=--+-::...... .. \n :...+....::-::-=====:++=======+-:. ..:-==+.XX++-:....:+::--==+XX++-=-::.:==+--::+......:--++=-::-++XXX.++X ==+=--::..::=:+....... . \n ...+.::--:::+===+= ==+++X+=+.+==++=:.::-====+XX++==--::--==++=+XX.XX=XX+-::--==-==--.....:-=XXX +=++XXXX.+X+++++=== =::.::-::........ \n ....::+:-=----=+ ====+:++=+++:++++= =---+X+==++XX++====-=+XXXXX+XXX.X-XXX++--+==+X++=-:..::-++XX++XX+XXXXXX++==++++=+X=--:-:-::..:...... \n . ....::--=+++==+X=++=+===+++XXXXX++:==+++++XX++XXXX+++++-+X.XXXXXXXX+-XXX:++++X+=XXXX-+=-:--=++ XXXXXXXXXXXX++=-+++XXXXXX=-==---+::...:. ..\n",
" \n \n \n \n \n \n \n . . \n .. \n .. \n .. \n . . . \n . . .:.. . \n .. . .--:...:. .. =.. .. \n . .. .::... . ..-::....-: .. . . . \n .:-::...: --::. ..-:-:.... .:.:...=. . . -.... \n .--:::::.. .:--:. .::-:.::.. .....:-::-:. . .. ........ \n . ..:- --:--....=. .:-+-:.. .::--:-:..... =:::-:.. . .. =..:-=-:.:..... \n .. ...... -----=--:::--.. .:-=+++-:.. ..:-----+::-:. .::--:.. ......:::.. .:=:===--=+-::::.. ... \n .+.. :...:--:::---+==+=--==--=-. ..:-=+XX+==:. .::.-=-=XX++=--:.. .: -- ::.. .+.:-+X=-:.:--=++-===+==-=--:..+...::.. \n ......::..::=-------====:-+===-==-.. .:---=+X+==-::::: --==X=XX XXXXX=-:.:------:::. ..::+X+ ==--=++X++=++==+=--:--:..:::..... . \n .....::=--::=------==-= ====++==:--:::=+===++X+==--:-::=++XX -:XXXXXXX+-::---=++=-:.. .:-=++==+===+XXXXX+===++=--=+=-::-::.+.....: \n ....::- ===--======-+-===++XXXX+=---+====+X++:XX++++=--=+XXXXXXXXXXXXXX++==+X++XXXX+-::.:--==+X++++=XXXX.++=====++ XX++=:--::::..+.. .. .\n ....:::-:==++==== ==+-===+=+XXXXX+==-= =+==XXXXXXXX++XX===+XXXXXXXXXXXXXXX+==+++-XXXXX =--===++XXX=X.X.XXXXX++:==++XX+XX+=.--:::.:.........\n",
" \n \n \n \n \n \n \n \n . \n \n \n ..- \n .. .::. . . \n .:. .::.......... \n .:... -:..+ . .:...=. .. \n .::..:.. .:-:. ..=::.:... ..........: . \n ..: -:::::....-. .:--:.. ..::---:..... ::..-:. . .::...:. \n ....-::--:-=:::.::. ..:.==::. .::----::: .. .:.::..: .... .::--:::-:... . \n ...::::..::=-=---.--:--:. .:--XX+=-:.. ..::-=====--:=:. ..:-::.+. ...:==-:-..:- -=----==-:::...-.. .. \n . ......::-:::::::- -=---.---=-:. ..:--+X+==--::. ..::-=+X+XXXXX+=:....:-----:.. ..:-XX==-::---====-=+=---:::......:... \n .-....:-::-:--:--: --==---.--==-- .. :-=--==++=--::::::--==+X XXXXX-X+-:::=--:=-:::. ..:-=+=--=--==+++==++==+=-+--=-:.::..... \n ....:::---:-----=--=====-===-+==----+--=++==+X====-::--=++XXXXXXXXXXXX+=+--==+XX+=-:....:-==+====+=+XX+X+==+==-==.===-::::::.:.... \n ...+:---===---.======-===+X:XX++==-----==+XXXXXX+==----==+XXXXXXXXXXXXX++=+X+ +XXXX+=-:+:--=+++++X++XXXXX+++=====+X++.==-::-::......... .\n .....::-==++.==-==-++=+==+++XXXXXX+========XXXXXXXX-+++++XX-X:XXX+XXXXXX ++=++==+XXXX:X+==.=++XXXXXXXXXXXXXXX+===:+XX=XX+=--:-::::.........\n",
" \n \n \n \n \n \n \n \n \n \n . .. \n . .. \n . ..+ \n .. ..:.. ....... \n ....-..- . :-:. . ...::.=. .. ... \n ...=..+.::. .::. : :.. ..::-::....... . ..: .... \n .. ........:::::..::. ..:-=-:.. ..:-----::..-... .... :... .:.:.....:. .. \n ........:.+.::.::.:-:. ...:=+==-:..- ..:-=+X+=--::..+ . ....:. .:.:=-:.. .:..::::::-:. ... . \n ............::::::--::-=-.. ..:-=++=--:::... ..::-=+:XX+-+=-:.....::-::.. .:==-:-::+::::----.-=-::..:.....+. \n .....:..::::::::::--:+::::--.=:..-:-:-==:=+=-:..+.:.::-==+XXX++XX+-:..::-==--:.. ..:-==--:::-=-==-=--=--+-::::::.... . \n ......: ::.::::.---.-=---.=-+-----:::::--++====--:..-::-==++XXXXXXXX+=--::-=+XX+=-:.....:--=------==X==:==--=-==-----:......... \n .:.:::.---::.--+:--+-==++======-:--::---=+X-+++==-.::-=++++:XXX:XXXXX+======++XXX+=-::.::-========++X++++==+====++==--::... -..... \n .....:--:===+------=--====+XX.++X+=-==---=+XXXXXXX+==----=++:X:X=XXXXXXX+=++===+X.X++==---=++XX+++ XXXX==XX++=-==+XXX++=-::::::.......... \n ......::-=.=+ ==--=++++====+X=XX:XXX+ +X==++++X XXXXX++===-+XXX.XXXXX.-XX++=+==.=+XX-+++:+=++XXXXXX.XX.XXXXX+.X+=+++XXX X+=--::--::.........\n",
" \n \n \n \n \n \n \n \n .. \n \n \n \n .: .-.. \n . .... .. ::.. \n ..... :.. .::. ...::::......... \n . ...:.. ..:. ..= ::.. ....:--==-- ::.. ... ... . .. \n ... .......::. ..:----::=..= ..:-+XX= =--:... ..=.::. .=.:.:.. .. ..=...::. \n . +......:.........:: . ..:-----=::.. ..:--=XX+====::.=....::::. ..:--::=....-....:::::::.... .. \n .... .. .=.::-::::::....:::..=.::-------::....:.:---=++===++==-:..:-==--::.. .:----::::::::--::--+:::::....=.. \n ...............::----- -:: ::::.:=:::-===----:.=..::-=-=+X+X++++==-:::=-+XX+=--. ...:::--:::=::-=--=---:----.::.::. . \n ....::::....::::+:---:==---=-=-::+::::--=X+===-:+:..::-==++X XXXXX+==:--=-=+X+=+=-::..::------:--==+=++==---====---::.-.... \n ...:::--:-:::::------===++====++-----++-=+X+X+=:=-:.:::-=+-+XXXXXXX++++-==-=+-+===---:: -=+++== =:+++XX+++===+=+X+==--::... .-... \n ..+...:----------=========XX=X++-+X+===---==++=XX+==+==--==+++XXXXXXXXX+++==--==+X++===---++XXXX+.++XXXXXXXXX++===+XX++==--:::::.-.......- \n ....=..::-========-=:++++ +++XXXXXXXX++=====+-=++XXXXX+++++XX++XX:XXXXXX.X+======+XX.+++++++==+X:XXXXXXXX+X:XXX-X+++XXXX++==--:--:::::.......\n",
" \n \n \n \n \n \n \n \n \n \n . \n .. ..... \n . .::..:. \n .. .:.. .. ..-:-:::.. .. \n . .-. ....... . ..:::=---::::.. ... . ... \n ... . ... .:::...... .. ..:--++=-----:. ...:.. .. ...... . . \n :..: ..:. ... ..:=--::.... . .:: -=++==-=---:=....:::... .:::..... . +....... \n ..:-:.::.. .. ..-...:-:-- :::.....::---+====- ----::..:--=:-:.. .:--::.......+=:.:::........ \n . . ..:-:::::+.....+=.::-...-=--::::....:.::- -+=====+=+-::+:-=-++=--:. ..:-::.:.. ..::=::-::=-:::::..... \n ...............::------+::::::..::::::-=+==-::... .::--=+X+++ +X+=-: :--++==---::...::=--::.=.::--====--:--==-::.... \n ..:-::::..-.::-:-=-- =+==--:--=-:::::---=++=-::::....::-=++XXXX++++=+=-.--===.---: ::.:-=++=--:-==+++++-==-=-=+==--::.......... \n ...:::.:::::-:----- ==+XX+=====++---.:--=++++=---=-+::---=+XXXXXXXX+==--:---=++===- ::---+X++=-===++XXXX++=-==+++==--::::.:...... .:. \n ....:.::----------===-===+XXX X++ +X+===--+-==:+XX+==:====++=+++XX.XXXXX+===--==+XX+= ==--===+XX+++++X=XX XXXXX+++++X++==-.-:+:::.......... \n ..=...::---.-==+=+==++++++++-=XXXXXX X++++:+==:=++XXX++ +XXXX+XXXXXXX+.+XX++===+XXXX=X+.==--:==+XXXXXXXXXXXXXXXXX+++XX=++++-=-----.::........\n",
" \n \n \n \n \n \n \n \n \n :... \n .:..: \n .. .:.:..+. \n .. . .. ...::.:.. . .. \n . .:-:-:--:...:. . . \n .. . .:..... . .::+===--:.::-:=.. ...:.. . \n .. . .:=-:::.. -.. .::-+===--::---:.....::+.. .:.... . ... \n .:...... ...::--::-:..... ..::==-=--::-:-.::.::--:::... .--::.. . .... . \n .::::........ ......----... .. .:+::==-------=-::::-===-::::. .::--:. .. ......::........ \n .. ..+.::-::--:..:.:...+..:..::==-:....: .::::-++========::.:-+=.=-::::.-..:-.-:::. ...::-----:: ---::.. \n .....-.+..:.:.:::--+==--::::-:-:..:::::-=--:+...+...:+:-=++X++== =--:::-+--=-:::..:..:-==--::.:--=+++==-----==--::.. . \n .....-.=..:::-:::--=-=+=-----:-=::.: ::--==--:::::..::+--+XXXX-====--:.::-=-==---::.::::=+==-----=+XX++-==:-===.--::........ \n ....-:::::::.------= -=+X++===+=.==-- --:--==++=-+--::-:====+XXXX+-+==-::=--=++==---:.:--==++==--=++ XXXXX++===+++===-::-:::....+:... \n ....-.::-:------====+=.=++:XX+X++++ ===+ ======+XX+==-== ++++++X+XXXXX=+==-==++XXXX==--::+--==XX++-+X XXXXXX:++++++++====-+-::::::........ \n ...=.::--=-=== ===++XXX++XXXXXXXXXX+XXX:++======+X XX+.++X++XX+++X+XX++XXX++=++ XXXX+===--:--=++XXXXXXXXXXXXXX++++XX+X+++.=-------::::......\n",
" \n \n \n \n \n \n \n :.... \n . . \n . ....- \n .:..:::::.. \n .:..+.::... . \n =::::.::... . +...::.. \n .. .:-==-:-:....::... .::. \n .:-::... .:-+=---::..:::......::.. ....+ . \n . . :..:-:::.... .:--=--::::::::-:.::+:-:.. :=-::. .. \n ....... ..::-:::.. .:::=--::::::.-:---=---:...:.:.:--:...- ..::..=. ..... \n . ..:::::::..... .. ...-:-::. ...-::-+=-=-::::::.:-+--=--::.....::-::... -..:------:.:.:... \n .. . ....:=::-::-:-...:..:..::..:--::. . .:::--+ +=----::.=.:-=:--::.+....+.-=::.=...:-=:==--+- ---::=... \n ............::::::--:-+-::=:::-:..=:...:--:::.... ..+---=++X+=-=-::..:.:==:=--:....::::==--::---=++.====--=---=:-:..: ... \n .....::::-::.:--::---====-------::.:::.---==--:.: ..-==--==-++====-::...::==----=::.:.::-==-::--=+=++++++======----: :. .... \n ....-:=::::: --:==----=+==++=+==+=- --=.=+=.==X+=--:: -===+==+++X+-+==-::--==.++=-:::. ..:-==++===+++XX+++X====+===----:::::.:...:.. \n ....::---------==:++==+++++++++X+=++XX+=======+XX++ ===+==-++==++X XXX++==++++++++=--:.:.::-==XX++.XXXXXXXX++=++:XX++==--::+: :::.... . \n .. .::---=========+X++XXX+XXXXXXX++:XXXX+=+=== =+X=X++X+-+.+++++XXXX++XXXX+++==++XX+==----- -=+XXXXXXXXXXXXX+++++X-XXX++=--------.:::......\n",
" \n \n \n \n \n \n . \n . . \n :. .:.. \n .. .. ..::. \n . .....:: ..:. \n .-:...-.:.. . .. .. \n . .:-=-::... ...+. :+.. \n ... .:-+=-::::. ........:--.. ..-.. \n . .=-:.... .::==::::......-::::..-:. .::... \n .... ..::=. .:-==:=:.......::---::-. .:-::::. ........ \n ........ . ..=.:. .=::==--:+......:-+.--::. .+. ..::-:. .:-::::-:::.:. \n .....:-:.::-.. .. .. :. ..:.. .::--+=--:. .. ..:=::=::.......::-:.. ..:-----:::-=-:.:.:. \n ...... .::::::::.::--...... .....:-:.. .::--=+=.=::.... ..:=::.:-..:....::--+....:-=+=--==-==-::::::... \n ....-:.....::::::::::--- :::::.=....::--==-:.. . ..:=--+====--::.......-+-:.::+.. ...:=-:..::-.=======+-==----:::...: .. \n ......::...:-::--::--------==-=-:::---=++==++=-::-..:--:-=:==++===-:..::::==- ::.... .--=+.---=+X===+==+==+= ----::........ \n ...=:::-::::--=-==-=+==--====+++-:===-=-==.++X+=----=---======+XXXX+=-:-===++==--::. . .:--X+===.:XX+.+=+===+++====-::.::-:.+.... \n ...:::------+=-==+.++X+=++XXXXXX++=+X+===+=-==+XX++=+=++ =====+XXXXXX +===+==++X+==::....::-++X ++XXXXXXX+===-=++-+++=-=--=::::::=.-.... \n ...:-----=----==+X++XXXXXXXXXXX+XX+++++==+==-==+XX.+=++-=====+++XX:XXX-X+=+++=+XXX+=--======+++XXXXXXXXXX+===+++XX++++=+------::::=:.....\n"
]
@@ -0,0 +1,34 @@
import { AnimatePresence, motion } from "motion/react";
import AnimatedWidth from "@/components/shared/layout/animated-width";
import ArrowRight from "@/components/app/(home)/sections/hero-input/_svg/ArrowRight";
import Button from "@/components/shared/button/Button";
export default function HeroInputSubmitButton({
dirty,
}: {
dirty: boolean;
}) {
return (
<Button className="hero-input-button !p-0 bg-heat-100 hover:bg-heat-200" size="large" variant="primary">
<AnimatedWidth>
<AnimatePresence initial={false} mode="popLayout">
<motion.div
animate={{ opacity: 1, x: 0, filter: "blur(0px)" }}
exit={{ opacity: 0, x: -10, filter: "blur(2px)" }}
initial={{ opacity: 0, x: 10, filter: "blur(2px)" }}
key={dirty ? "dirty" : "clean"}
>
{dirty ? (
<div className="py-8 w-126 text-center text-white">Re-imagine Site</div>
) : (
<div className="w-60 py-8 flex-center">
<ArrowRight />
</div>
)}
</motion.div>
</AnimatePresence>
</AnimatedWidth>
</Button>
);
}
@@ -0,0 +1,84 @@
"use client";
import Link from "next/link";
import { useState } from "react";
import Globe from "./_svg/Globe";
import HeroInputSubmitButton from "./Button/Button";
import HeroInputTabsMobile from "./Tabs/Mobile/Mobile";
import HeroInputTabs from "./Tabs/Tabs";
import AsciiExplosion from "@/components/shared/effects/flame/ascii-explosion";
import { Endpoint } from "@/components/shared/Playground/Context/types";
export default function HeroInput() {
const [tab, setTab] = useState<Endpoint>(Endpoint.Scrape);
const [url, setUrl] = useState<string>("");
return (
<div className="max-w-552 mx-auto w-full z-[11] lg:z-[2] rounded-20 lg:-mt-76">
<div
className="overlay bg-accent-white"
style={{
boxShadow:
"0px 0px 44px 0px rgba(0, 0, 0, 0.02), 0px 88px 56px -20px rgba(0, 0, 0, 0.03), 0px 56px 56px -20px rgba(0, 0, 0, 0.02), 0px 32px 32px -20px rgba(0, 0, 0, 0.03), 0px 16px 24px -12px rgba(0, 0, 0, 0.03), 0px 0px 0px 1px rgba(0, 0, 0, 0.05), 0px 0px 0px 10px #F9F9F9",
}}
/>
<label className="p-16 flex gap-8 items-center w-full relative border-b border-black-alpha-5">
<Globe />
<input
className="w-full bg-transparent text-body-input text-accent-black placeholder:text-black-alpha-48"
placeholder="https://example.com"
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
(
document.querySelector(
".hero-input-button",
) as HTMLButtonElement
)?.click();
}
}}
/>
</label>
<div className="p-10 flex justify-between items-center relative">
<HeroInputTabs
setTab={setTab}
tab={tab}
allowedModes={[
Endpoint.Scrape,
Endpoint.Search,
Endpoint.Map,
Endpoint.Crawl,
]}
/>
<HeroInputTabsMobile
setTab={setTab}
tab={tab}
allowedModes={[
Endpoint.Scrape,
Endpoint.Search,
Endpoint.Map,
Endpoint.Crawl,
]}
/>
<Link
className="contents"
href={`/playground?endpoint=${tab}&url=${url}&autorun=true`}
>
<HeroInputSubmitButton dirty={url.length > 0} />
</Link>
</div>
<div className="h-248 top-84 cw-768 pointer-events-none absolute overflow-clip -z-10">
<AsciiExplosion className="-top-200" />
</div>
</div>
);
}
@@ -0,0 +1,196 @@
//@ts-nocheck
import { animate, AnimatePresence, cubicBezier, motion } from "motion/react";
import { useEffect, useRef, useState } from "react";
import { tabs } from "@/components/app/(home)/sections/hero-input/Tabs/Tabs";
import { cn } from "@/utils/cn";
import { Endpoint } from "@/components/shared/Playground/Context/types";
export default function HeroInputTabsMobile(props: {
setTab: (tab: Endpoint) => void;
tab: Endpoint;
allowedModes?: Endpoint[];
}) {
// Filter tabs based on allowedModes if provided
const visibleTabs = props.allowedModes
? tabs.filter((tab) => props.allowedModes.includes(tab.value))
: tabs;
const activeTab = visibleTabs.find((tab) => tab.value === props.tab)!;
const [isOpen, setIsOpen] = useState(false);
const ref = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (window.innerWidth > 996) {
return;
}
document.addEventListener("click", (e) => {
if (ref.current && e.composedPath().includes(ref.current)) {
return;
}
setIsOpen(false);
});
}, []);
return (
<>
<button
className="py-8 px-10 flex items-center rounded-10 inside-border before:border-black-alpha-4 relative lg:hidden gap-4"
ref={ref}
onClick={() => setIsOpen(!isOpen)}
>
<activeTab.icon size={24} alwaysHeat />
<div className="px-6 text-label-medium">{activeTab.label}</div>
<svg
className={cn(
"transition-all duration-200",
isOpen ? "rotate-180 text-accent-black" : "text-black-alpha-48",
)}
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.4001 10.2L12.0001 13.8L15.6001 10.2"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.25"
/>
</svg>
</button>
<AnimatePresence mode="popLayout">
{isOpen && (
<motion.div
animate={{ opacity: 1, filter: "blur(0px)" }}
className="absolute z-[1001] top-[calc(100%-4px)] left-[calc(50%-(50vw-6px))] w-[calc(100vw-12px)]"
exit={{ opacity: 0, filter: "blur(2px)" }}
initial={{ opacity: 0, filter: "blur(2px)" }}
transition={{
duration: 0.2,
ease: cubicBezier(0.25, 0.1, 0.25, 1.0),
}}
>
<div
className="mx-auto w-full p-4 max-w-366 rounded-16 bg-accent-white"
style={{
boxShadow:
"0 32px 40px 6px rgba(0, 0, 0, 0.02), 0 12px 32px 0 rgba(0, 0, 0, 0.02), 0 24px 32px -8px rgba(0, 0, 0, 0.02), 0 8px 16px -2px rgba(0, 0, 0, 0.02), 0 0 0 1px rgba(0, 0, 0, 0.04)",
}}
>
<div className="py-10 px-12 text-label-small text-black-alpha-48">
Output
</div>
<MenuItems
setTab={props.setTab}
tab={props.tab}
visibleTabs={visibleTabs}
/>
</div>
</motion.div>
)}
</AnimatePresence>
</>
);
}
function MenuItems(props: {
tab: Endpoint;
setTab: (tab: Endpoint) => void;
visibleTabs: typeof tabs;
}) {
const backgroundRef = useRef<HTMLDivElement>(null);
return (
<div className="relative">
<div
className="absolute top-0 opacity-0 left-0 bg-black-alpha-4 rounded-12 w-full pointer-events-none"
ref={backgroundRef}
/>
{props.visibleTabs.map((tab) => (
<div
className="text-label-small select-none cursor-pointer flex gap-12 py-12 px-16"
key={tab.value}
onClick={() => {
animate(
backgroundRef.current!,
{
scaleX: [1, 0.99, 1],
scaleY: [1, 0.96, 1],
opacity: [1, 0.9, 1],
},
{
ease: cubicBezier(0.165, 0.84, 0.44, 1),
duration: 0.15,
},
);
props.setTab(tab.value);
}}
onMouseEnter={async (e) => {
const child = e.currentTarget as HTMLElement;
if (backgroundRef.current?.getBoundingClientRect().height === 0) {
backgroundRef.current!.style.height = child.offsetHeight + "px";
}
if (getComputedStyle(backgroundRef.current!).opacity === "0") {
await animate(
backgroundRef.current!,
{
y: child.offsetTop,
},
{
ease: cubicBezier(0.165, 0.84, 0.44, 1),
duration: 0.01,
},
);
}
animate(backgroundRef.current!, { scale: 0.995 }).then(() =>
animate(backgroundRef.current!, { scale: 1 }),
);
animate(
backgroundRef.current!,
{
y: child.offsetTop,
opacity: 1,
height: child.offsetHeight + "px",
},
{
ease: cubicBezier(0.165, 0.84, 0.44, 1),
duration: 0.2,
},
);
}}
onMouseLeave={() => {
animate(
backgroundRef.current!,
{
opacity: 0,
},
{
ease: cubicBezier(0.165, 0.84, 0.44, 1),
duration: 0.2,
},
);
}}
>
<div className="size-24 p-2">
<tab.icon size={20} alwaysHeat />
</div>
<div className="px-6 text-label-medium">{tab.label}</div>
</div>
))}
</div>
);
}
@@ -0,0 +1,183 @@
import { animate } from "motion";
import { Fragment, useRef } from "react";
import EndpointsSearch from "@/components/app/(home)/sections/endpoints/EndpointsSearch/EndpointsSearch";
import EndpointsCrawl from "@/components/app/(home)/sections/endpoints/EndpointsCrawl/EndpointsCrawl";
import EndpointsMap from "@/components/app/(home)/sections/endpoints/EndpointsMap/EndpointsMap";
import EndpointsScrape from "@/components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape";
import EndpointsExtract from "@/components/app/(home)/sections/endpoints/EndpointsExtract/EndpointsExtract";
import { cn } from "@/utils/cn";
import Tooltip from "@/components/ui/shadcn/tooltip";
import { Endpoint } from "@/components/shared/Playground/Context/types";
export const tabs = [
{
label: "Scrape",
value: Endpoint.Scrape,
action: "scraping",
description:
"Scrapes only the specified URL without crawling subpages. Outputs the content from the page.",
icon: EndpointsScrape,
},
{
label: "Search",
value: Endpoint.Search,
description: "Search the web and get full content from results",
action: "searching",
icon: EndpointsSearch,
new: true,
},
{
label: "Map",
value: Endpoint.Map,
action: "mapping",
description: "Attempts to output all website's urls in a few seconds.",
icon: EndpointsMap,
},
{
label: "Crawl",
value: Endpoint.Crawl,
action: "crawling",
description:
"Crawls a URL and all its accessible subpages, outputting the content from each page.",
icon: EndpointsCrawl,
},
{
label: "Extract",
value: Endpoint.Extract,
action: "extracting",
description:
"Extract structured data from pages using LLMs. Provide URLs and a schema to get organized data.",
icon: EndpointsExtract,
new: true,
},
];
export default function HeroInputTabs(props: {
setTab: (tab: Endpoint) => void;
tab: Endpoint;
disabled?: boolean;
allowedModes?: Endpoint[];
}) {
// Filter tabs based on allowedModes if provided
const visibleTabs = props.allowedModes
? tabs.filter((tab) => props.allowedModes!.includes(tab.value))
: tabs;
const activeIndex = visibleTabs.findIndex((tab) => tab.value === props.tab);
const backgroundRef = useRef<HTMLDivElement>(null);
return (
<div
className="bg-black-alpha-4 flex items-center rounded-10 p-2 relative lg-max:hidden"
style={{
boxShadow:
"0px 6px 12px 0px rgba(0, 0, 0, 0.02) inset, 0px 0.75px 0.75px 0px rgba(0, 0, 0, 0.02) inset, 0px 0.25px 0.25px 0px rgba(0, 0, 0, 0.04) inset",
}}
>
<div
className="absolute top-2 left-2 h-32 bg-accent-white rounded-8 w-89"
ref={backgroundRef}
style={{
boxShadow:
"0px 6px 12px -3px rgba(0, 0, 0, 0.04), 0px 3px 6px -1px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 0.5px 0.5px 0px rgba(0, 0, 0, 0.06)",
}}
/>
{visibleTabs.map((tab, index) => (
<Fragment key={tab.value}>
{index > 0 && (
<div
className={cn(
"px-2 transition-all",
!(index !== activeIndex && index !== activeIndex + 1) &&
"opacity-0",
)}
>
<div className="w-1 h-12 bg-black-alpha-5" />
</div>
)}
<button
className={cn(
"text-label-medium p-6 relative transition-all group flex items-center",
tab.value === props.tab
? "text-accent-black"
: "text-black-alpha-56",
!tab.new && "pr-4",
)}
key={tab.value}
ref={(element) => {
if (element && backgroundRef.current) {
if (activeIndex === index) {
animate(
backgroundRef.current,
{
x: element.offsetLeft - 2,
width: element.offsetWidth - 1,
},
{
type: "spring",
stiffness: 200,
damping: 23,
},
);
}
}
}}
onClick={(e) => {
props.setTab(tab.value);
const t = e.target as HTMLElement;
const target =
t instanceof HTMLButtonElement
? t
: (t.closest("button") as HTMLButtonElement);
if (backgroundRef.current) {
animate(backgroundRef.current, { scale: 0.975 }).then(() =>
animate(backgroundRef.current!, { scale: 1 }),
);
animate(
backgroundRef.current,
{
x: target.offsetLeft - 2,
width: target.offsetWidth - 1,
},
{
type: "spring",
stiffness: 250,
damping: 25,
},
);
}
}}
>
{tab.icon && <tab.icon active={tab.value === props.tab} />}
<span className="px-6"> {tab.label}</span>
{tab.new && (
<div
className={cn(
"py-2 px-6 rounded-4 text-[12px]/[16px] font-[450] transition-all",
tab.value === props.tab
? "bg-heat-12 text-heat-100"
: "bg-black-alpha-4 text-black-alpha-56",
)}
>
New
</div>
)}
<Tooltip delay={0.25} description={tab.description} offset={-8} />
</button>
</Fragment>
))}
</div>
);
}
@@ -0,0 +1,19 @@
export default function ArrowRight() {
return (
<svg
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.6667 4.79163L16.875 9.99994M16.875 9.99994L11.6667 15.2083M16.875 9.99994H3.125"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
</svg>
);
}
@@ -0,0 +1,19 @@
export default function Globe() {
return (
<svg
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 19.7083C16.2572 19.7083 19.7083 16.2572 19.7083 12C19.7083 7.74276 16.2572 4.29163 12 4.29163M12 19.7083C7.74276 19.7083 4.29163 16.2572 4.29163 12C4.29163 7.74276 7.74276 4.29163 12 4.29163M12 19.7083C10.044 19.7083 8.45829 16.2572 8.45829 12C8.45829 7.74276 10.044 4.29163 12 4.29163M12 19.7083C13.956 19.7083 15.5416 16.2572 15.5416 12C15.5416 7.74276 13.956 4.29163 12 4.29163M19.5 12H4.49996"
stroke="#262626"
strokeLinecap="square"
strokeOpacity="0.32"
strokeWidth="1.25"
/>
</svg>
);
}
@@ -0,0 +1,159 @@
import { useCallback, useEffect, useState } from "react";
import CurvyRect, { Connector } from "@/components/shared/layout/curvy-rect";
import { encryptText } from "@/components/app/(home)/sections/hero/Title/Title";
import HeroScrapingCodeLoading from "./Loading/Loading";
import Code from "@/components/ui/code";
const URL = {
value: "https://example.com",
encrypted: "h=t*A:!/z!aap?A-cZz",
};
const MARKDOWN = {
value: "# Getting Started...",
encrypted: "# ?0z-ang S*a-Z-a0*9",
};
const TITLE = {
value: "Guide",
encrypted: "G!=*?",
};
const SCREENSHOT = {
value: "https://example.com/hero",
encrypted: "ht-=*:/?*Za!zl=-?a9?h0-!",
};
export default function HeroScrapingCode({ step }: { step: number }) {
const [url, setUrl] = useState(URL.encrypted);
const [markdown, setMarkdown] = useState(MARKDOWN.encrypted);
const [title, setTitle] = useState(TITLE.encrypted);
const [screenshot, setScreenshot] = useState(SCREENSHOT.encrypted);
const reveal = useCallback((value: string, setter: (v: string) => void) => {
let progress = 0;
let increaseProgress = -10;
const animate = () => {
increaseProgress = (increaseProgress + 1) % 5;
if (increaseProgress === 4) {
progress += 0.2;
}
if (progress > 1) {
progress = 1;
setter(encryptText(value, progress, { randomizeChance: 0.3 }));
return;
}
setter(encryptText(value, progress, { randomizeChance: 0.3 }));
const interval = 70 + progress * 30;
setTimeout(animate, interval);
};
animate();
}, []);
useEffect(() => {
if (step >= 0 && url === URL.encrypted) reveal(URL.value, setUrl);
if (step >= 3 && title === TITLE.encrypted) reveal(TITLE.value, setTitle);
if (step >= 4 && markdown === MARKDOWN.encrypted)
reveal(MARKDOWN.value, setMarkdown);
if (step >= 5 && screenshot === SCREENSHOT.encrypted)
reveal(SCREENSHOT.value, setScreenshot);
const interval = setInterval(() => {
if (step < 0) {
URL.encrypted = encryptText(URL.value, 0, { randomizeChance: 0.3 });
setUrl(URL.encrypted);
}
if (step < 3) {
TITLE.encrypted = encryptText(TITLE.value, 0, { randomizeChance: 0.3 });
setTitle(TITLE.encrypted);
}
if (step < 4) {
MARKDOWN.encrypted = encryptText(MARKDOWN.value, 0, {
randomizeChance: 0.3,
});
setMarkdown(MARKDOWN.encrypted);
}
if (step < 5) {
SCREENSHOT.encrypted = encryptText(SCREENSHOT.value, 0, {
randomizeChance: 0.3,
});
setScreenshot(SCREENSHOT.encrypted);
}
}, 70);
return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [step, reveal]);
return (
<div className="h-280 lg:h-310 flex z-[1] w-full relative -top-1 bg-background-base">
<Connector className="lg:hidden absolute -top-10 -left-[10.5px]" />
<Connector className="lg:hidden absolute -top-10 -right-[10.5px]" />
<div className="lg:hidden absolute top-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint" />
<Connector className="lg:hidden absolute -bottom-10 -left-[10.5px]" />
<Connector className="lg:hidden absolute -bottom-10 -right-[10.5px]" />
<div className="lg:hidden absolute bottom-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint" />
<div className="flex-1 lg-max:min-w-0 h-full relative lg:inside-border before:border-border-faint">
<CurvyRect className="overlay" allSides />
<CurvyRect
className="size-32 absolute bottom-0 -left-31 lg-max:hidden"
bottomRight
/>
<div className="pl-15 border-b border-border-faint p-13 flex justify-between items-center">
<div className="flex gap-10 items-center">
{Array.from({ length: 3 }).map((_, index) => (
<div
className="w-12 h-12 rounded-full relative inside-border before:border-border-muted"
key={index}
/>
))}
</div>
<div className="text-mono-x-small font-mono text-black-alpha-20">
[ .JSON ]
</div>
</div>
<div className="overflow-x-scroll hide-scrollbar lg:contents relative">
<Code
code={`[
{
"url": "${url}",
"markdown": "${markdown}",
"json": { "title": "${title}", "docs": "..." },
"screenshot": "${screenshot}.png"
}
]`}
language="json"
/>
</div>
<HeroScrapingCodeLoading finished={step >= 6} />
</div>
<div className="w-28 lg-max:hidden -ml-1 relative">
<div className="h-1 w-[calc(100%-1px)] top-0 left-0 absolute bg-border-faint" />
<CurvyRect className="overlay" topLeft />
</div>
<div className="h-53 lg-max:hidden -right-37 bottom-0 absolute w-65">
<CurvyRect className="overlay" bottom topRight />
<div className="overlay border-y border-border-faint" />
</div>
</div>
);
}
@@ -0,0 +1,63 @@
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useState } from "react";
import { encryptText } from "@/components/app/(home)/sections/hero/Title/Title";
import AnimatedWidth from "@/components/shared/layout/animated-width";
import Spinner from "@/components/ui/spinner";
export default function HeroScrapingCodeLoading({
finished,
}: {
finished: boolean;
}) {
const [scrapingText, setScrapingText] = useState("Scraping...");
useEffect(() => {
if (finished) return;
let timeout = 0;
let tick = 0;
const animate = () => {
tick += 1;
if (tick % 3 !== 0) {
setScrapingText(
encryptText("Scraping", 0, {
randomizeChance: 0.6 + Math.random() * 0.3,
}) + "...",
);
} else {
setScrapingText("Scraping...");
}
const interval = 80;
timeout = window.setTimeout(animate, interval);
};
animate();
return () => {
window.clearTimeout(timeout);
};
}, [finished]);
return (
<div className="flex gap-6 p-6 pr-0 rounded-full inside-border before:border-border-faint absolute right-20 bottom-20 text-mono-small font-mono text-accent-black">
<Spinner finished={finished} />
<AnimatedWidth initial={{ width: "auto" }}>
<AnimatePresence initial={false} mode="popLayout">
<motion.div
animate={{ opacity: 1, x: 0 }}
className="pr-12"
exit={{ opacity: 0, x: 10 }}
initial={{ opacity: 0, x: -10 }}
>
{finished ? "Scrape Completed" : scrapingText}
</motion.div>
</AnimatePresence>
</AnimatedWidth>
</div>
);
}
@@ -0,0 +1,18 @@
export default function Check() {
return (
<svg
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
clipRule="evenodd"
d="M10 2.5C5.85786 2.5 2.5 5.85786 2.5 10C2.5 14.1421 5.85786 17.5 10 17.5C14.1421 17.5 17.5 14.1421 17.5 10C17.5 5.85786 14.1421 2.5 10 2.5ZM12.8305 8.59995C13.0928 8.27937 13.0455 7.80685 12.7249 7.54455C12.4043 7.28226 11.9318 7.32951 11.6695 7.65009L8.81932 11.1337L7.90533 10.2197C7.61244 9.9268 7.13756 9.9268 6.84467 10.2197C6.55178 10.5126 6.55178 10.9875 6.84467 11.2804L8.34467 12.7804C8.4945 12.9302 8.70073 13.0096 8.91236 12.9991C9.12399 12.9885 9.32129 12.8889 9.45547 12.725L12.8305 8.59995Z"
fill="#FA5D19"
fillRule="evenodd"
/>
</svg>
);
}
@@ -0,0 +1,21 @@
.hero-scraping-highlight::before {
animation: hero-scraping-highlight-before 1s linear infinite;
transition: none !important;
}
@keyframes hero-scraping-highlight-before {
0% {
border-color: var(--border-loud);
opacity: 1;
}
40% {
opacity: 0.25;
border-color: var(--heat-100);
}
80% {
border-color: var(--border-loud);
opacity: 1;
}
}
@@ -0,0 +1,314 @@
"use client";
import { animate } from "motion";
import { useEffect, useRef, useState } from "react";
import CurvyRect from "@/components/shared/layout/curvy-rect";
import { sleep } from "@/utils/sleep";
import BrowserMobile from "./_svg/BrowserMobile";
import BrowserTab from "./_svg/BrowserTab";
import HeroScrapingCode from "./Code/Code";
import HeroScrapingTag from "./Tag/Tag";
import "./HeroScraping.css";
export default function HeroScraping() {
const [step, setStep] = useState(-1);
const navigationRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLDivElement>(null);
const h1Ref = useRef<HTMLDivElement>(null);
const descriptionRef = useRef<HTMLDivElement>(null);
const ctaRef = useRef<HTMLDivElement>(null);
const highlightRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const wrapElement = async (
element: HTMLElement,
{ borderRadius }: { borderRadius?: number } = {},
) => {
if (!containerRef.current) return;
const containerBnds = containerRef.current.getBoundingClientRect();
const elementBnds = element.getBoundingClientRect();
if (!highlightRef.current) return;
try {
if (highlightRef.current) {
await animate(highlightRef.current, { opacity: 0 }, { duration: 0.3 });
}
} catch (error) {
console.error("Error animating highlight:", error);
}
if (!highlightRef.current) return;
Object.assign(highlightRef.current.style, {
left: elementBnds.left - containerBnds.left - 4 + "px",
top: elementBnds.top - containerBnds.top - 4 + "px",
width: elementBnds.width + 8 + "px",
height: elementBnds.height + 8 + "px",
borderRadius: borderRadius ? `${borderRadius}px` : undefined,
});
try {
await animate(
highlightRef.current,
{ opacity: [1, 0.5, 0.3, 0.8, 0.6, 0.9, 0.7, 1] },
{ duration: 0.4 },
);
} catch (error) {
console.error("Error animating highlight:", error);
}
};
const start = async () => {
setStep(0);
if (!highlightRef.current) return;
await animate(highlightRef.current, {
scale: 1,
opacity: 1,
});
await sleep(700);
setTimeout(() => setStep(1), 300);
if (navigationRef.current) {
await wrapElement(navigationRef.current);
}
await sleep(1200);
setTimeout(() => setStep(2), 300);
if (buttonRef.current) {
await wrapElement(buttonRef.current);
}
await sleep(1200);
setTimeout(() => setStep(3), 300);
if (h1Ref.current) {
await wrapElement(h1Ref.current, { borderRadius: 12 });
}
await sleep(1200);
setTimeout(() => setStep(4), 300);
if (descriptionRef.current) {
await wrapElement(descriptionRef.current, { borderRadius: 8 });
}
await sleep(1200);
setTimeout(() => setStep(5), 300);
if (ctaRef.current) {
await wrapElement(ctaRef.current, { borderRadius: 24 });
}
await sleep(1500);
setTimeout(() => setStep(6), 300);
if (highlightRef.current) {
await animate(highlightRef.current, { opacity: 0 }, { duration: 0.3 });
}
};
let started = false;
const onScroll = () => {
if (started) return;
if (window.scrollY > 100) {
started = true;
start();
window.removeEventListener("scroll", onScroll);
}
};
setTimeout(() => {
if (started) return;
started = true;
start();
window.removeEventListener("scroll", onScroll);
}, 2000);
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
return (
<div
className="pt-56 lg:pt-25 lg:px-25 container -mt-36 relative"
ref={containerRef}
>
<div className="h-53 absolute top-[calc(100%-1px)] w-full left-0">
<div className="h-1 bg-border-faint bottom-0 left-0 w-full absolute" />
</div>
<div
className="left-61 top-89 rounded-[16px] size-32 absolute hero-scraping-highlight inside-border before:border-border-loud opacity-0 scale-[0.9]"
ref={highlightRef}
/>
<div className="overlay lg-max:hidden">
<div className="h-1 absolute bottom-0 w-full left-0 bg-border-faint" />
<CurvyRect className="overlay" bottom />
</div>
<div className="lg:h-370 rounded-t-16 lg-max:pt-70 relative">
<div className="overlay mask-border lg-max:hidden p-1 bg-gradient-to-b from-black/7 to-transparent" />
<div className="top-17 left-17 flex gap-8 items-center absolute lg-max:hidden">
{Array.from({ length: 3 }).map((_, index) => (
<div
className="w-10 h-10 rounded-full relative inside-border before:border-border-muted"
key={index}
/>
))}
</div>
<div className="pt-42 lg:px-6">
<BrowserMobile className="absolute top-0 cw-316 lg:hidden" />
<BrowserTab className="absolute top-[7.5px] left-70 lg-max:hidden bg-background-base z-[1]" />
<div className="absolute size-18 top-17 left-89 lg-max:hidden inside-border before:border-border-muted z-[2] rounded-full" />
<div className="rounded-t-16 relative lg:h-330 lg:p-6">
<div className="overlay mask-border lg-max:hidden p-1 bg-gradient-to-b from-black/7 to-transparent" />
<div className="lg:h-322 rounded-t-10 relative">
<div className="overlay mask-border lg-max:hidden p-1 bg-gradient-to-b z-[2] from-black/7 to-transparent" />
<div className="px-28 lg-max:hidden py-20 flex justify-between items-center relative border-b border-border-faint">
<div className="flex gap-8 items-center relative">
<div className="size-24 rounded-full relative inside-border before:border-border-muted" />
<div className="w-64 h-12 rounded-full relative inside-border before:border-border-muted" />
{step >= 0 && (
<HeroScrapingTag
active={step === 0}
className="absolute left-[calc(100%+24px)] top-0"
initial={{ x: -12, opacity: 0 }}
label="Logo"
/>
)}
</div>
<div
className="absolute top-24 center-x flex gap-8"
ref={navigationRef}
>
{step >= 1 && (
<HeroScrapingTag
active={step === 1}
className="absolute right-[calc(100%+20px)] -top-4"
initial={{ x: 12, opacity: 0 }}
label="Navigation"
/>
)}
{Array.from({ length: 4 }).map((_, index) => (
<div
className="w-64 h-16 rounded-full relative inside-border before:border-border-muted"
key={index}
/>
))}
</div>
<div
className="w-72 h-24 rounded-full relative inside-border before:border-border-muted"
ref={buttonRef}
>
{step >= 2 && (
<HeroScrapingTag
active={step === 2}
className="absolute right-[calc(100%+20px)] top-0"
initial={{ x: 12, opacity: 0 }}
label="Button"
/>
)}
</div>
</div>
<div className="lg:grid grid-cols-2">
<div className="pt-40 pl-151 flex gap-16 relative lg-max:hidden">
<CurvyRect
className="size-32 -top-1 -right-1 absolute"
topRight
/>
<div className="h-53 lg-max:hidden -left-37 bottom-1 absolute w-65">
<CurvyRect className="overlay" left />
</div>
<div>
<div
className="flex gap-16 mb-16 flex-wrap w-300 relative"
ref={h1Ref}
>
{step >= 3 && (
<HeroScrapingTag
active={step === 3}
className="absolute right-[calc(100%+16px)] top-0"
initial={{ x: 12, opacity: 0 }}
label="H1 Title"
/>
)}
<div className="w-144 h-32 rounded-8 relative inside-border before:border-border-muted" />
<div className="w-82 h-32 rounded-8 relative inside-border before:border-border-muted" />
<div className="w-100 h-32 rounded-8 relative inside-border before:border-border-muted" />
<div className="w-180 h-32 rounded-8 relative inside-border before:border-border-muted" />
</div>
<div
className="flex gap-6 mb-32 flex-wrap w-300 relative"
ref={descriptionRef}
>
{step >= 4 && (
<HeroScrapingTag
active={step === 4}
className="absolute top-0 right-[calc(100%+16px)]"
initial={{ x: 12, opacity: 0 }}
label="Description"
/>
)}
<div className="w-131 h-10 rounded-full relative inside-border before:border-border-muted" />
<div className="w-72 h-10 rounded-full relative inside-border before:border-border-muted" />
<div className="w-34 h-10 rounded-full relative inside-border before:border-border-muted" />
<div className="w-56 h-10 rounded-full relative inside-border before:border-border-muted" />
<div className="w-116 h-10 rounded-full relative inside-border before:border-border-muted" />
<div className="w-116 h-10 rounded-full relative inside-border before:border-border-muted" />
</div>
<div
className="w-64 h-24 rounded-full relative inside-border before:border-border-muted"
ref={ctaRef}
>
{step >= 5 && (
<HeroScrapingTag
active={step === 5}
className="absolute top-0 right-[calc(100%+16px)]"
initial={{ x: 12, opacity: 0 }}
label="CTA Button"
/>
)}
</div>
</div>
</div>
<HeroScrapingCode step={step} />
</div>
</div>
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,69 @@
import { motion } from "motion/react";
import { ComponentProps, useEffect, useState } from "react";
import { encryptText } from "@/components/app/(home)/sections/hero/Title/Title";
import { cn } from "@/utils/cn";
export default function HeroScrapingTag({
active,
label,
...attrs
}: ComponentProps<typeof motion.div> & { active?: boolean; label: string }) {
const [value, setValue] = useState(
encryptText(label, 0, { randomizeChance: 0 }),
);
useEffect(() => {
let progress = 0;
let increaseProgress = -10;
const animate = () => {
increaseProgress = (increaseProgress + 1) % 5;
if (increaseProgress === 4) {
progress += 0.2;
}
if (progress > 1) {
progress = 1;
setValue(encryptText(label, progress, { randomizeChance: 0 }));
return;
}
setValue(encryptText(label, progress, { randomizeChance: 0 }));
const interval = 40 + progress * 20;
setTimeout(animate, interval);
};
animate();
}, []);
return (
<motion.div
{...attrs}
animate={{
x: 0,
y: 0,
scale: 1,
opacity: 1,
filter: "blur(0px)",
}}
className={cn(
"py-4 h-max font-mono w-max px-6 text-mono-x-small rounded-6 transition-colors",
active
? "bg-heat-12 text-heat-100"
: "bg-black-alpha-4 text-black-alpha-56",
attrs.className,
)}
transition={{
type: "spring",
stiffness: 100,
damping: 18,
}}
>
{value}
</motion.div>
);
}
@@ -0,0 +1,137 @@
export default function BrowserMobile(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
fill="none"
height="112"
viewBox="0 0 316 112"
width="316"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g clipPath="url(#clip0_2254_6088)">
<rect
height="370"
rx="15.5"
stroke="url(#paint0_linear_2254_6088)"
strokeOpacity="0.07"
width="315"
x="0.5"
y="0.5"
/>
<mask fill="white" id="path-2-inside-1_2254_6088">
<path d="M240 32C240 37.5228 244.477 42 250 42H294C302.837 42 310 49.1634 310 58V361C310 366.523 305.523 371 300 371H16C10.4772 371 6 366.523 6 361V58C6 49.1634 13.1634 42 22 42H70C75.5228 42 80 37.5228 80 32V18C80 12.4772 84.4772 8 90 8H230C235.523 8 240 12.4772 240 18V32Z" />
</mask>
<path
d="M310 58L311 58L310 58ZM22 42L22 41L22 42ZM250 42V43H294V42V41H250V42ZM294 42V43C302.284 43 309 49.7157 309 58L310 58L311 58C311 48.6112 303.389 41 294 41V42ZM310 58H309V361H310H311V58H310ZM300 371V370H16V371V372H300V371ZM6 361H7V58H6H5V361H6ZM6 58H7C7 49.7157 13.7157 43 22 43L22 42L22 41C12.6112 41 5 48.6112 5 58H6ZM22 42V43H70V42V41H22V42ZM80 32H81V18H80H79V32H80ZM90 8V9H230V8V7H90V8ZM240 18H239V32H240H241V18H240ZM230 8V9C234.971 9 239 13.0294 239 18H240H241C241 11.9249 236.075 7 230 7V8ZM70 42V43C76.0751 43 81 38.0751 81 32H80H79C79 36.9706 74.9706 41 70 41V42ZM16 371V370C11.0294 370 7 365.971 7 361H6H5C5 367.075 9.92487 372 16 372V371ZM80 18H81C81 13.0294 85.0294 9 90 9V8V7C83.9249 7 79 11.9249 79 18H80ZM310 361H309C309 365.971 304.971 370 300 370V371V372C306.075 372 311 367.075 311 361H310ZM250 42V41C245.029 41 241 36.9706 241 32H240H239C239 38.0751 243.925 43 250 43V42Z"
fill="url(#paint1_linear_2254_6088)"
fillOpacity="0.07"
mask="url(#path-2-inside-1_2254_6088)"
/>
<rect
height="310"
rx="9.5"
stroke="url(#paint2_linear_2254_6088)"
strokeOpacity="0.07"
width="291"
x="12.5"
y="48.5"
/>
<rect
height="9"
rx="4.5"
stroke="#E8E8E8"
width="9"
x="17.5"
y="17.5"
/>
<rect
height="9"
rx="4.5"
stroke="#E8E8E8"
width="9"
x="35.5"
y="17.5"
/>
<rect
height="9"
rx="4.5"
stroke="#E8E8E8"
width="9"
x="53.5"
y="17.5"
/>
<rect
height="17"
rx="8.5"
stroke="#E8E8E8"
width="17"
x="89.5"
y="17.5"
/>
<mask fill="white" id="path-10-inside-2_2254_6088">
<path d="M12 48H304V112H12V48Z" />
</mask>
<path
d="M304 112V111H12V112V113H304V112Z"
fill="#EDEDED"
mask="url(#path-10-inside-2_2254_6088)"
/>
<rect
height="23"
rx="11.5"
stroke="#E8E8E8"
width="71"
x="212.5"
y="68.5"
/>
<circle cx="44" cy="80" r="11.5" stroke="#E8E8E8" />
<rect
height="11"
rx="5.5"
stroke="#E8E8E8"
width="63"
x="64.5"
y="74.5"
/>
</g>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint0_linear_2254_6088"
x1="158"
x2="158"
y1="0"
y2="371"
>
<stop />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint1_linear_2254_6088"
x1="529.5"
x2="529.5"
y1="8"
y2="324"
>
<stop offset="0.4" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint2_linear_2254_6088"
x1="158"
x2="158"
y1="48"
y2="359"
>
<stop />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<clipPath id="clip0_2254_6088">
<rect fill="white" height="112" width="316" />
</clipPath>
</defs>
</svg>
);
}
@@ -0,0 +1,19 @@
import { HTMLAttributes } from "react";
export default function BrowserTab(attrs: HTMLAttributes<SVGSVGElement>) {
return (
<svg
fill="none"
height="36"
viewBox="0 0 226 36"
width="226"
xmlns="http://www.w3.org/2000/svg"
{...attrs}
>
<path
d="M0 35C5.52285 35 10 30.5228 10 25V11C10 5.47715 14.4772 1 20 1H206C211.523 1 216 5.47715 216 11V25C216 30.5228 220.477 35 226 35"
stroke="#E8E8E8"
/>
</svg>
);
}
@@ -0,0 +1,184 @@
"use client";
import { Fragment } from "react";
import CurvyRect from "@/components/shared/layout/curvy-rect";
import CenterStar from "./_svg/CenterStar";
export default function HomeHeroBackground() {
return (
<div className="overlay contain-layout pointer-events-none lg-max:hidden">
<div className="top-100 h-[calc(100%-99px)] border-border-faint border-y w-full left-0 absolute" />
<div className="cw-[1314px] z-[105] absolute top-0 border-x border-border-faint h-full">
<div className="text-mono-x-small font-mono text-black-alpha-12 select-none">
<div className="absolute top-111 -left-1 w-102 text-center">
{" "}
[ 200 OK ]{" "}
</div>
<div className="absolute bottom-10 -left-1 w-102 text-center">
{" "}
[ .JSON ]{" "}
</div>
<div className="absolute top-111 -right-1 w-102 text-center">
{" "}
[ SCRAPE ]{" "}
</div>
<div className="absolute bottom-10 -right-1 w-102 text-center">
{" "}
[ .MD ]{" "}
</div>
</div>
<div className="top-302 h-1 left-0 bg-border-faint w-303 absolute" />
<div className="top-403 h-1 left-0 bg-border-faint w-303 absolute" />
<div className="top-504 h-1 left-100 bg-border-faint w-203 absolute" />
<div className="top-302 h-1 right-0 bg-border-faint w-303 absolute" />
<div className="top-403 h-1 right-0 bg-border-faint w-303 absolute" />
<div className="top-504 h-1 right-100 bg-border-faint w-203 absolute" />
{Array.from({ length: 2 }, (_, i) => (
<Fragment key={i}>
<CurvyRect
bottomLeft={i === 1}
bottomRight={i === 0}
className="w-101 h-[calc(100%-99px)] top-100 absolute"
style={{ [i === 0 ? "left" : "right"]: -101 }}
/>
<CurvyRect
className="w-102 h-203 top-100 absolute"
style={{ [i === 0 ? "left" : "right"]: -1 }}
allSides
/>
<CurvyRect
className="size-102 top-302 absolute"
style={{ [i === 0 ? "left" : "right"]: -1 }}
allSides
/>
<CurvyRect
className="w-102 h-203 top-403 absolute"
style={{ [i === 0 ? "left" : "right"]: -1 }}
allSides
/>
</Fragment>
))}
</div>
<div className="cw-[910px] absolute top-100 border-x border-border-faint h-[calc(100%-99px)]" />
<div className="cw-[708px] absolute top-100 border-x border-border-faint h-[calc(100%-99px)]">
<CenterStar className="absolute top-77 -right-24 z-[1]" />
<CenterStar className="absolute top-77 -left-24 z-[1]" />
</div>
<CurvyRect
className="cw-[708px] absolute top-100 h-[calc(100%-99px)]"
bottom
/>
<div className="cw-[506px] absolute top-100 border-x border-border-faint h-102" />
<div className="cw-[304px] absolute top-100 border-x border-border-faint h-102" />
<div className="cw-[102px] absolute top-100 border-x border-border-faint h-102" />
<div className="top-201 h-1 bg-border-faint cw-[1112px] absolute" />
<div className="cw-[1112px] absolute top-0 h-full">
<CurvyRect className="w-full absolute top-full h-100 left-0" top />
<CurvyRect
className="w-100 absolute top-full h-100 -left-99"
topRight
/>
<CurvyRect
className="w-100 absolute top-full h-100 -right-99"
topLeft
/>
{Array.from({ length: 5 }, (_, i) => (
<Fragment key={i}>
<CurvyRect
className="size-102 absolute left-0"
style={{
top: 100 + i * 101,
}}
allSides
/>
<CurvyRect
className="size-102 absolute right-0"
style={{
top: 100 + i * 101,
}}
allSides
/>
</Fragment>
))}
<CurvyRect
className="size-102 absolute left-101 top-100"
bottomLeft
top
/>
<CurvyRect
className="size-102 absolute left-101 top-201"
bottom
topLeft
/>
<CurvyRect
className="size-102 absolute right-101 top-100"
bottomRight
top
/>
<CurvyRect
className="size-102 absolute right-101 top-201"
bottom
topRight
/>
{Array.from({ length: 3 }, (_, i) => (
<Fragment key={i}>
<CurvyRect
className="size-102 absolute left-101"
style={{
top: 302 + i * 101,
}}
allSides
/>
<CurvyRect
className="size-102 absolute right-101"
style={{
top: 302 + i * 101,
}}
allSides
/>
</Fragment>
))}
<CurvyRect
className="size-102 absolute top-100 left-202"
bottomRight
top
/>
{Array.from({ length: 5 }, (_, i) => (
<CurvyRect
className="size-102 absolute top-100"
key={i}
style={{ left: 303 + i * 101 }}
allSides
/>
))}
<CurvyRect
className="size-102 absolute top-100 right-202"
bottomLeft
top
/>
</div>
</div>
);
}
@@ -0,0 +1,56 @@
"use client";
import { useEffect, useState } from "react";
import { Connector } from "@/components/shared/layout/curvy-rect";
import {
useHeaderContext,
useHeaderHeight,
} from "@/components/shared/header/HeaderContext";
import { cn } from "@/utils/cn";
export const BackgroundOuterPiece = () => {
const [noRender, setNoRender] = useState(false);
const { dropdownContent } = useHeaderContext();
const { headerHeight } = useHeaderHeight();
useEffect(() => {
const heroContent = document.getElementById("hero-content");
if (!heroContent) {
// If hero-content doesn't exist, don't render the background piece
setNoRender(true);
return;
}
const heroContentHeight = heroContent.clientHeight;
const onScroll = () => {
setNoRender(window.scrollY > heroContentHeight - 120);
};
onScroll();
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
return (
<div
className={cn(
"cw-[1335px] transition-all z-[105] absolute top-0 flex justify-between h-[calc(100%+21px)] duration-[200ms] pointer-events-none",
{ "opacity-0": noRender || dropdownContent || !headerHeight },
)}
style={{
paddingTop: headerHeight - 10,
}}
>
<div className="h-[3000px] w-[calc(100%-21px)] left-[10.5px] absolute bottom-21 border-x border-border-faint" />
<Connector className="sticky" style={{ top: headerHeight - 10 }} />
<Connector className="sticky" style={{ top: headerHeight - 10 }} />
</div>
);
};
@@ -0,0 +1,21 @@
export default function CenterStar({
...props
}: React.SVGProps<SVGSVGElement>) {
return (
<svg
fill="none"
height="47"
viewBox="0 0 47 47"
width="47"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M24 18C24 21.3137 26.6863 24 30 24H34V25H30C26.6863 25 24 27.6863 24 31V35H23V31C23 27.6863 20.3137 25 17 25H13V24H17C20.3137 24 23 21.3137 23 18V14H24V18Z"
fill="var(--heat-100)"
fillOpacity="1"
/>
<circle cx="23.5" cy="23.5" r="23" stroke="#EDEDED" strokeOpacity="1" />
</svg>
);
}
@@ -0,0 +1,43 @@
import Link from "next/link";
export default function HomeHeroBadge() {
return (
<Link
className="p-4 rounded-full flex w-max mx-auto mb-12 lg:mb-16 items-center relative inside-border before:border-border-faint group"
href="#"
onClick={(e) => e.preventDefault()}
>
<div className="px-8 text-label-x-small">Website Builder</div>
<div className="p-1">
<div className="size-18 bg-accent-black flex-center rounded-full group-hover:bg-heat-100 transition-all group-hover:w-30">
<svg
fill="none"
height="8"
viewBox="0 0 10 8"
width="10"
xmlns="http://www.w3.org/2000/svg"
>
<path
className="transition-all -translate-x-2 group-hover:translate-x-0"
d="M6 1L9 4L6 7"
stroke="white"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.25"
/>
<path
className="transition-all -translate-x-3 group-hover:translate-x-0 scale-x-[0] group-hover:scale-x-[1] origin-right"
d="M1 4L9 4"
stroke="white"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.25"
/>
</svg>
</div>
</div>
</Link>
);
}
@@ -0,0 +1,62 @@
import Link from "next/link";
import { Connector } from "@/components/shared/layout/curvy-rect";
import HeroFlame from "@/components/shared/effects/flame/hero-flame";
import HomeHeroBackground from "./Background/Background";
import { BackgroundOuterPiece } from "./Background/BackgroundOuterPiece";
import HomeHeroBadge from "./Badge/Badge";
import HomeHeroPixi from "./Pixi/Pixi";
import HomeHeroTitle from "./Title/Title";
import HeroInput from "../hero-input/HeroInput";
import HeroScraping from "../hero-scraping/HeroScraping";
export default function HomeHero() {
return (
<section className="overflow-x-clip" id="home-hero">
<div
className="pt-28 lg:pt-254 lg:-mt-100 pb-115 relative"
id="hero-content"
>
<HomeHeroPixi />
<HeroFlame />
<BackgroundOuterPiece />
<HomeHeroBackground />
<div className="relative container px-16">
<HomeHeroBadge />
<HomeHeroTitle />
<p className="text-center text-body-large">
Power your AI apps with clean data crawled
<br className="lg-max:hidden" />
from any website.
<Link
className="bg-black-alpha-4 hover:bg-black-alpha-6 lg:ml-4 rounded-6 px-8 lg:px-6 text-label-large lg-max:py-2 h-30 lg:h-24 block lg-max:mt-8 lg-max:mx-auto lg-max:w-max lg:inline-block gap-4 transition-all"
href="https://github.com/firecrawl/firecrawl"
target="_blank"
>
It&apos;s also open source.
</Link>
</p>
</div>
</div>
<div className="container lg:contents !p-16 relative -mt-90">
<div className="absolute top-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint lg:hidden" />
<div className="absolute bottom-0 left-[calc(50%-50vw)] w-screen h-1 bg-border-faint lg:hidden" />
<Connector className="-top-10 -left-[10.5px] lg:hidden" />
<Connector className="-top-10 -right-[10.5px] lg:hidden" />
<Connector className="-bottom-10 -left-[10.5px] lg:hidden" />
<Connector className="-bottom-10 -right-[10.5px] lg:hidden" />
<HeroInput />
</div>
<HeroScraping />
</section>
);
}
@@ -0,0 +1,46 @@
"use client";
import { Suspense, lazy, useState, useEffect } from "react";
const Pixi = lazy(() => import("@/components/shared/pixi/Pixi"));
import features from "./tickers/features";
function PixiContent() {
return (
<Pixi
canvasAttrs={{
className: "cw-[1314px] h-506 absolute top-100 lg-max:hidden",
}}
fps={Infinity}
initOptions={{ backgroundAlpha: 0 }}
smartStop={false}
tickers={[features]}
/>
);
}
export default function HomeHeroPixi() {
const [hasError, setHasError] = useState(false);
useEffect(() => {
const handleError = (e: ErrorEvent) => {
if (e.message.includes('pixi') || e.message.includes('ChunkLoadError')) {
setHasError(true);
}
};
window.addEventListener('error', handleError);
return () => window.removeEventListener('error', handleError);
}, []);
if (hasError) {
// Return empty div as fallback if Pixi fails to load
return <div className="cw-[1314px] h-506 absolute top-100 lg-max:hidden" />;
}
return (
<Suspense fallback={<div className="cw-[1314px] h-506 absolute top-100 lg-max:hidden" />}>
<PixiContent />
</Suspense>
);
}
@@ -0,0 +1,141 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import PixiAssetManager from "@/components/shared/pixi/PixiAssetManager";
import { RenderTexture, Sprite, Text } from "pixi.js";
// Add more contrast to the ASCII_CHARS and ensure 'X' is used
// const ASCII_CHARS = [' ', '.', ':', '-', '=', '+', 'X'];
const ASCII_CHARS = ' .":,-_^=+';
function getAsciiChar(luminance: number) {
if (luminance < 50) return " ";
const norm = Math.max(0, Math.min(1, (luminance - 16) / (250 - 16)));
const skewed = Math.pow(norm, 1.5);
const minIdx = 1;
const maxIdx = ASCII_CHARS.length - 1;
const idx = minIdx + Math.floor(skewed * (maxIdx - minIdx + 1));
const safeIdx = Math.max(minIdx, Math.min(maxIdx, idx));
return ASCII_CHARS[safeIdx];
}
// Sprinkle logic is now a no-op, as getAsciiChar handles the randomness
function sprinkleAscii(line: string) {
return line;
}
const tickAscii: Ticker = async ({ app, canvas }) => {
const textures = await Promise.all(
Array.from({ length: 150 }, async (_, i) => {
const texture = await PixiAssetManager.load(
`/Arşiv/FAQ Demo/FAQ_${i.toString().padStart(5, "0")}.png`,
);
return texture!;
}),
);
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const sprites = textures.map((texture) => new Sprite(texture));
sprites.forEach((sprite) => {
sprite.width = width;
sprite.height = height;
sprite.x = 0;
sprite.y = 0;
app.stage.addChild(sprite);
sprite.alpha = 0;
});
// Render the texture to a renderTexture to extract pixels
const renderTexture = RenderTexture.create({ width, height });
let ascii = "";
const asciiText = new Text({
text: ascii,
style: {
fontFamily: "monospace",
fontSize: 8,
fill: 0x000,
align: "left",
lineHeight: 8,
whiteSpace: "pre",
},
});
asciiText.alpha = 0.2;
asciiText.x = 0;
asciiText.y = 0;
const variants: string[] = [];
const render = (index: number) => {
ascii = "";
const sprite = sprites[index];
sprites.forEach((sprite) => {
sprite.alpha = 0;
});
sprite.alpha = 1;
app.renderer.render({ container: sprite, target: renderTexture });
sprite.alpha = 0;
const pixels = app.renderer.extract.pixels(renderTexture).pixels;
const charWidth = 4.81640625;
for (let y = 0; y < height; y += 8) {
let line = "";
for (let x = 0; x < width; x += charWidth) {
let totalLum = 0;
let count = 0;
for (let dy = 0; dy < 8; dy++) {
for (let dx = 0; dx < 4; dx++) {
const px = Math.floor(x + dx);
const py = Math.floor(y + dy);
if (px >= width || py >= height) continue;
const idx = (py * width + px) * 4;
const r = pixels[idx];
const g = pixels[idx + 1];
const b = pixels[idx + 2];
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
totalLum += lum;
count++;
}
}
const avgLum = count ? totalLum / count : 0;
line += getAsciiChar(avgLum);
}
ascii += sprinkleAscii(line) + "\n";
}
variants[index] = ascii;
asciiText.text = ascii;
};
app.stage.addChild(asciiText);
for (let i = 0; i < sprites.length; i++) {
render(i);
}
let i = 0;
//@ts-ignore
app.ticker.safeAdd(() => {
i++;
if (i >= sprites.length) i = 0;
render(i);
});
};
export default tickAscii;
@@ -0,0 +1,65 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import AnimatedRect from "./components/AnimatedRect";
import BlinkingContainer from "./components/BlinkingContainer";
import crawl from "./crawl";
import mapping from "./mapping";
import scrape from "./scrape";
import search from "./search";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
};
export const CELL_SIZE = 80;
export const MAIN_COLOR = 0xe6e6e6;
const animations = [scrape, mapping, search, crawl];
let lastActive = -1;
export default function cell(props: Props) {
const blinkingContainer = BlinkingContainer({
x: props.x + 10,
y: props.y + 10,
app: props.app,
});
const anchorGraphic = AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 4,
height: 4,
radius: 10,
color: MAIN_COLOR,
});
blinkingContainer.container.addChild(anchorGraphic.graphic);
props.app.stage.addChild(blinkingContainer.container);
let running = false;
return {
trigger: async () => {
if (running) return;
running = true;
lastActive = (lastActive + 1) % animations.length;
const fn = animations[lastActive];
await fn({
...props,
blinkingContainer,
anchorGraphic,
});
running = false;
},
};
}
@@ -0,0 +1,51 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import AnimatedRect from "./components/AnimatedRect";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
};
export default function cellReveal(props: Props) {
const graphic = AnimatedRect({
app: props.app,
x: props.x + 0.5,
y: props.y + 0.5,
width: 101,
height: 101,
radius: 0,
alpha: 0,
color: 0x000,
centering: false,
});
props.app.stage.addChild(graphic.graphic);
return {
trigger: async () => {
let cycleCount = 0;
const cycle = async () => {
await graphic.animate(
{
alpha: Math.random() * 0.04,
},
{
ease: "linear",
duration: 0.03,
},
);
if (cycleCount < 5) {
cycleCount += 1;
cycle();
} else {
await graphic.animate({ alpha: 0 });
graphic.graphic.destroy();
}
};
cycle();
},
};
}
@@ -0,0 +1,104 @@
//@ts-nocheck
import { AnimationOptions, cubicBezier } from "motion";
import { Application, Container, Graphics, Sprite } from "pixi.js";
import { isDestroyed } from "@/components/shared/pixi/utils";
type Props = {
app: Application;
x: number;
y: number;
width: number;
height: number;
radius: number;
color: number;
scale?: number;
rotation?: number;
type?: "rect" | "arc" | "container" | Sprite;
animationConfig?: AnimationOptions;
alpha?: number;
centering?: boolean;
};
export type IAnimatedRect = ReturnType<typeof AnimatedRect>;
export default function AnimatedRect(props: Props) {
const graphic = (() => {
if (props.type === "container") return new Container();
if (props.type instanceof Sprite) return props.type;
return new Graphics();
})();
props.alpha ??= 1;
props.scale ??= 1;
props.centering ??= true;
props.rotation ??= 0;
const p = {
...props,
};
const render = () => {
if (isDestroyed(props.app) || graphic.destroyed) return;
graphic.scale.set(p.scale!);
graphic.alpha = p.alpha!;
graphic.rotation = p.rotation!;
if (!(graphic instanceof Graphics)) {
if (graphic instanceof Sprite) {
graphic.x = p.x;
graphic.y = p.y;
}
return;
}
const g = graphic as Graphics;
g.clear();
if (p.type !== "arc") {
g.roundRect(
p.centering ? p.x - p.width / 2 : p.x,
p.centering ? p.y - p.height / 2 : p.y,
p.width,
p.height,
p.radius,
);
} else {
g.arc(p.x, p.y, p.width / 2, 0, Math.PI * 2);
}
g.fill({ color: p.color });
};
render();
p.animationConfig ??= {
duration: 0.4,
ease: cubicBezier(0.83, 0, 0.17, 1),
};
return {
defaultProps: props,
currentProps: p,
graphic,
setStyle: (style: Partial<Props>) => {
Object.assign(p, style);
render();
},
render,
animate: (renderProps: Partial<Props>, settings?: AnimationOptions) =>
props.app.animate(p, renderProps, {
...p.animationConfig,
...settings,
onUpdate: render,
}),
reset: () => props.app.animate(p, props, { onUpdate: render }),
};
}
@@ -0,0 +1,83 @@
//@ts-nocheck
import { Application, Graphics } from "pixi.js";
import { CELL_SIZE } from "@/components/app/(home)/sections/hero/Pixi/tickers/features/cell";
import AnimatedRect from "./AnimatedRect";
export type IBlinkingContainer = ReturnType<typeof BlinkingContainer>;
export default function BlinkingContainer({
x,
y,
app,
}: {
x: number;
y: number;
app: Application;
}) {
const animatedRect = AnimatedRect({
app,
x: 0,
y: 0,
width: CELL_SIZE,
height: CELL_SIZE,
radius: 0,
color: 0xededed,
type: "container",
});
animatedRect.graphic.pivot.set(CELL_SIZE / 2, CELL_SIZE / 2);
animatedRect.graphic.x = x + CELL_SIZE / 2;
animatedRect.graphic.y = y + CELL_SIZE / 2;
animatedRect.graphic.addChild(
new Graphics()
.rect(0, 0, CELL_SIZE, CELL_SIZE)
.fill({ color: "#EDEDED", alpha: 0 }),
);
const blinkLayer = new Graphics()
.rect(0, 0, CELL_SIZE, CELL_SIZE)
.fill({ color: "#F9F9F9" });
blinkLayer.zIndex = 1;
blinkLayer.alpha = 0;
animatedRect.graphic.addChild(blinkLayer);
return {
container: animatedRect.graphic,
animate: animatedRect.animate,
reset: animatedRect.reset,
shrink: async () => {
await animatedRect.animate({ scale: 0.92 });
animatedRect.animate({ scale: 1 });
},
blink: ({ delay = 0 }: { delay?: number } = {}) => {
app
.animate(0, 0.32, {
repeatType: "reverse",
repeat: 2,
delay,
duration: 0.065,
ease: "linear",
onUpdate: (value) => {
blinkLayer.alpha = value as number;
},
})
.then(() => {
app.animate(0.32, 0, {
duration: 0.065,
ease: "linear",
onUpdate: (value) => {
blinkLayer.alpha = value as number;
},
});
});
},
};
}
@@ -0,0 +1,19 @@
import { MAIN_COLOR } from "@/components/app/(home)/sections/hero/Pixi/tickers/features/cell";
import AnimatedRect from "./AnimatedRect";
export default function Dot(
props: Pick<
Parameters<typeof AnimatedRect>[0],
"x" | "y" | "app" | "animationConfig"
>,
) {
return AnimatedRect({
...props,
width: 2,
height: 2,
radius: 10,
color: MAIN_COLOR,
type: "arc",
});
}
@@ -0,0 +1,200 @@
import { animate } from "motion";
import { Ticker } from "@/components/shared/pixi/Pixi";
import { sleep } from "@/utils/sleep";
import { CELL_SIZE, MAIN_COLOR } from "./cell";
import AnimatedRect, { IAnimatedRect } from "./components/AnimatedRect";
import { IBlinkingContainer } from "./components/BlinkingContainer";
import Dot from "./components/Dot";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
blinkingContainer: IBlinkingContainer;
anchorGraphic: IAnimatedRect;
};
export default async function crawl(props: Props) {
const rects = Array.from({ length: 6 }, () => {
return AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 8,
height: 8,
radius: 0,
color: MAIN_COLOR,
});
});
const dots = Array.from({ length: 16 }, () => {
return Dot({
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
app: props.app,
});
});
dots.forEach((dot) =>
props.blinkingContainer.container.addChild(dot.graphic),
);
await sleep(500);
/* Step 1: Reveal the main square, reveal the corner dots */
await Promise.all(
[
dots[0].animate({ x: 30, y: 30 }, { delay: 0.2 }),
dots[1].animate({ x: CELL_SIZE - 30, y: 30 }, { delay: 0.2 }),
dots[2].animate({ x: 30, y: CELL_SIZE - 30 }, { delay: 0.2 }),
dots[3].animate({ x: CELL_SIZE - 30, y: CELL_SIZE - 30 }, { delay: 0.2 }),
props.anchorGraphic.animate({
radius: 0,
width: 12,
height: 12,
}),
].flat(),
);
rects.forEach((rect) =>
props.blinkingContainer.container.addChild(rect.graphic),
);
rects.unshift(props.anchorGraphic);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.3 });
await props.blinkingContainer.shrink();
let spriteOverlay: IAnimatedRect | null = null;
// Use fallback rectangle instead of trying to load missing image
spriteOverlay = AnimatedRect({
x: 13,
y: 39,
color: MAIN_COLOR,
width: 54,
height: 34,
app: props.app,
radius: 4,
centering: false,
});
spriteOverlay.graphic.zIndex = -1;
props.blinkingContainer.container.addChild(spriteOverlay.graphic);
await Promise.all(
[
spriteOverlay?.animate({ height: 23, y: 50 }),
rects[0].animate({ width: 16, height: 16, y: 34 }),
rects.slice(1, 4).map((rect) => rect.animate({ x: 24, y: 50 })),
rects.slice(4, 8).map((rect) => rect.animate({ x: 56, y: 50 })),
dots[0].animate({ x: 28, y: 22 }),
dots[1].animate({ x: 52, y: 22 }),
dots[2].animate({ x: 16, y: 58 }),
dots[3].animate({ x: 64, y: 58 }),
dots[4].animate({ x: 16, y: 42 }),
dots[5].animate({ x: 64, y: 42 }),
dots[6].animate({ x: 32, y: 58 }),
dots[7].animate({ x: 48, y: 58 }),
dots.slice(8, 12).map((dot) => dot.animate({ x: 24, y: 50 })),
dots.slice(12, 16).map((dot) => dot.animate({ x: 56, y: 50 })),
].flat().filter(Boolean),
);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.3 });
await props.blinkingContainer.shrink();
try {
await Promise.all(
[
spriteOverlay?.animate({ height: 8, y: 58 }),
rects[0].animate({ y: 28 }),
[1, 4].map((i) => rects[i].animate({ y: 44 })),
[2, 3].map((i) => rects[i].animate({ x: 12, y: 56 })),
[5, 6].map((i) => rects[i].animate({ x: 68, y: 56 })),
dots[0].animate({ y: 16 }),
dots[1].animate({ y: 16 }),
dots[2].animate({ x: 4, y: 64 }),
dots[3].animate({ x: 76, y: 64 }),
dots[4].animate({ x: 4, y: 48 }),
dots[5].animate({ x: 76, y: 48 }),
dots[6].animate({ x: 20, y: 64 }),
dots[7].animate({ x: 60, y: 64 }),
dots[8].animate({ x: 16, y: 36 }),
dots[12].animate({ x: 64, y: 36 }),
dots[9].animate({ x: 32, y: 52 }),
dots[13].animate({ x: 48, y: 52 }),
[10, 11].map((i) => dots[i].animate({ x: 12, y: 56 })),
[14, 15].map((i) => dots[i].animate({ x: 68, y: 56 })),
].flat().filter(Boolean),
);
} catch (e) {
console.error(e);
}
await sleep(500);
props.blinkingContainer.blink({ delay: 0.3 });
await props.blinkingContainer.shrink();
await Promise.all(
[
spriteOverlay.animate({ height: 0, y: 66 }),
rects[0].animate({ y: 20 }),
[1, 4].map((i) => rects[i].animate({ y: 36 })),
[2, 5].map((i) => rects[i].animate({ y: 48 })),
[3, 6].map((i) => rects[i].animate({ y: 60, x: i === 3 ? 24 : 56 })),
[0, 1, 4, 5, 8, 9, 12, 13].map((i) =>
dots[i].animate({ y: dots[i].currentProps.y - 8 }),
),
dots[2].animate({ x: 4, y: 56 }),
dots[3].animate({ x: 76, y: 56 }),
dots[6].animate({ x: 32, y: 68 }),
dots[7].animate({ x: 48, y: 68 }),
dots[10].animate({ x: 32, y: 52 }),
dots[11].animate({ x: 16, y: 68 }),
dots[14].animate({ x: 48, y: 52 }),
dots[15].animate({ x: 64, y: 68 }),
].flat(),
);
await sleep(2000);
await Promise.all(
[
rects.map((rect) =>
rect.animate(props.anchorGraphic.defaultProps, {
delay: Math.random() * 0.3,
duration: 0.3,
}),
),
dots.map((dot) =>
dot.animate(dot.defaultProps, { delay: Math.random() * 0.3 }),
),
].flat(),
);
rects.shift();
rects.forEach((rect) => rect.graphic.destroy());
dots.forEach((dot) => dot.graphic.destroy());
spriteOverlay.graphic.destroy();
}
@@ -0,0 +1,124 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import setTimeoutOnVisible from "@/utils/set-timeout-on-visible";
import cell from "./cell";
import cellReveal from "./cellReveal";
const CELL_GRID = [
"-ooooooooooo-",
"-oo-------oo-",
"ooo-------ooo",
"-oo-------oo-",
"-oo-------oo-",
];
const REVEAL_ANIMATION_GRID = [
[
"---ooooooo---",
"--o-------o--",
"--o-------o--",
"--o-------o--",
"--o-------o--",
],
[
"--o-------o--",
"-o---------o-",
"-o---------o-",
"-o---------o-",
"-o---------o-",
],
[
"-o---------o-",
"-------------",
"o-----------o",
"-------------",
"-------------",
],
[
"-------------",
"-------------",
"o-----------o",
"-------------",
"-------------",
],
];
const features: Ticker = (params) => {
const cells: ReturnType<typeof cell>[] = [];
const cellReveals: {
cell: ReturnType<typeof cellReveal>;
row: number;
column: number;
}[] = [];
for (let i = 0; i < CELL_GRID.length; i++) {
const row = CELL_GRID[i];
for (let j = 0; j < row.length; j++) {
if (row[j] === "o") {
cells.push(
cell({
...params,
x: j * 101,
y: i * 101,
}),
);
cellReveals.push({
cell: cellReveal({
...params,
x: j * 101,
y: i * 101,
}),
row: i,
column: j,
});
}
}
}
const cycle = () =>
setTimeoutOnVisible({
element: params.canvas,
callback: () => {
const cell = cells[Math.floor(Math.random() * cells.length)];
if (cell) {
cell.trigger().then(() => cycle());
}
},
timeout: 3000 * Math.random(),
});
for (let i = 0; i < 5; i++) {
cycle();
}
let revealIndex = -1;
const revealCycle = () => {
revealIndex += 1;
for (let i = 0; i < REVEAL_ANIMATION_GRID[revealIndex].length; i++) {
const row = REVEAL_ANIMATION_GRID[revealIndex][i];
for (let j = 0; j < row.length; j++) {
if (row[j] === "o") {
cellReveals
.find((cell) => cell.row === i && cell.column === j)
?.cell.trigger();
}
}
}
if (revealIndex < REVEAL_ANIMATION_GRID.length - 1) {
setTimeout(() => {
revealCycle();
}, 150);
}
};
revealCycle();
};
export default features;
@@ -0,0 +1,207 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import { sleep } from "@/utils/sleep";
import { CELL_SIZE, MAIN_COLOR } from "./cell";
import AnimatedRect, { IAnimatedRect } from "./components/AnimatedRect";
import { IBlinkingContainer } from "./components/BlinkingContainer";
import Dot from "./components/Dot";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
blinkingContainer: IBlinkingContainer;
anchorGraphic: IAnimatedRect;
};
export default async function mapping(props: Props) {
const rects = Array.from({ length: 8 }, () => {
return AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 10,
height: 10,
radius: 0,
color: MAIN_COLOR,
});
});
const dots = Array.from({ length: 20 }, () => {
return Dot({
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
app: props.app,
});
});
dots.forEach((dot) =>
props.blinkingContainer.container.addChild(dot.graphic),
);
await sleep(500);
await props.anchorGraphic.animate({
radius: 0,
width: 12,
height: 12,
});
rects.forEach((rect) =>
props.blinkingContainer.container.addChild(rect.graphic),
);
rects.unshift(props.anchorGraphic);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
await Promise.all(
[
dots.slice(0, 16).map((dot, index) => {
const x = 13 + (index % 4) * 18;
const y = 13 + Math.floor(index / 4) * 18;
return dot.animate({ x, y });
}),
rects[0].animate({ width: 10, height: 10 }),
rects.map((rect, index) => {
const x = 22 + (index % 3) * 18;
const y = 22 + Math.floor(index / 3) * 18;
return rect.animate({ x, y });
}),
].flat(),
);
await sleep(300);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
const baseDotPositions = [
[13, 13],
[31, 31],
[49, 31],
[13, 31],
[49, 49],
[67, 49],
[13, 49],
[31, 67],
[67, 67],
];
const dotPositions: string[] = [];
for (const [x, y] of baseDotPositions) {
const positions = [
{ x: x - 9, y: y - 9 },
{ x: x + 9, y: y - 9 },
{ x: x - 9, y: y + 9 },
{ x: x + 9, y: y + 9 },
];
for (const position of positions) {
if (!dotPositions.includes(`${position.x},${position.y}`)) {
dotPositions.push(`${position.x},${position.y}`);
}
}
}
await Promise.all(
[
rects[0].animate({ x: 13, y: 13 }),
rects[1].animate({ x: 31, y: 31 }),
rects[2].animate({ x: 49, y: 31 }),
rects[3].animate({ x: 13, y: 31 }),
rects[4].animate({ x: 49, y: 49 }),
rects[5].animate({ x: 67, y: 49 }),
rects[6].animate({ x: 13, y: 49 }),
rects[7].animate({ x: 31, y: 67 }),
rects[8].animate({ x: 67, y: 67 }),
dots.map((dot, index) => {
const position = dotPositions[index].split(",").map(Number);
return dot.animate({ x: position[0], y: position[1] });
}),
].flat(),
);
await sleep(500);
const lines = Array.from({ length: 8 }, () => {
return AnimatedRect({
app: props.app,
x: 0,
y: 0,
width: 0,
height: 0,
radius: 0,
color: MAIN_COLOR,
centering: false,
animationConfig: {
duration: 0.25,
ease: "linear",
},
});
});
lines.forEach((graphic) =>
props.blinkingContainer.container.addChild(graphic.graphic),
);
(async () => {
lines[0].setStyle({ width: 1, height: 0, y: 18, x: 12.5 });
await lines[0].animate({ height: 9 });
lines[1].setStyle({ width: 0, height: 1, y: 30.5, x: 18 });
await lines[1].animate({ width: 9 });
lines[2].setStyle({ width: 0, height: 1, y: 30.5, x: 36 });
await lines[2].animate({ width: 9 });
lines[3].setStyle({ width: 1, height: 3, y: 36, x: 48.5 });
await lines[3].animate({ height: 9 });
lines[4].setStyle({ width: 0, height: 1, y: 48.5, x: 54 });
await lines[4].animate({ width: 9 });
})();
lines[5].setStyle({ width: 0, height: 1, y: 66.5, x: 62 });
await lines[5].animate({ width: 28, x: 62 - 28 }, { duration: 0.4 });
lines[6].setStyle({ width: 0, height: 1, y: 66.5, x: 26 });
await lines[6].animate({ width: 13.5, x: 26 - 13.5 });
lines[7].setStyle({ width: 1, height: 0, y: 66.5, x: 12.5 });
await lines[7].animate({ height: 14.5, y: 66.5 - 13.5 });
await sleep(2000);
props.blinkingContainer.blink({ delay: 0.1 });
await Promise.all(
[
lines.map((line) => line.animate({ alpha: 0 })),
rects.map((rect) =>
rect.animate(props.anchorGraphic.defaultProps, {
delay: Math.random() * 0.3,
duration: 0.3,
}),
),
dots.map((dot) =>
dot.animate(dot.defaultProps, { delay: Math.random() * 0.3 }),
),
].flat(),
);
rects.shift();
lines.forEach((line) => line.graphic.destroy());
rects.forEach((rect) => rect.graphic.destroy());
dots.forEach((dot) => dot.graphic.destroy());
}
@@ -0,0 +1,219 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import { sleep } from "@/utils/sleep";
import { CELL_SIZE, MAIN_COLOR } from "./cell";
import AnimatedRect, { IAnimatedRect } from "./components/AnimatedRect";
import { IBlinkingContainer } from "./components/BlinkingContainer";
import Dot from "./components/Dot";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
blinkingContainer: IBlinkingContainer;
anchorGraphic: IAnimatedRect;
};
export default async function scrape(props: Props) {
const rects = Array.from({ length: 15 }, () => {
return AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 10,
height: 10,
radius: 0,
color: MAIN_COLOR,
});
});
const dots = Array.from({ length: 25 }, () => {
return Dot({
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
app: props.app,
});
});
dots.forEach((dot) =>
props.blinkingContainer.container.addChild(dot.graphic),
);
await sleep(500);
await Promise.all(
[
[0, 12, 13, 14].map((index) =>
dots[index].animate({ x: 30, y: 30 }, { delay: 0.2 }),
),
[1, 15, 16, 17].map((index) =>
dots[index].animate({ x: CELL_SIZE - 30, y: 30 }, { delay: 0.2 }),
),
[2, 18, 19, 20].map((index) =>
dots[index].animate({ x: 30, y: CELL_SIZE - 30 }, { delay: 0.2 }),
),
[3, 21, 22, 23].map((index) =>
dots[index].animate(
{ x: CELL_SIZE - 30, y: CELL_SIZE - 30 },
{ delay: 0.2 },
),
),
props.anchorGraphic.animate({
radius: 0,
width: 12,
height: 12,
}),
].flat(),
);
rects.forEach((rect) =>
props.blinkingContainer.container.addChild(rect.graphic),
);
rects.unshift(props.anchorGraphic);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
await Promise.all(
[
[0, 12, 13, 14].map((index) => dots[index].animate({ x: 22, y: 22 })),
[1, 15, 16, 17].map((index) =>
dots[index].animate({ x: CELL_SIZE - 22, y: 22 }),
),
[2, 18, 19, 20].map((index) =>
dots[index].animate({ x: 22, y: CELL_SIZE - 22 }),
),
[3, 21, 22, 23].map((index) =>
dots[index].animate({ x: CELL_SIZE - 22, y: CELL_SIZE - 22 }),
),
dots[4].animate({ x: 40, y: 22 }),
dots[5].animate({ x: 22, y: 40 }),
dots[6].animate({ x: CELL_SIZE - 22, y: 40 }),
dots[7].animate({ x: 40, y: 58 }),
dots[8].animate({ x: 40, y: 22 }),
dots[9].animate({ x: 22, y: 40 }),
dots[10].animate({ x: CELL_SIZE - 22, y: 40 }),
dots[11].animate({ x: 40, y: 58 }),
rects[0].animate({ width: 10, height: 10 }),
rects.slice(0, 4).map((rect) => rect.animate({ x: 31, y: 31 })),
rects
.slice(4, 8)
.map((rect) => rect.animate({ x: CELL_SIZE - 31, y: 31 })),
rects
.slice(8, 12)
.map((rect) => rect.animate({ x: 31, y: CELL_SIZE - 31 })),
rects
.slice(12, 16)
.map((rect) => rect.animate({ x: CELL_SIZE - 31, y: CELL_SIZE - 31 })),
].flat(),
);
await sleep(1000);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
await Promise.all(
[
dots[0].animate({ x: 4, y: 4 }),
dots[1].animate({ x: CELL_SIZE - 4, y: 4 }),
dots[2].animate({ x: 4, y: CELL_SIZE - 4 }),
dots[3].animate({ x: CELL_SIZE - 4, y: CELL_SIZE - 4 }),
dots[4].animate({ x: 40, y: 4 }),
dots[5].animate({ x: 4, y: 40 }),
dots[6].animate({ x: 76, y: 40 }),
dots[7].animate({ x: 40, y: 76 }),
dots[13].animate({ x: 22, y: 4 }),
dots[14].animate({ x: 4, y: 22 }),
dots[16].animate({ x: 58, y: 4 }),
dots[17].animate({ x: 76, y: 22 }),
dots[19].animate({ x: 4, y: 58 }),
dots[20].animate({ x: 22, y: 76 }),
dots[22].animate({ x: 58, y: 76 }),
dots[23].animate({ x: 76, y: 58 }),
rects.map((rect, index) => {
const quadrant = Math.floor(index / 4);
const position = index % 4;
const col = (position % 2 === 0 ? 1 : 2) + (quadrant % 2 === 0 ? 0 : 2);
const row = Math.floor(position / 2) + (quadrant < 2 ? 1 : 3);
return rect.animate({
x: 13 + (col - 1) * 18,
y: 13 + (row - 1) * 18,
});
}),
].flat(),
);
await sleep(1200);
Promise.all(
dots.map((dot) =>
dot.animate({ alpha: 0 }, { delay: Math.random() * 0.3 }),
),
);
await sleep(100);
props.blinkingContainer.blink({ delay: 0.2 });
const newWidths: number[] = [];
for (let i = 0; i < rects.length; i++) {
if (i % 2 === 0) {
newWidths.push(20 + Math.random() * 28);
} else {
const remainingSpace = 62 - newWidths[i - 1];
newWidths.push(10 + Math.random() * remainingSpace);
}
}
await Promise.all([
rects.map((rect, index) => {
const y = 8 + Math.floor(index / 2) * 6 + Math.floor(index / 4) * 8;
return rect.animate(
{
y,
x:
(index % 2 === 0 ? 8 : newWidths[index - 1] + 10) +
newWidths[index] / 2,
height: 4,
width: newWidths[index],
},
{
delay: Math.random() * 0.1,
},
);
}),
]);
props.blinkingContainer.blink({ delay: 0.1 });
await sleep(2000);
await Promise.all(
[
rects.map((rect) =>
rect.animate(props.anchorGraphic.defaultProps, {
delay: Math.random() * 0.3,
duration: 0.3,
}),
),
].flat(),
);
rects.shift();
rects.forEach((rect) => rect.graphic.destroy());
dots.forEach((dot) => dot.graphic.destroy());
}
@@ -0,0 +1,144 @@
import { Ticker } from "@/components/shared/pixi/Pixi";
import { sleep } from "@/utils/sleep";
import { CELL_SIZE, MAIN_COLOR } from "./cell";
import AnimatedRect, { IAnimatedRect } from "./components/AnimatedRect";
import { IBlinkingContainer } from "./components/BlinkingContainer";
import Dot from "./components/Dot";
type Props = Parameters<Ticker>[0] & {
x: number;
y: number;
blinkingContainer: IBlinkingContainer;
anchorGraphic: IAnimatedRect;
};
export default async function search(props: Props) {
const rects = Array.from({ length: 8 }, () => {
return AnimatedRect({
app: props.app,
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
width: 10,
height: 10,
radius: 0,
color: MAIN_COLOR,
});
});
const dots = Array.from({ length: 16 }, () => {
return Dot({
x: CELL_SIZE / 2,
y: CELL_SIZE / 2,
app: props.app,
});
});
dots.forEach((dot) =>
props.blinkingContainer.container.addChild(dot.graphic),
);
await sleep(500);
await props.anchorGraphic.animate({
radius: 0,
width: 12,
height: 12,
});
rects.forEach((rect) =>
props.blinkingContainer.container.addChild(rect.graphic),
);
rects.unshift(props.anchorGraphic);
await sleep(500);
props.blinkingContainer.blink({ delay: 0.1 });
await props.blinkingContainer.shrink();
await Promise.all(
[
dots.map((dot, index) => {
const x = 13 + (index % 4) * 18;
const y = 13 + Math.floor(index / 4) * 18;
return dot.animate({ x, y });
}),
rects[0].animate({ width: 10, height: 10 }),
rects.map((rect, index) => {
const x = 22 + (index % 3) * 18;
const y = 22 + Math.floor(index / 3) * 18;
return rect.animate({ x, y });
}),
].flat(),
);
await sleep(300);
Promise.all(
[
rects.map((rect) => rect.animate({ alpha: 0.68 })),
dots.map((dot) => dot.animate({ alpha: 0.68 })),
].flat(),
);
props.blinkingContainer.blink();
await sleep(400);
for await (const rect of rects) {
// Get the surrounding dots of this rect
const rectX = rect.currentProps.x;
const rectY = rect.currentProps.y;
const surroundingDots = dots.filter((dot) => {
const dx = Math.abs(dot.currentProps.x - rectX);
const dy = Math.abs(dot.currentProps.y - rectY);
// Consider "surrounding" as adjacent horizontally, vertically, or diagonally (distance 18)
return (
(dx === 0 && dy === 9) ||
(dx === 9 && dy === 0) ||
(dx === 9 && dy === 9)
);
});
await Promise.all(
[
surroundingDots.map((dot) =>
dot.animate({ alpha: 1 }, { duration: 0.75 }),
),
rect.animate({ alpha: 1, width: 14, height: 14 }, { duration: 0.75 }),
].flat(),
);
rect.animate({ alpha: 0.68, width: 10, height: 10 }, { duration: 0.75 });
Promise.all(
surroundingDots.map((dot) =>
dot.animate({ alpha: 0.68 }, { duration: 0.75 }),
),
);
}
await Promise.all(
[
rects.map((rect) =>
rect.animate(props.anchorGraphic.defaultProps, {
delay: Math.random() * 0.3,
duration: 0.3,
}),
),
dots.map((dot) =>
dot.animate(dot.defaultProps, { delay: Math.random() * 0.3 }),
),
].flat(),
);
rects.shift();
rects.forEach((rect) => rect.graphic.destroy());
dots.forEach((dot) => dot.graphic.destroy());
}
@@ -0,0 +1,263 @@
"use client";
// import dynamic from "next/dynamic";
// import { useRef, useEffect, forwardRef } from "react";
// const originalText =
// "";
type Options = {
randomizeChance?: number;
reversed?: boolean;
};
export const encryptText = (
text: string,
progress: number,
_options?: Options,
) => {
const options = {
randomizeChance: 0.7,
..._options,
};
const encryptionChars = "a-zA-Z0-9*=?!";
const skipTags = ["<br class='lg-max:hidden'>", "<span>", "</span>"];
// Calculate how many characters should be encrypted
const totalChars = text.length;
const encryptedCount = Math.floor(totalChars * (1 - progress));
let result = "";
let charIndex = 1;
for (let i = 0; i < text.length; i++) {
const char = text[i];
// Check if we're at the start of a tag to skip
let shouldSkip = false;
for (const tag of skipTags) {
if (text.substring(i, i + tag.length) === tag) {
result += tag;
i += tag.length - 1; // -1 because loop will increment
shouldSkip = true;
break;
}
}
if (shouldSkip) continue;
// Skip spaces - keep them as is
if (char === " ") {
result += char;
charIndex++;
continue;
}
// If this character should be encrypted
if (
options.reversed
? charIndex < encryptedCount
: text.length - charIndex < encryptedCount
) {
// 40% chance to show original character, 60% chance to encrypt
if (Math.random() < options.randomizeChance) {
result += char;
} else {
// Use random character from encryption set
const randomIndex = Math.floor(Math.random() * encryptionChars.length);
result += encryptionChars[randomIndex];
}
} else {
// Keep original character
result += char;
}
charIndex++;
}
return result;
};
// const Wrapper = forwardRef<
// HTMLDivElement,
// React.HTMLAttributes<HTMLDivElement>
// >((props, ref) => {
// return (
// <div className="text-title-h1 mx-auto text-center [&_span]:text-heat-100 mb-12 lg:mb-16">
// <div {...props} className="hidden lg:contents" ref={ref} />
// <div
// className="lg:hidden contents"
// dangerouslySetInnerHTML={{ __html: originalText }}
// />
// </div>
// );
// });
// Wrapper.displayName = "Wrapper";
// export default dynamic(() => Promise.resolve(HomeHeroTitle), {
// ssr: false,
// loading: () => (
// <Wrapper
// dangerouslySetInnerHTML={{ __html: encryptText(originalText, 0) }}
// />
// ),
// });
// function HomeHeroTitle() {
// const textRef = useRef<HTMLDivElement>(null);
// useEffect(() => {
// if (window.innerWidth < 996) {
// return;
// }
// let progress = 0;
// let increaseProgress = -10;
// const animate = () => {
// increaseProgress = (increaseProgress + 1) % 5;
// if (increaseProgress === 4) {
// progress += 0.3;
// }
// if (progress > 1) {
// progress = 1;
// textRef.current!.innerHTML = encryptText(originalText, progress);
// return;
// }
// textRef.current!.innerHTML = encryptText(originalText, progress);
// const interval = 50 + progress * 20;
// setTimeout(animate, interval);
// };
// animate();
// }, []);
// return (
// <Wrapper
// dangerouslySetInnerHTML={{ __html: encryptText(originalText, 0) }}
// ref={textRef}
// />
// );
// }
// import dynamic from "next/dynamic";
// import { useRef, useEffect, forwardRef } from "react";
// const originalText =
// "Turn websites into <br class='lg-max:hidden'><span>LLM-ready</span> data";
// type Options = {
// randomizeChance?: number;
// reversed?: boolean;
// };
// export const encryptText = (
// text: string,
// progress: number,
// _options?: Options,
// ) => {
// const options = {
// randomizeChance: 0.7,
// ..._options,
// };
// const encryptionChars = "a-zA-Z0-9*=?!";
// const skipTags = ["<br class='lg-max:hidden'>", "<span>", "</span>"];
// // Calculate how many characters should be encrypted
// const totalChars = text.length;
// const encryptedCount = Math.floor(totalChars * (1 - progress));
// let result = "";
// let charIndex = 1;
// for (let i = 0; i < text.length; i++) {
// const char = text[i];
// // Check if we're at the start of a tag to skip
// let shouldSkip = false;
// for (const tag of skipTags) {
// if (text.substring(i, i + tag.length) === tag) {
// result += tag;
// i += tag.length - 1; // -1 because loop will increment
// shouldSkip = true;
// break;
// }
// }
// if (shouldSkip) continue;
// // Skip spaces - keep them as is
// if (char === " ") {
// result += char;
// charIndex++;
// continue;
// }
// // If this character should be encrypted
// if (
// options.reversed
// ? charIndex < encryptedCount
// : text.length - charIndex < encryptedCount
// ) {
// // 40% chance to show original character, 60% chance to encrypt
// if (Math.random() < options.randomizeChance) {
// result += char;
// } else {
// // Use random character from encryption set
// const randomIndex = Math.floor(Math.random() * encryptionChars.length);
// result += encryptionChars[randomIndex];
// }
// } else {
// // Keep original character
// result += char;
// }
// charIndex++;
// }
// return result;
// };
// const Wrapper = forwardRef<
// HTMLDivElement,
// React.HTMLAttributes<HTMLDivElement>
// >((props, ref) => {
// return (
// <div className="text-title-h1 mx-auto text-center [&_span]:text-heat-100 mb-12 lg:mb-16">
// <div {...props} className="hidden lg:contents" ref={ref} />
// <div
// className="lg:hidden contents"
// dangerouslySetInnerHTML={{ __html: originalText }}
// />
// </div>
// );
// });
// Wrapper.displayName = "Wrapper";
// export default dynamic(() => Promise.resolve(HomeHeroTitle), {
// ssr: false,
// loading: () => (
// <Wrapper
// dangerouslySetInnerHTML={{ __html: encryptText(originalText, 0) }}
// />
// ),
// });
export default function HomeHeroTitle() {
return (
<h1 className="text-title-h1 mx-auto text-center [&_span]:text-heat-100 mb-12 lg:mb-16">
Open Lovable <span>v2</span>
</h1>
);
}
@@ -0,0 +1,54 @@
# Home Page Components Rules
When working with home/landing page components in components-new/app/(home):
## Structure
```
home/
├── sections/ # Major page sections
│ ├── hero/ # Hero section with flames
│ ├── features/ # Feature showcase
│ ├── testimonials/# Customer testimonials
│ ├── pricing/ # Pricing cards
│ └── faq/ # FAQ section
├── navbar/ # Landing page navbar
└── footer/ # Landing page footer
```
## Migration Notes
These components will be migrated from `marketing/` when beginning home page migration after Dashboard v2.
### Priority Sections to Migrate:
1. **Hero** - Main landing with HeroFlame effect
2. **Features** - Feature grid with animations
3. **Testimonials** - Social proof section
4. **Pricing** - Pricing tiers with heat buttons
5. **FAQ** - Collapsible FAQ items
### Usage Pattern:
```tsx
// app/page.tsx (future)
import { Hero } from '@/components/home/sections/hero';
import { Features } from '@/components/home/sections/features';
import { Testimonials } from '@/components/home/sections/testimonials';
import { Pricing } from '@/components/home/sections/pricing';
import { FAQ } from '@/components/home/sections/faq';
export default function HomePage() {
return (
<>
<Hero />
<Features />
<Testimonials />
<Pricing />
<FAQ />
</>
);
}
```
## Design Principles
- **Fire theme**: Subtle flame effects in hero
- **Performance**: Lazy load below-fold sections
- **Responsive**: Mobile-first approach
- **Animations**: Intersection observer for scroll effects
+163
View File
@@ -0,0 +1,163 @@
"use client";
import { useState } from "react";
import Globe from "@/components/app/(home)/sections/hero-input/_svg/Globe";
import HeroInputSubmitButton from "@/components/app/(home)/sections/hero-input/Button/Button";
interface SidebarInputProps {
onSubmit: (url: string, style: string, model: string, instructions?: string) => void;
disabled?: boolean;
}
export default function SidebarInput({ onSubmit, disabled = false }: SidebarInputProps) {
const [url, setUrl] = useState<string>("");
const [selectedStyle, setSelectedStyle] = useState<string>("1");
const [selectedModel, setSelectedModel] = useState<string>("moonshotai/kimi-k2-instruct-0905");
const [additionalInstructions, setAdditionalInstructions] = useState<string>("");
const [isValidUrl, setIsValidUrl] = useState<boolean>(false);
// Simple URL validation
const validateUrl = (urlString: string) => {
if (!urlString) return false;
const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
return urlPattern.test(urlString.toLowerCase());
};
const styles = [
{ id: "1", name: "Glassmorphism", description: "Frosted glass effect" },
{ id: "2", name: "Neumorphism", description: "Soft 3D shadows" },
{ id: "3", name: "Brutalism", description: "Bold and raw" },
{ id: "4", name: "Minimalist", description: "Clean and simple" },
{ id: "5", name: "Dark Mode", description: "Dark theme design" },
{ id: "6", name: "Gradient Rich", description: "Vibrant gradients" },
{ id: "7", name: "3D Depth", description: "Dimensional layers" },
{ id: "8", name: "Retro Wave", description: "80s inspired" },
];
const models = [
{ id: "moonshotai/kimi-k2-instruct-0905", name: "Kimi K2 0905 on Groq" },
{ id: "openai/gpt-5", name: "GPT-5" },
{ id: "anthropic/claude-sonnet-4-20250514", name: "Sonnet 4" },
{ id: "google/gemini-2.0-flash-exp", name: "Gemini 2.0" },
];
const handleSubmit = (e?: React.FormEvent) => {
if (e) e.preventDefault();
if (!url.trim() || disabled) return;
onSubmit(url.trim(), selectedStyle, selectedModel, additionalInstructions || undefined);
// Reset form
setUrl("");
setAdditionalInstructions("");
setIsValidUrl(false);
};
return (
<div className="w-full">
<div className="bg-white rounded-20 border border-gray-200 shadow-sm">
<div className="p-4 border-b border-gray-100">
<h3 className="text-sm font-medium text-gray-900 mb-3">Generate New Website</h3>
{/* URL Input */}
<div className="flex gap-3 items-center mb-3">
<Globe />
<input
className="flex-1 bg-transparent text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none"
placeholder="example.com"
type="text"
value={url}
disabled={disabled}
onChange={(e) => {
setUrl(e.target.value);
setIsValidUrl(validateUrl(e.target.value));
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleSubmit();
}
}}
/>
</div>
</div>
{/* Options Section - Show when valid URL */}
{isValidUrl && (
<div className="p-4 space-y-4">
{/* Style Selector */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-2">Style</label>
<div className="grid grid-cols-2 gap-1.5">
{styles.map((style) => (
<button
key={style.id}
onClick={() => setSelectedStyle(style.id)}
disabled={disabled}
className={`
py-2 px-2 rounded text-xs font-medium border transition-all text-center
${selectedStyle === style.id
? 'border-orange-500 bg-orange-50 text-orange-900'
: 'border-gray-200 hover:border-gray-300 bg-white text-gray-700'
}
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
`}
>
{style.name}
</button>
))}
</div>
</div>
{/* Model Selector */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-2">AI Model</label>
<select
value={selectedModel}
onChange={(e) => setSelectedModel(e.target.value)}
disabled={disabled}
className="w-full px-3 py-2 text-xs font-medium text-gray-700 bg-white rounded border border-gray-200 focus:border-orange-500 focus:outline-none focus:ring-1 focus:ring-orange-500"
>
{models.map((model) => (
<option key={model.id} value={model.id}>
{model.name}
</option>
))}
</select>
</div>
{/* Additional Instructions */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-2">Additional Instructions (optional)</label>
<input
type="text"
value={additionalInstructions}
onChange={(e) => setAdditionalInstructions(e.target.value)}
disabled={disabled}
className="w-full px-3 py-2 text-xs text-gray-700 bg-gray-50 rounded border border-gray-200 focus:border-orange-500 focus:outline-none focus:ring-1 focus:ring-orange-500 placeholder:text-gray-400"
placeholder="e.g., make it more colorful, add animations..."
/>
</div>
{/* Submit Button */}
<div className="pt-2">
<button
onClick={handleSubmit}
disabled={!isValidUrl || disabled}
className={`
w-full py-2.5 px-4 rounded-lg text-sm font-medium transition-all
${isValidUrl && !disabled
? 'bg-orange-500 hover:bg-orange-600 text-white'
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
}
`}
>
{disabled ? 'Generating...' : 'Generate Website'}
</button>
</div>
</div>
)}
</div>
</div>
);
}
@@ -0,0 +1,47 @@
"use client";
import { useState } from "react";
import HeroInputSubmitButton from "@/components/app/(home)/sections/hero-input/Button/Button";
interface SidebarQuickInputProps {
onSubmit: (url: string) => void;
disabled?: boolean;
}
export default function SidebarQuickInput({ onSubmit, disabled = false }: SidebarQuickInputProps) {
const [url, setUrl] = useState<string>("");
const handleSubmit = (e?: React.FormEvent) => {
if (e) e.preventDefault();
if (!url.trim() || disabled) return;
onSubmit(url.trim());
setUrl("");
};
return (
<div className="w-full">
<div className="bg-gray-50 rounded-lg border border-gray-200">
<div className="p-3 flex items-center gap-3">
<input
className="flex-1 bg-transparent text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none"
placeholder="Enter a new URL to regenerate..."
type="text"
value={url}
disabled={disabled}
onChange={(e) => setUrl(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleSubmit();
}
}}
/>
<div onClick={handleSubmit}>
<HeroInputSubmitButton dirty={url.length > 0} />
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,48 @@
export enum Endpoint {
Scrape = "scrape",
Crawl = "crawl",
Search = "search",
Map = "map",
Extract = "extract",
}
export enum AgentModel {
FIRE_1 = "FIRE-1",
}
export enum FormatType {
Markdown = "markdown",
Summary = "summary",
Json = "json",
RawHtml = "rawHtml",
Html = "html",
Screenshot = "screenshot",
ScreenshotFullPage = "screenshot@fullPage",
Links = "links",
}
export enum SearchFormatType {
Web = "web",
Images = "images",
News = "news",
}
type Prev = [never, 0, 1, 2, 3, 4, 5];
type Join<K, P> = K extends string | number
? P extends string | number
? `${K}.${P}`
: never
: never;
export type Paths<T, D extends number = 5> = [D] extends [never]
? never
: T extends object
? {
[K in keyof T]-?: K extends string | number
? T[K] extends object
? K | Join<K, Paths<T[K], Prev[D]>>
: K
: never;
}[keyof T]
: "";
+394
View File
@@ -0,0 +1,394 @@
"use client";
import { animate } from "framer-motion";
import { useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
interface AnimatedDotIconProps {
active?: boolean;
alwaysHeat?: boolean;
triggerOnHover?: boolean;
size?: number;
className?: string;
pattern?:
| "usage"
| "api-keys"
| "settings"
| "overview"
| "team"
| "billing"
| "account-settings"
| "admin"
| "domain-checker"
| "extract-playground"
| "extract"
| "logs"
| "playground"
| "teams";
}
const initCanvas = (canvas: HTMLCanvasElement) => {
const { width, height } = canvas.getBoundingClientRect();
const ctx = canvas.getContext("2d")!;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const upscaleCanvas = () => {
const scale = window.visualViewport?.scale || 1;
const dpr = (window.devicePixelRatio || 1) * scale;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
canvas.dispatchEvent(new Event("resize"));
};
upscaleCanvas();
const handleResize = () => {
setTimeout(upscaleCanvas, 500);
};
window.addEventListener("resize", handleResize);
window.visualViewport?.addEventListener("resize", handleResize);
return ctx;
};
// Pattern definitions for different pages
const patterns = {
usage: {
grid: [
[10, 11, 12, 14, 15, 16],
[3, 7, 19, 23],
[0, 2, 24, 26],
[27, 28, 29, 31, 32, 33],
],
gridSize: 7,
cellSize: 2,
spacing: 2,
offset: 3,
},
"api-keys": {
grid: [[12], [10, 14], [8, 16], [6, 18], [4, 5, 19, 20]],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
settings: {
grid: [
[0, 1, 2, 3, 4],
[5, 9],
[10, 14],
[15, 19],
[20, 21, 22, 23, 24],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
overview: {
grid: [
[24],
[16, 18, 30, 32],
[8, 12, 36, 40],
[0, 3, 6, 21, 27, 42, 45, 48],
],
gridSize: 7,
cellSize: 2,
spacing: 2,
offset: 3,
},
team: {
grid: [
[6, 7, 8],
[11, 12, 13],
[16, 17, 18],
[0, 4, 20, 24],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
teams: {
grid: [
[6, 7, 8],
[11, 12, 13],
[16, 17, 18],
[0, 4, 20, 24],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
billing: {
grid: [
[0, 4],
[5, 6, 8, 9],
[10, 11, 13, 14],
[15, 19],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
"account-settings": {
grid: [
[2, 7, 12, 17, 22],
[5, 10, 15, 20],
[8, 13, 18],
[11, 16],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
admin: {
grid: [
[0, 1, 2, 3, 4],
[5, 14],
[10, 11, 12, 13],
[15, 24],
[20, 21, 22, 23, 24],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
"domain-checker": {
grid: [
[12, 13, 14],
[7, 11, 15, 19],
[2, 6, 20, 24],
[0, 1, 25, 26],
],
gridSize: 6,
cellSize: 2,
spacing: 2,
offset: 3,
},
"extract-playground": {
grid: [
[5, 10, 15, 20],
[6, 11, 16, 21],
[7, 12, 17, 22],
[8, 13, 18, 23],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
extract: {
grid: [[12], [7, 17], [2, 6, 18, 22], [0, 1, 3, 4, 20, 21, 23, 24]],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
logs: {
grid: [
[0, 5, 10, 15, 20],
[1, 6, 11, 16, 21],
[2, 7, 12, 17, 22],
[3, 8, 13, 18, 23],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
playground: {
grid: [
[6, 8, 16, 18],
[10, 11, 12, 13, 14],
[5, 9, 15, 19],
[0, 4, 20, 24],
],
gridSize: 5,
cellSize: 2,
spacing: 2,
offset: 3,
},
};
export function AnimatedDotIcon({
active = true,
alwaysHeat = false,
triggerOnHover = false,
size = 20,
className,
pattern = "usage",
}: AnimatedDotIconProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const fnRefs = useRef<{
activate: () => void;
deactivate: () => void;
}>({ activate: () => {}, deactivate: () => {} });
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = initCanvas(canvas);
const config = patterns[pattern];
let isRunning = false;
let isActive = false;
let activeGroup = 0;
const rowAlphas = [0.2, 0.4, 1, 0.04];
const scaler = size / 20;
const render = () => {
ctx.fillStyle = "#fa5d19";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const group of config.grid.slice(0, 4)) {
const groupIndex = config.grid.indexOf(group);
ctx.globalAlpha = rowAlphas[groupIndex];
for (const index of group) {
ctx.fillRect(
(config.offset + (index % config.gridSize) * config.spacing) *
scaler,
(config.offset +
Math.floor(index / config.gridSize) * config.spacing) *
scaler,
config.cellSize * scaler,
config.cellSize * scaler,
);
}
}
if (isRunning) {
requestAnimationFrame(render);
}
};
const timeouts: number[] = [];
let runCount = 0;
const cycle = () => {
isRunning = true;
activeGroup = (activeGroup + 1) % 5;
rowAlphas.forEach((alpha, index) => {
let targetAlpha = alpha;
if (index === activeGroup) targetAlpha = 1;
else if (index === (activeGroup + 1) % 4) targetAlpha = 0.12;
else if (index === (activeGroup + 2) % 4) targetAlpha = 0.2;
else if (index === (activeGroup + 3) % 4) targetAlpha = 0.4;
animate(alpha, targetAlpha, {
duration: 0.05,
onUpdate: (value) => {
rowAlphas[index] = value;
},
});
});
timeouts.forEach((timeout) => {
window.clearTimeout(timeout);
});
timeouts.push(
window.setTimeout(() => {
isRunning = false;
}, 300),
);
if (activeGroup === 3) runCount += 1;
if ((runCount === 2 || !isActive) && activeGroup === 2) return;
timeouts.push(
window.setTimeout(() => {
cycle();
}, 50),
);
};
fnRefs.current = {
activate: () => {
if (isActive) return;
isActive = true;
runCount = 0;
cycle();
render();
},
deactivate: () => {
if (!isActive) return;
isActive = false;
},
};
render();
canvas.addEventListener("resize", render);
if (triggerOnHover) {
const group = canvasRef.current!.closest(".group");
if (group) {
group.addEventListener("mouseenter", fnRefs.current.activate);
group.addEventListener("mouseleave", fnRefs.current.deactivate);
return () => {
group.removeEventListener("mouseenter", fnRefs.current.activate);
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
};
}
}
}, [triggerOnHover, size, pattern]);
useEffect(() => {
if (triggerOnHover) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && active) {
fnRefs.current.activate();
} else {
fnRefs.current.deactivate();
}
},
{ threshold: 0.5 },
);
observer.observe(canvasRef.current!);
return () => {
observer.disconnect();
};
}, [active, triggerOnHover]);
return (
<canvas
className={cn(
alwaysHeat
? ""
: [
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
!active && "grayscale",
],
className,
)}
ref={canvasRef}
style={{ width: size, height: size }}
/>
);
}
+58
View File
@@ -0,0 +1,58 @@
"use client";
import React, { useRef, useEffect, ReactNode, useState } from "react";
// Smoothly animates its container to match the natural height of its content.
// Fixes previous behavior where the component observed itself, causing height 0
// with overflow hidden (content clipped) or visible overflow that overlapped
// following sections like the footer.
export default function AnimatedHeight({
children,
overflow = true,
}: {
children: ReactNode;
overflow?: boolean;
}) {
const containerRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const [measuredHeight, setMeasuredHeight] = useState<number | null>(null);
const hasAnimatedOnceRef = useRef(false);
useEffect(() => {
const contentEl = contentRef.current;
const containerEl = containerRef.current;
if (!contentEl || !containerEl) return;
const updateHeight = () => {
// Use scrollHeight to capture full natural height, including overflowed content
const height = contentEl.scrollHeight;
setMeasuredHeight((prev) => (prev === height ? prev : height));
// Enable transition after the first measurement to avoid initial jank
if (!hasAnimatedOnceRef.current) {
containerEl.style.transition = "height 300ms ease-in-out";
hasAnimatedOnceRef.current = true;
}
};
// Initial measure
updateHeight();
const resizeObserver = new ResizeObserver(() => updateHeight());
resizeObserver.observe(contentEl);
return () => resizeObserver.disconnect();
}, []);
return (
<div
ref={containerRef}
style={{
overflow: overflow ? "hidden" : "visible",
height: measuredHeight === null ? undefined : `${measuredHeight}px`,
}}
>
<div ref={contentRef}>{children}</div>
</div>
);
}
+60
View File
@@ -0,0 +1,60 @@
"use client";
import { useEffect, useState } from "react";
import { cn } from "@/utils/cn";
const asciiPatterns = [
`· · · · · · · · · · · · · · · · · · · ·
· · · · · · · · · · · · · · · · · · ·
· · · · · · · · · · · · · · · · · · · ·
· · · · · · · · · · · · · · · · · · ·
· · · · · · · · · · · · · · · · · · · ·`,
`· · · · · · · · · · · · · · · · · · · ·
· · · · ▪ · · · · · · · · ▪ · · · · ·
· · · · · · · · · · · · · · · · · · · ·
· · · · · · · · · · · · · · · · · · ·
· · · · ▪ · · · · · · · · ▪ · · · · · ·`,
`· · · · · · · · · · · · · · · · · · · ·
· · · ▪ ▄ ▪ · · · · · · ▪ ▄ ▪ · · · ·
· · · · ▪ · · · · · · · · ▪ · · · · · ·
· · · · · · · · · · · · · · · · · · ·
· · · ▪ ▄ ▪ · · · · · · ▪ ▄ ▪ · · · · ·`,
`· · · · · · · · · · · · · · · · · · · ·
· · ▪ ▄ █ ▄ ▪ · · · · ▪ ▄ █ ▄ ▪ · · ·
· · · ▪ ▄ ▪ · · · · · · ▪ ▄ ▪ · · · · ·
· · · ▪ · · · · · · · · · ▪ · · · · ·
· · ▪ ▄ █ ▄ ▪ · · · · ▪ ▄ █ ▄ ▪ · · · ·`,
];
interface AsciiBackgroundProps {
className?: string;
variant?: "dots" | "grid" | "flame";
}
export function AsciiBackground({
className,
variant = "dots",
}: AsciiBackgroundProps) {
const [frameIndex, setFrameIndex] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setFrameIndex((prev) => (prev + 1) % asciiPatterns.length);
}, 2000);
return () => clearInterval(interval);
}, []);
return (
<div
className={cn(
"absolute inset-0 pointer-events-none select-none overflow-hidden",
className,
)}
>
<pre className="text-heat-100/3 font-mono text-[10px] leading-tight whitespace-pre absolute top-0 left-0 w-full h-full flex items-center justify-center">
{asciiPatterns[frameIndex]}
</pre>
</div>
);
}
@@ -0,0 +1,59 @@
"use client";
import React, { useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "@/components/shared/effects/flame/explosion-data.json";
interface AsciiFlameBackgroundProps {
className?: string;
colorClassName?: string;
fontSizePx?: number;
lineHeightPx?: number;
}
// Reusable ASCII flame background (same frames used by CoreReliableBarFlame)
export default function AsciiFlameBackground({
className,
colorClassName = "text-heat-100/30",
fontSizePx = 10,
lineHeightPx = 12.5,
}: AsciiFlameBackgroundProps) {
const wrapperRef = useRef<HTMLDivElement>(null);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let index = 0;
const stop = setIntervalOnVisible({
element: wrapperRef.current,
callback: () => {
index += 1;
if (index >= (data as string[]).length) index = 0;
if (ref.current) ref.current.innerHTML = (data as string[])[index];
},
interval: 80,
});
return () => stop?.();
}, []);
return (
<div
ref={wrapperRef}
className={cn("relative pointer-events-none select-none", className)}
>
<div
ref={ref}
className={cn(
"font-ascii absolute inset-0 fc-decoration",
colorClassName,
)}
style={{
whiteSpace: "pre",
fontSize: `${fontSizePx}px`,
lineHeight: `${lineHeightPx}px`,
}}
/>
</div>
);
}
+72
View File
@@ -0,0 +1,72 @@
.button {
transition: all 0.2s cubic-bezier(0.25, 0.1, 0.25, 1),
scale 0.1s cubic-bezier(0.25, 0.1, 0.25, 1),
box-shadow 0.1s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.button:active {
transition: all 0.2s cubic-bezier(0.25, 0.1, 0.25, 1),
scale 0.05s cubic-bezier(0.25, 0.1, 0.25, 1),
box-shadow 0.05s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.button-primary {
background: #ff4c00;
background: color(display-p3 0.9816 0.3634 0.0984);
box-shadow: 0px -6px 12px 0px rgba(255, 0, 0, 0.2) inset,
0px 2px 4px 0px rgba(255, 77, 0, 0.12),
0px 1px 1px 0px rgba(255, 77, 0, 0.12),
0px 0.5px 0.5px 0px rgba(255, 77, 0, 0.16),
0px 0.25px 0.25px 0px rgba(255, 77, 0, 0.2);
box-shadow: 0px -6px 12px 0px color(display-p3 0.9804 0.1127 0.098 / 0.2) inset,
0px 2px 4px 0px color(display-p3 0.9804 0.3647 0.098 / 0.12),
0px 1px 1px 0px color(display-p3 0.9804 0.3647 0.098 / 0.12),
0px 0.5px 0.5px 0px color(display-p3 0.9804 0.3647 0.098 / 0.16),
0px 0.25px 0.25px 0px color(display-p3 0.9804 0.3647 0.098 / 0.2);
}
.button-primary:hover {
box-shadow: 0px -6px 12px 0px rgba(255, 0, 0, 0.2) inset,
0px 4px 8px 0px rgba(255, 77, 0, 0.16),
0px 1px 1px 0px rgba(255, 77, 0, 0.12),
0px 0.5px 0.5px 0px rgba(255, 77, 0, 0.16),
0px 0.25px 0.25px 0px rgba(255, 77, 0, 0.2);
box-shadow: 0px -6px 12px 0px color(display-p3 0.9804 0.1127 0.098 / 0.2) inset,
0px 4px 8px 0px color(display-p3 0.9804 0.3647 0.098 / 0.16),
0px 1px 1px 0px color(display-p3 0.9804 0.3647 0.098 / 0.12),
0px 0.5px 0.5px 0px color(display-p3 0.9804 0.3647 0.098 / 0.16),
0px 0.25px 0.25px 0px color(display-p3 0.9804 0.3647 0.098 / 0.2);
}
.button-primary:active {
box-shadow: 0px -6px 12px 0px rgba(255, 0, 0, 0.2) inset,
0px 2px 4px 0px rgba(255, 77, 0, 0.12),
0px 1px 1px 0px rgba(255, 77, 0, 0.12),
0px 0.5px 0.5px 0px rgba(255, 77, 0, 0.16),
0px 0.25px 0.25px 0px rgba(255, 77, 0, 0.2);
box-shadow: 0px -6px 12px 0px color(display-p3 0.9804 0.1127 0.098 / 0.2) inset,
0px 2px 4px 0px color(display-p3 0.9804 0.3647 0.098 / 0.12),
0px 1px 1px 0px color(display-p3 0.9804 0.3647 0.098 / 0.12),
0px 0.5px 0.5px 0px color(display-p3 0.9804 0.3647 0.098 / 0.16),
0px 0.25px 0.25px 0px color(display-p3 0.9804 0.3647 0.098 / 0.2);
}
.button-background {
background: linear-gradient(to bottom, white, transparent);
opacity: 0.06;
transition: opacity 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.button:hover .button-background {
opacity: 0.08;
}
.button:active .button-background {
opacity: 0;
transition: opacity 0.05s cubic-bezier(0.25, 0.1, 0.25, 1);
}
+67
View File
@@ -0,0 +1,67 @@
import { Children, ButtonHTMLAttributes } from "react";
import { cn } from "@/utils/cn";
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "tertiary" | "playground" | "destructive";
size?: "default" | "large";
disabled?: boolean;
}
export default function Button({
variant = "primary",
size = "default",
disabled,
...attrs
}: Props) {
const children = handleChildren(attrs.children);
return (
<button
{...attrs}
type={attrs.type ?? "button"}
className={cn(
attrs.className,
"[&>span]:px-6 flex items-center justify-center button relative [&>*]:relative",
"text-label-medium lg-max:[&_svg]:size-24",
`button-${variant} group/button`,
{
"rounded-8 p-6": size === "default",
"rounded-10 p-8 gap-2": size === "large",
"text-accent-white active:[scale:0.995]": variant === "primary",
"text-accent-black active:[scale:0.99] active:bg-black-alpha-7": [
"secondary",
"tertiary",
"playground",
].includes(variant),
"bg-black-alpha-4 hover:bg-black-alpha-6": variant === "secondary",
"hover:bg-black-alpha-4": variant === "tertiary",
},
variant === "playground" && [
"inside-border before:border-black-alpha-4",
disabled
? "before:opacity-0 bg-black-alpha-4 text-black-alpha-24"
: "hover:bg-black-alpha-4 hover:before:opacity-0 active:before:opacity-0",
],
)}
disabled={disabled}
>
{variant === "primary" && (
<div className="overlay button-background !absolute" />
)}
{children}
</button>
);
}
const handleChildren = (children: React.ReactNode) => {
return Children.toArray(children).map((child) => {
if (typeof child === "string") {
return <span key={child}>{child}</span>;
}
return child;
});
};
@@ -0,0 +1,148 @@
"use client";
import React from "react";
import { cn } from "@/utils/cn";
import { LucideIcon } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import AnimatedWidth from "@/components/shared/layout/animated-width";
interface CapsuleButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
icon?: LucideIcon | React.ComponentType<{ className?: string }>;
iconPosition?: "left" | "right";
children: React.ReactNode;
size?: "sm" | "md" | "lg";
fullWidth?: boolean;
variant?: "primary" | "secondary" | "tertiary" | "ghost";
loading?: boolean;
}
export function CapsuleButton({
icon: Icon,
iconPosition = "left",
children,
className,
size = "md",
fullWidth = false,
variant = "primary",
loading = false,
disabled,
...props
}: CapsuleButtonProps) {
const [isPressed, setIsPressed] = React.useState(false);
const sizeClasses = {
sm: "h-32 px-16 text-label-small gap-6",
md: "h-40 px-20 text-label-medium gap-8",
lg: "h-40 px-20 text-label-medium gap-8",
};
const iconSizes = {
sm: "w-14 h-14",
md: "w-16 h-16",
lg: "w-16 h-16",
};
const variants = {
primary: [
"bg-heat-100 text-white",
"hover:bg-heat-200",
"active:scale-[0.98]",
"shadow-[0_1px_2px_rgba(0,0,0,0.05)]",
"hover:shadow-[0_4px_12px_rgba(250,93,25,0.25)]",
],
secondary: [
"bg-black text-white",
"hover:bg-black/90",
"active:scale-[0.98]",
"shadow-[0_1px_2px_rgba(0,0,0,0.05)]",
"hover:shadow-[0_4px_12px_rgba(0,0,0,0.15)]",
],
tertiary: [
"bg-white text-black border border-black-alpha-8",
"hover:bg-black-alpha-4 hover:border-black-alpha-12",
"active:scale-[0.98]",
],
ghost: [
"bg-transparent text-black-alpha-60",
"hover:text-black hover:bg-black-alpha-4",
"active:scale-[0.98]",
],
};
const isDisabled = disabled || loading;
return (
<button
className={cn(
// Base styles
"inline-flex items-center justify-center rounded-full transition-all duration-200",
// Size
sizeClasses[size],
// Variant
variants[variant],
// Full width
fullWidth && "w-full",
// Disabled state
isDisabled && [
"opacity-50 cursor-not-allowed",
"hover:shadow-none hover:bg-current",
],
// Pressed state
isPressed && "scale-[0.98]",
className,
)}
disabled={isDisabled}
onMouseDown={() => !isDisabled && setIsPressed(true)}
onMouseUp={() => setIsPressed(false)}
onMouseLeave={() => setIsPressed(false)}
{...props}
>
<AnimatedWidth initial={{ width: "auto" }}>
<AnimatePresence initial={false} mode="popLayout">
{loading ? (
<motion.div
key="loading"
animate={{ opacity: 1, filter: "blur(0px)", scale: 1 }}
className="flex gap-8 items-center justify-center"
exit={{ opacity: 0, filter: "blur(2px)", scale: 0.9 }}
initial={{ opacity: 0, filter: "blur(2px)", scale: 0.95 }}
>
<span>Loading...</span>
</motion.div>
) : (
<motion.div
key="content"
animate={{ opacity: 1, filter: "blur(0px)", scale: 1 }}
className="flex gap-8 items-center justify-center"
exit={{ opacity: 0, filter: "blur(2px)", scale: 0.9 }}
initial={{ opacity: 0, filter: "blur(2px)", scale: 0.95 }}
>
{Icon && iconPosition === "left" && (
<span
className={cn(
iconSizes[size],
"flex-shrink-0 inline-flex items-center justify-center",
)}
>
<Icon className="w-full h-full" />
</span>
)}
<span>{children}</span>
{Icon && iconPosition === "right" && (
<span
className={cn(
iconSizes[size],
"flex-shrink-0 inline-flex items-center justify-center",
)}
>
<Icon className="w-full h-full" />
</span>
)}
</motion.div>
)}
</AnimatePresence>
</AnimatedWidth>
</button>
);
}
@@ -0,0 +1,48 @@
import Link from "next/link";
import { cn } from "@/utils/cn";
interface FireActionLinkProps {
href?: string;
label: string;
className?: string;
variant?: "link" | "button";
onClick?: () => void;
}
export function FireActionLink({
href,
label,
className,
variant = "link",
onClick,
}: FireActionLinkProps) {
const baseClasses =
variant === "button"
? cn(
"inline-block py-4 px-8 rounded-6",
"text-label-small text-heat-100 bg-heat-4",
"hover:bg-heat-8 transition-all",
"active:scale-[0.98]",
className,
)
: cn(
"text-label-small text-secondary hover:text-heat-100 transition-all",
"hover:underline underline-offset-4",
"active:scale-[0.98]",
className,
);
if (onClick) {
return (
<button onClick={onClick} className={baseClasses}>
{label}
</button>
);
}
return (
<Link href={href || "#"} className={baseClasses}>
{label}
</Link>
);
}
+4
View File
@@ -0,0 +1,4 @@
// Button Components
export { SlateButton } from "./slate-button";
// export { HeatButton } from "./heat-button";
export { FireActionLink } from "./fire-action-link";
+122
View File
@@ -0,0 +1,122 @@
"use client";
import React from "react";
import { cn } from "@/utils/cn";
import { LucideIcon } from "lucide-react";
interface SlateButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
icon?:
| LucideIcon
| React.ComponentType<{
className?: string;
isHovered?: boolean;
isOpen?: boolean;
}>
| React.ReactNode;
iconPosition?: "left" | "right";
children: React.ReactNode;
size?: "sm" | "md" | "lg";
fullWidth?: boolean;
isLoading?: boolean;
isOpen?: boolean;
}
export const SlateButton = React.forwardRef<
HTMLButtonElement,
SlateButtonProps
>(
(
{
icon: Icon,
iconPosition = "left",
children,
className,
size = "md",
fullWidth = false,
isLoading = false,
isOpen = false,
disabled,
...props
},
ref,
) => {
const [isHovered, setIsHovered] = React.useState(false);
const sizeClasses = {
sm: "h-32 px-12 text-body-small gap-6",
md: "h-40 px-16 text-body-medium gap-8",
lg: "h-48 px-24 text-body-large gap-10",
};
const iconSizes = {
sm: "w-14 h-14",
md: "w-16 h-16",
lg: "w-20 h-20",
};
return (
<button
ref={ref}
className={cn(
// Base styles
"inline-flex items-center justify-center rounded-12 transition-all",
// Colors
"bg-black-alpha-4 text-accent-black",
"hover:bg-black-alpha-6",
"active:scale-[0.98]",
// Border
// "border-0",
// Size
sizeClasses[size],
// States
disabled && "opacity-50 cursor-not-allowed",
isLoading && "cursor-wait",
// Full width
fullWidth && "w-full",
className,
)}
disabled={disabled || isLoading}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
{...props}
>
{isLoading ? (
<div className={cn("animate-spin rounded-full", iconSizes[size])} />
) : (
<>
{Icon &&
iconPosition === "left" &&
(React.isValidElement(Icon) ? (
Icon
) : (
//@ts-ignore
<Icon
className={cn(iconSizes[size], "flex-shrink-0")}
// @ts-ignore - Some icons support isHovered and isOpen
isHovered={isHovered}
isOpen={isOpen}
/>
))}
{children}
{Icon &&
iconPosition === "right" &&
(React.isValidElement(Icon) ? (
Icon
) : (
//@ts-ignore
<Icon
className={cn(iconSizes[size], "flex-shrink-0")}
// @ts-ignore - Some icons support isHovered and isOpen
isHovered={isHovered}
isOpen={isOpen}
/>
))}
</>
)}
</button>
);
},
);
SlateButton.displayName = "SlateButton";
@@ -0,0 +1,31 @@
import colors from "@/styles/colors.json";
const TYPED_COLORS = colors as unknown as Record<
string,
Record<"hex" | "p3", string>
>;
const hslValues = Object.entries(TYPED_COLORS).map(([key, value]) => {
// Fix hex values - they need # prefix
const hexValue = value.hex.startsWith("#") ? value.hex : `#${value.hex}`;
return `--${key}: ${hexValue}`;
});
const p3Values = Object.entries(TYPED_COLORS)
.filter(([, value]) => value.p3)
.map(([key, value]) => `--${key}: color(display-p3 ${value.p3})`);
const colorsStyle = `
:root {
${hslValues.join(";\n ")}
}
@supports (color: color(display-p3 1 1 1)) {
:root {
${p3Values.join(";\n ")}
}
}`;
export default function ColorStyles() {
return <style dangerouslySetInnerHTML={{ __html: colorsStyle }} />;
}
+174
View File
@@ -0,0 +1,174 @@
import { animate, AnimatePresence, cubicBezier, motion } from "motion/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { cn } from "@/utils/cn";
import { lockBody } from "../lockBody";
import PortalToBody from "../utils/portal-to-body";
export default function Combobox({
placeholder,
options,
value,
onChange,
className,
}: {
placeholder?: string;
options: { label: string; value: string }[];
value: string;
onChange: (value: string) => void;
className?: string;
}) {
const selected = useMemo(() => {
return options.find((option) => option.value === value);
}, [options, value]);
const [isOpen, setIsOpen] = useState(false);
const [bounds, setBounds] = useState<DOMRect | null>(null);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
lockBody("combobox", isOpen);
}, [isOpen]);
useEffect(() => {
document.addEventListener("click", (e) => {
if (ref.current && e.composedPath().includes(ref.current)) {
return;
}
setIsOpen(false);
});
}, []);
return (
<div className={cn("w-full", className)} ref={ref}>
<button
className={cn(
"relative bg-accent-white flex w-full gap-4 rounded-8 p-6 pl-10",
"inside-border before:border-black-alpha-8 hover:before:border-black-alpha-12 hover:bg-black-alpha-2",
"text-body-medium",
isOpen &&
"!bg-accent-white before:!border-heat-100 before:!border-[1.25px]",
)}
type="button"
onClick={(e) => {
e.preventDefault();
setIsOpen(!isOpen);
setBounds(ref.current?.getBoundingClientRect() ?? null);
}}
>
<div className={cn("flex-1", !selected && "text-black-alpha-40")}>
{selected?.label || placeholder}
</div>
<motion.svg
animate={{ rotate: isOpen ? 180 : 0 }}
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7 8.5L10 11.5L13 8.5"
stroke="#262626"
strokeLinecap="round"
strokeLinejoin="round"
strokeOpacity="0.56"
strokeWidth="1.25"
/>
</motion.svg>
</button>
<PortalToBody>
<AnimatePresence initial={false}>
{isOpen && bounds && (
<motion.div
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
className="fixed bg-accent-white rounded-12 z-[401]"
exit={{ opacity: 0, y: 0, filter: "blur(4px)" }}
initial={{ opacity: 0, y: -12, filter: "blur(4px)" }}
style={{
top: bounds.top + bounds.height + 8,
left: bounds.left,
width: bounds.width,
boxShadow:
"0px 32px 40px 6px rgba(0, 0, 0, 0.02), 0px 12px 32px 0px rgba(0, 0, 0, 0.02), 0px 24px 32px -8px rgba(0, 0, 0, 0.02), 0px 8px 16px -2px rgba(0, 0, 0, 0.02), 0px 0px 0px 1px rgba(0, 0, 0, 0.04)",
}}
transition={{ duration: 0.2 }}
>
<div className="p-4">
<Items
options={options}
onChange={(value) => {
onChange(value);
setIsOpen(false);
}}
/>
</div>
</motion.div>
)}
</AnimatePresence>
</PortalToBody>
</div>
);
}
const Items = ({
options,
onChange,
}: {
options: { label: string; value: string }[];
onChange: (value: string) => void;
}) => {
const backgroundRef = useRef<HTMLDivElement>(null);
return (
<div className="relative">
<div
className="absolute top-0 opacity-0 left-0 bg-black-alpha-4 rounded-8 w-full h-32 pointer-events-none"
ref={backgroundRef}
/>
{options.map((option) => (
<button
className="w-full group py-6 px-10 text-label-small"
key={option.value}
type="button"
onClick={() => {
onChange(option.value);
}}
onMouseEnter={(e) => {
const t = e.target as HTMLElement;
let target =
t instanceof HTMLButtonElement
? t
: (t.closest("button") as HTMLButtonElement);
target = target.closest(".group") as HTMLButtonElement;
animate(backgroundRef.current!, { scale: 0.995 }).then(() =>
animate(backgroundRef.current!, { scale: 1 }),
);
animate(
backgroundRef.current!,
{
y: target.offsetTop,
opacity: 1,
},
{
ease: cubicBezier(0.165, 0.84, 0.44, 1),
duration: 0.2,
},
);
}}
onMouseLeave={() => {
animate(backgroundRef.current!, { opacity: 0 });
}}
>
{option.label}
</button>
))}
</div>
);
};
@@ -0,0 +1,73 @@
# Flame Effects Rules
When working with visual effects components in components-new/shared/effects:
## Flame ASCII System
The flame effects are data-driven ASCII animations that create subtle, fire-inspired backgrounds.
### How It Works
1. **data.json Files**: Each flame component has an accompanying `data.json` file containing ASCII art frames
2. **Frame Animation**: Components cycle through frames at specified intervals (40-85ms)
3. **Visibility-Based**: Uses `setIntervalOnVisible` to only animate when in viewport
4. **innerHTML Rendering**: ASCII frames are inserted as HTML to preserve formatting
### Available Flames
#### CoreFlame
- Frame Speed: 80ms
- Size: 1110px × 470px
- Color: `text-black-alpha-20`
- Usage: Background texture for sections
#### AsciiExplosion
- Frame Speed: 40ms (faster)
- Initial Delay: 30 frames (1.2s)
- Color: `text-[#FA5D19]` (heat orange)
- Usage: Dramatic accent for CTAs or empty states
#### HeroFlame
- Frame Speed: 85ms
- Features: Mirrored flames on both sides
- Usage: Hero sections with dual flames
#### FlameBackground (Wrapper)
- Intensity based on metrics (0-100)
- Dynamic color (black → orange)
- Speed increases with intensity
- Optional pulse animation
### Usage Examples
```tsx
import { CoreFlame } from '@/components/shared/effects/flame';
import { FlameBackground } from '@/components/shared/effects/flame';
// Static flame
<div className="relative">
<CoreFlame />
<YourContent />
</div>
// Dynamic intensity flame
<FlameBackground intensity={cpuUsage} animate={cpuUsage > 80}>
<DashboardCard />
</FlameBackground>
```
### Performance Considerations
- **Viewport Detection**: Only animates when visible
- **GPU Acceleration**: Use `transform` for positioning
- **Frame Caching**: Frames are pre-loaded from JSON
- **Cleanup**: Intervals cleared on unmount
### Design Guidelines
- **Subtlety**: Keep opacity low (10-30%) for backgrounds
- **Context**: Use sparingly, match intensity to data
- **Accessibility**: Ensure contrast ratios maintained
- **Mobile**: Consider reducing/disabling on mobile for performance
### Custom Utility Classes
- `cw-*`: Custom width (e.g., `cw-720` = 720px)
- `ch-*`: Custom height (e.g., `ch-470` = 470px)
- `font-ascii`: Monospace font for ASCII art
- Colors from heat scale: `text-heat-*`, `text-black-alpha-*`
+57
View File
@@ -0,0 +1,57 @@
"use client";
import { HTMLAttributes, useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./hero-flame-data.json";
export default function CoreFlame(attrs: HTMLAttributes<HTMLDivElement>) {
const ref = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let index = 0;
const interval = setIntervalOnVisible({
element: wrapperRef.current,
callback: () => {
index++;
if (index >= data.length) index = 0;
const newStr = data[index];
ref.current!.innerHTML = newStr;
},
interval: 80,
});
return () => interval?.();
}, []);
return (
<>
<div className="absolute inset-10 -z-[10] overflow-clip">
<div
ref={wrapperRef}
{...attrs}
className={cn(
"cw-[1110px] ch-470 absolute pointer-events-none select-none",
attrs.className,
)}
>
<div
className="text-black-alpha-20 relative left-0 font-ascii fc-decoration"
ref={ref}
style={{
whiteSpace: "pre",
fontSize: 8,
lineHeight: "10px",
}}
/>
</div>
</div>
</>
);
}
@@ -0,0 +1,56 @@
"use client";
import { HTMLAttributes, useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./explosion-data.json";
export function AsciiExplosion(attrs: HTMLAttributes<HTMLDivElement>) {
const ref = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let index = -30;
const interval = setIntervalOnVisible({
element: wrapperRef.current,
callback: () => {
index++;
if (index >= data.length) index = -40;
if (index < 0) return;
ref.current!.innerHTML = data[index];
},
interval: 40,
});
return () => interval?.();
}, []);
return (
<div
ref={wrapperRef}
{...attrs}
className={cn(
"w-[720px] h-[400px] absolute flex gap-16 pointer-events-none select-none",
attrs.className,
)}
>
<div
className="text-[#FA5D19] font-mono fc-decoration"
dangerouslySetInnerHTML={{ __html: data[0] }}
ref={ref}
style={{
whiteSpace: "pre",
fontSize: "10px",
lineHeight: "12.5px",
}}
/>
</div>
);
}
// Default export for backward compatibility
export default AsciiExplosion;
@@ -0,0 +1,64 @@
"use client";
import { HTMLAttributes, useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./pulse-data.json";
interface AuthPulseProps extends HTMLAttributes<HTMLDivElement> {
interval?: number;
opacity?: number;
}
export function AuthPulse({
interval = 100,
opacity = 0.15,
className,
...attrs
}: AuthPulseProps) {
const ref = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const frameIndex = useRef(0);
useEffect(() => {
const animate = () => {
if (ref.current) {
ref.current.innerHTML = data[frameIndex.current];
frameIndex.current = (frameIndex.current + 1) % data.length;
}
};
// Initialize first frame
animate();
const cleanup = setIntervalOnVisible({
element: wrapperRef.current,
callback: animate,
interval,
});
return () => cleanup?.();
}, [interval]);
return (
<div
ref={wrapperRef}
{...attrs}
className={cn(
"absolute inset-0 pointer-events-none select-none overflow-hidden",
className,
)}
>
<div
ref={ref}
className="font-mono text-heat-100 absolute inset-0 flex items-center justify-center fc-decoration"
style={{
whiteSpace: "pre",
fontSize: "9px",
lineHeight: "11px",
opacity,
}}
/>
</div>
);
}
@@ -0,0 +1,17 @@
[
" \n \n \n \n \n ░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░ \n \n \n \n \n ",
" \n \n \n \n ░░░░░░ \n ░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░ \n ░░░░░░ \n \n \n \n ",
" \n \n \n ░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░ \n \n \n \n ",
" \n \n ░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▒▒░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▒▒░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n ",
" \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \n \n \n \n "
]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,58 @@
"use client";
import { HTMLAttributes, useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./core-flame.json";
export function CoreFlame(attrs: HTMLAttributes<HTMLDivElement>) {
const ref = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let index = 0;
const interval = setIntervalOnVisible({
element: wrapperRef.current,
callback: () => {
index++;
if (index >= data.length) index = 0;
const newStr = data[index];
ref.current!.innerHTML = newStr;
},
interval: 80,
});
return () => interval?.();
}, []);
return (
<>
<div className="absolute inset-10 -z-[10] overflow-clip">
<div
ref={wrapperRef}
{...attrs}
className={cn(
"cw-[1110px] ch-470 absolute pointer-events-none select-none",
attrs.className,
)}
>
<div
className="text-black-alpha-20 relative left-0 font-ascii"
ref={ref}
style={{
whiteSpace: "pre",
fontSize: 8,
lineHeight: "10px",
}}
/>
</div>
</div>
</>
);
}
// Export default for backward compatibility
export default CoreFlame;
@@ -0,0 +1,27 @@
[
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '_^_ ,^_' \n '.\"\"\"+=\"\"=+\"\"^-'' \n ''..:::,_::::::_,:::..'' \n ''.-_\"::___:^^\"___::\":-.''' \n '.'-_:::\"+^\"\"\"+^:::_-'.' \n \n \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '-''''-' \n ',::_-'-:\"-'_++++^^^^++++:'-\":-'._::,' \n '''''^+++^^\"^++^\"\"^++^^++++^^++^\"\"^++^\"\"^+++^.'''' \n '''-:^^^^+++===+++^\"\"\":^^^\":\"\"\"\":\"^^^:\"\"\"^++++==+++^^^^:-''' \n ''.-_\"^^++++===++^-'''''-:\",....,::,'''''-^++===++++^^\":-.'' \n '''.-_^^+^++=+++:_-..._^^^:\"\":^^^:...-_:+++=++^+^^_-.''' \n '''''.,,_+++^^:_...' '' '..._:^^+++:,,''''''' \n '-' '-' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '''-''''-''' \n '.:+^\"\"''_+^_-:^+++^^^^+++^:-,^+_''\"\"^+:.' \n ' '' ''-.-:..^++=^\"\"\"\"^^\":::\"++^++++^^+^:::\"^^\"\"\"\"^=+++..:-.-'' '''' \n -:::\"\"-,^+++=++++==++^^\"::--_\"^\"^^\"\"\"\"^^\"^\"_--_:\"^^^+==++++=+++^--:\":::- \n '_:^^^^+^^++++++==++^:'''''''.:\"^,--,^\":.'''''''_^++==+++++++^+^^^\":_. \n ''-\"^\"_:_:^++++++++\".'---...:\"^\",,\"^\":-..---'.:+++++++++:_:::^^,'' \n '.'' '-\",.-.'-\"^^^,:^\"_:-' ' ' '-:_\"^:,^^^^-'.-.,\"-' ''.' \n '''.,- -_.''' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ','' ''' '.'.' '.'.' ''' ''-' \n .''''''' '' ',_\"^\"\"^\"\":^^:\"^^^^^^^^^^^^^^^^^:^^:\"\"^\"\"^\"_,' '' ''''' '. \n ':++:++^^:'' ''-,\"_:::\"^++^^\"\"^\"\"_\"^+^^++^\"\"^++^^+^\"_:^^\"\"^^++^\":::_\",-'' '':^^++:^+:' \n '_^++===++^\":.,:+++^^\"\"^^^:::::\"::_,:\"\":::-__-::::\":,,::\":::::^^^\"\"^^+++:,.:\"^++====+^_' \n ',\"+++=++\"_:++++++^\"\"^^^\"-_,,_::-..-_,:\"::,,::\":,_-..-::_,,_,\"^^^\"\"^++++++:_\"++=+++\"_. \n '_-.\"^:-^:,,-.:\"^^^^__::::_,:___:\"::__''''__:\"\":___:,_::::__^^^^\":.-,,:^-:^\".-_' \n -.' .' '.:_::-'''-\"^\"--,_,\"\"^' '^^\"__,--:^\"-'''-::_:-'' '. ''- \n '-.'' ''' '' ''.-. \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' '_.' ''.'' ''.'.' '.'-'' ''.'' '._. ' \n ':.'' '' '' '' ''._::\"^\":^^^\"^^\"^^+^^^^^::^^^^^+^^\"^^\"^^^:\"^\":::.'' '' '' '' ''.:' \n '-^++++^\"\".' ''_^^:_--_:^^^^^^\"\"^^^\"++++^^^\",,:^^^++++\"^^^\"\"\"^^^^^:_--_:^^:'' '.\"\"\"++++^-' \n ,^+++===+^\"_:--\"^^^\"\"\"\"\"\"\":::_::::::\"\"\":::,-,,-,:::\"\"\":_::::_:::\"\"\"\"\"\"^^^^^--:_\"^++==+++^: \n '^^^++++++\",\"++++^\"\":--::_,__:::\":,-,,__:::,''-:::__,,--:::::__,_::,-:\"\"^++++^,:++++++^\"^' \n ':,._^-:'''--,_::_,\"--_:::_::::\"^\"^^:-' '-:^^\"^\"\":::_:::_--\"__::_---''':-^_.,:' \n .' ''-:^\"_.----:^:,,:\"_._\". .\"_.,::,,:^\"----._:^:,'' '. \n ''''' '--,' ',--' ''''' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n .'' ''''''.''':-___.' '''' ' '''' '.___-_.''.'''''' ''. \n '\"\"^'' .\"^\"++\":_^\"^^^+++^^++++^\"\"++:_:_:- .:_:_:++\"\"^++++^^+++^^^\"^__\"++^^\"- ''\"\"\"' \n '^+++^\"_,-_\"::\"\"\"\":^^^^^\"^+++++++=++++^-',_.' ._,'-^++++=++++++++\"^^^^^:\"\"\"\"::\"_-,_:^++++' \n '^+=++^\"^^\"^:_:\"^^^^^^^^^\"^^++^++++^\"::_-''' '' '''-_::\"^++++^++^^\"^^^^^^^^^^:_:^\"^^\"^++=++' \n ':^+++++++^:,,\"^^:_:_:\"\"\"\"^^^^\"^^^^::\":,-' '-,:\"::^^^^^\"^^^\"\":\":_:_:^^\"_,:^+++++++^:' \n ' '' ''...-,::_::,,,_\"\"^++++^^++++^^\"\"\"' ':\"\"^+++++^^++++^\"\"_,_,::_::,-...'' '' ' \n .-\"-,:::_:::\":\"^^^^^^+\"' ':+^^^^^^\":\":::_:::,-\"-. \n '.--.,^\"_''' '-,,' ',,-' '''_\"^,.---' \n '' '' \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' ' \n '' :^,-..'-''-,...-.,__^:-' '.:\"_,_.-.-.,-''-'..-,\": '' \n '::^'' ..\"\"+++++^^+^++++++=+++++++^\":-'_-.- ---_'-:\"^+++++++=++++++++^^+++++\"\".- ''^::' \n \"++++_,,_^^^\"\"^\"^+++++++++++++++==+^^^:.'''' '.''.:^^^+====+++++++++++++^\"^\"\"^^^:,__^+++\" \n \"==++^\"^^^_,:\"^+++++++++^++^++++++^:::-'' '''..''' ''-:::^++++++^++^+++++++++^\":,:^^^\"^++==\" \n .:^++++++^\"-,:^+^:\"\"^^^^^+++^^+^^+^::,-,' ',-,_:^+^^+^^^++^^^^^\"\"\"^+^:,-\"^++++++^\". \n '' .::_:\"\"\"_:\":\"^^^++++=+++++===+:.''' '''':^===++++++++++^^^\":\":_\"\"\":__:- '' \n ''''''_::,:\"^^++^^++++++++^. '^++++++++^^++^^\":,::_'''''' \n -_,--\"^^_:-' ''''.,- -,.'''' '-:_^^\",-,_-' \n ''''' ''''' \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n '_:' ' ' ''' '' ' ''' ' ' ':_' \n .,' -_:::-.:+^__^^^::.'--,\"^:''' ''':^\"_-,'._:^^^__\"+:..:::_- ',. \n '..\"\"\":\"^\"\"^^++++^^^\":\"^++^+++++++^^-''' '''-^^+++++++^++^\":\"^^^++++^^\"\"^\":^\"^..' \n .^^+^^+^^++^^+++^^^^^^^\"^^^^^\"\"^+++^::..' '..::^+++^\"\"^^^^^\"^^^^^^^+++^^++^^+^^+^^. \n '++^^++^\"^+^^^^^^^^^^+^\"::^^\":::\"++^_,.''''''' '''''''.,_^++^::_\"^^:::^++^^^^^^^^^+^\"^++^^+=' \n '_^^^^^+++^\"\"^^::\"^+^\"\"^^+++^::::\"\"^^,'' ' ' ''-^^\"\"::::^+++^^\"\"^+^^::^^\"\"^+++^^^^^:' \n , .:^++^_,--_::^++++^+==+==+=^^^\"::. '::\"^^^++==+==+^++++^::_--,_^++^:. ,' \n -\"^^. ''--_,_\"^++++^^++:\"+^+^+^- -^+^+^+^:++^^++++^\"_,_--' .^^^, \n .'',,_^++\"-'' ' '''' '''' ' ''-\"++^_-_''.' \n '' ' ' ''' \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n '' '' \n ' ''-- ''.',-' '' '' '-,'.'' .-'' ' \n ''.,.'''_::'':+^\":\"^:::-''__:^::. ' ' .::^:__''-:::^\":\"^+:.':\"_'''',-'' \n '-.^++++=+^++^^^+^^^^^^\"^^^^^^^^++:,, ,,:+++^^^^^^^\"^^^^^^+^^^++^+=++++^--' \n '^^==++^+++++++^^^\"\"^^::::\":\"^++^\":\":-.'' ' ' '''-:\"::^++^^:\"::::^^\"\"^^^^++++++^++==+^' \n '+++++++^^+=+^\"^^+++^^:,:::\"\":\"^\"^^^\".''.'' '''''' ''.''.\"^^^\"^\":\"\":::,:^^+^+^^\"^+=++^+++++++' \n ^+++++++^^^^::\"^++^^\"^^++\"\":,,::\"\"^^_- ' ' ._^^\"\"::,,:\"\"++^^\"^^++^\"::^^^^+++++++^ \n :-\"^^++\"_,_::\"\"^+++++++++++^^^++\":_' '_:\"^+^^^+++++++^+++^\"\"::___\"+++^^-: \n -^+++: '-::_:\"^+++^^:^++,'-::::^_ _^:_::,'.++\":\"^+++^\":_::-' _+++^, \n ''' ' .,__\"\"_. '' '' -_\"\"__,. ' ''' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n ' .' '. '' \n '-''' '''''-,\"^_. '' '' .,\"\",-''''' '''.' \n '-:^+^^\"-..::^:^^\"^,-.'.'.'''_,:,.' '.,_,_.''.'.'.--\"\"^^:^::..-\"^^+^:-' \n .,^++===+^^:_:^^^:_:::,,___\"^^\"^^\":--.. ..--:\"^^\"^^\"___,,:::_\"^^^:_:^\"+===++^,. \n :\"^^+++=+^^\"\"\"\"^\":_-,,___::^^^^^\":_\"^:-'''''''' '''' ''''''''-:^\"_:\"^^^^^::___,,-,:\"^\":\"\"^^+=+++^^\":' \n _\"^++^++++^^^^^^^++::_:___:_:\"::\":_\"::--.''''.'' '''' '.''''.--::\"_:\"::\"\"_:___:__:++^^^^^^^+^++^++^^_ \n -_^\"+++++^+^+^^^^^,-.'.-._::^::_.'''..'''' ' ' '''.-''''_::^::_.-.'.-,\"^^^^+^+^+++++\"^_, \n ':^:^+++^\"^^^^::^^:_-.-,:^^:_''.-.' .-.''_:^^:,-.-_:^^\":\"^^^^^+++^:^:' \n '_:::^:.-_,'-:_:::_--..'._:' ''' ''' ':_.'..--_:::,:-',_-._^:::_' \n -.' '' ' ' '' '.- \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n ' ' \n '-,.''..'''''-,' '--''''''.''.,-' \n ''''-:^^^::\"\":_::\"__.'' ' ' ' ' ''.__\"\":_::\"::^^^:-''.' \n '-\"^^+=++++^_:\":+^^^:_-..'''''''-__-' '.__-'''''''..-_:^^^+\"\":_\"^+++=+^^:,' \n ',:^^+==++++:\"^++++:_--,::::::^+++++:.' '.-.''' ' '.--'' '.:^++++^::::::,-._:+++^^\":^++++=+^^\"_' \n _^^+++++++++^\"^^\"^\"\"\"::\"^:\"^++++^++^\":,. ''--,:-_''''''''_-:,--'' ',_\"^^+^++++^\":^\"::\"\"\"^\"\"^\"^+++++++++^^_' \n ':+++++++++++^^^+^:^^^^^+^\"^+^\":\"\"+^:_\"_.'''..''\"-_,..-_-\".'..'''._\"_:^+\"\":\"^+^\"^++^^^^:^+^^^+++++++++++\"' \n ,^^+++++==+^+:::::::::^^:---_-..-.' '.'''''' '''''''''' ''''''.' '.-..-_---:^^:::_:::::^^+==+++++^^_ \n _\"\"++++^+^\"^__::_,_,_\":.-.-,..-.' ''' ''' ' '.-..,,.-._\"_,_,_::__^\"^+^++++^\": \n ':\"+^\"-_.'' ''''''-_''' ' ' '''_,.' '''' ''._,:^+^:' \n ''-,...' ''..,-.' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n '-' ' ' '.' \n '-:^^_-.'''' .' '.' ''''.-_^^:,' \n ',_,'_^++++\"_:^:\"\"__' '__\"\":^:,\"++++^:',_,' \n ':\"\"^+++++++^\"\"^^:_::----..--_--'-__ -_-'.-_,--.----::_:^+^\"^+++++++^\"\":' \n _\"\"^^++==++^^+++^_-_:_::_:\"\"++++=+^\"-.'' :^^, -^\": ''.-\"^^=++++\"\":_::,::-_^++++^++==++^^\"\"_' \n _\"^^+++==+++++^^\"\"^^^\"\"\"\"\"^++==+\"\"^++\"^:-'^+:' '-' ''__'' '-' ':+^'.:^\"++^:\"^===+^\"\"^\":^^^^\"^^+++++==+++^^:_' \n '\"^++++++++++++++^^^+^\"^\"::++^+:--:+^\"\"_.,\":-.'.----''''----.'.-_\",._\"\"^+:--:+^++\":\"^\"\"++^^++++++++++++++^\"' \n -:^++++++^+^^+^^\"\"^^,_:,_\"++^:_-'''-.-,,:^\".''.''.' '.''.''.:^:,,-.-.''-_:^++\"_,:_,\"^\"\"^^+^^+^^+++++^\"- \n ::^+++\":\":\"\":^^\"^\":\"-'''--,,--' ''''' ' ' ''''' '--,,,-.''.\":\"^\"^^:\"\":\":\"+++^:: \n ':\"^^:_-' ''.-.' '.-.'' '-_:^^\"\"' \n '_:^,.-' '-.,^:_' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n '' '' \n ''..::- -:\"..'' \n ''' '.,^:\"\":-'' ' ' ''-:\"\":^_.' ''' \n '-_::____:\"\"\"^\"_,---..''' ' ''' ''' ' ''...---,_:^\"\"\"\"____::_-' \n -::::\":\"\"\"^^\"^:_---:_..----.''.\"^^- .^^\".''.----..,:---_:^\"^^\"\"\":\"::::, \n -:\":\"\":\"^^+++^\",,_:_:^\"^^\"\"^\":^+\"^+^-'''-^. ' ' '^,'''-\"+^\"+^:\"^\"\"^^\"\":_:_-,:^+++^^\":\"\":\":- \n .._:^^^^^^+++^:,-\"^\"^+++^^^:^^^^+=+++-.\"_,..' ''' ''' '..,,\"--+++=+^^^^:^^^+++^\"^\"-,:^+++^^+^^^:_.' \n ':\"^\"^^^^^++^^_-:^^^+++++:\"\":\"\"+++++^:^^\"_'''''___''' ''',__''''',\"^^:^+++++\"\":\"\":+++++^^^:,_^^++^^^^^\"^\"\"' \n ,\"\"^^^^^^^++\"_,_-_:\"\"\"++++:_.._-:,,--,_,.'''''-:-'''' ''''-:-'''''.,_,--,,:,_.'_:++++^\"^:_-_,_\"++^^^^^^^\"\"_ \n '-_\"^^+\":::\"\"--,__-:-'''-,. ''''''',.' ''' '''' '''' ''' '.-''''''' .,-'''-:-__,--\"\":::\"+^^\"_-' \n ._:::_,' '''' ' ' '''' ',_:::_- \n .,::,,'' '',,_:,. \n ''.'' ''.'' \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n '-. .-' \n '.-,.\"_.' .,\".--.' \n '-,'''._^^:^:.' '' '' .:^\"^^:.'''--' \n ',\"::::\"_:^^^^\"\",,_-.' '.' '.' '.-__,\"\"^^^^:_\"::::\"_' \n ,\"\"\"::::^^^^^\"\":,_\":_:_-.-''..::+\"'' '':+::.-''-.._:_:\"_,:\"\"^^^^^::::\"\"\", \n -:::\"\"\"\"^^++++^:\":\"^^\"^\":^^^^+^+++++' ''..-. '-.''' '+++++^+^^^^\"\"^\"^^^:\":^++++^^\"\"\"\":::, \n ..-_\"^^^+++++^^^^^++^^+^^^^^++^+===+\"\"\"^\"_-'.'' ' ' ''.'-_\"^\"\"\"+===+^++^^^^^+^^+++^^^^+++++^^^\"_-.. \n ',\"^^^^^+++++^^^^^^^++==+^^^++=+++\":^^,--' '-:,,. .,,:-' '.--^^:\"+++=++^^^+=++++^^^^^^+++++^+^^^\"_' \n '\"^++^^^^^++^+\"\"\"\"^\":^^++\"_\":-,\"^^::::-''''''-_:-' '-:_-.'''''-_\"::^^\",-::_\"++^^:\"^\"\"\"\"^^++^^^^^++^^' \n -,\"++^^\"\"\",'-::\":\"_.' '' '.' ''-_,,.' ''''''' ''''''' '.,,_-'' '.' ' '._\":\"::-'-\"\"\"^^++\"_- \n '-\"^:::-' -.'' ''' ''' ''.-' '-:::\"\",' \n ',__::,' ',::__,' \n '.--' '--.' \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n ''-.''.' '.''.-'' \n '' '---,'.'' ''.'-,--' '' \n ''-__,,.',:^:-'.' '.'-:^:,..,,__-.' \n '-:\":::::_:\"\"\":_,+^:_ ' ' _:^+,,:\"\"\":_:::::\":-' \n ',:\"::__\"^^^^^\"::++^,-''''''-_.''.' ' '-''._-'''' '--^++::\"^^^^^\"__::\":,' \n ',_::\"\"\"\"\"^+^+++++++++++^\"\":^^^:::__'.'.' ' ' .'''__:::^^^:\"\"^+++++++++++^+^\"\"\"\"\"\":_,' \n .-_:\"\"^^^+++++++^^^++++^^\"\"+++:--.'..--.'' ''.--..'..-_+++\"\"\"^++++^^^+++++++^^^\"\":_-. \n '-_:^^^+++++^\":\",-.:^_\"\"+++=+==^^^..:^_.''--_'',_'.' '''__''_--''._\":..^^^==+=+++^\"_^:.--\"_\"^+++++^^^:_-' \n ':^^+++++^\"\":-. ' '-'..\"^++\"^--_\":'-\"\"\":,^,''''' ''''',\",:^\"\"-':\"_--^\"^+^\"..'-' ' '-:\"\"^+++++^^:' \n .\"^^^^^:. '''''' '\"^_+: ''.'-,-:\":.'' ' ' ''._\":-,-'.'' _^_^\"' '''''' .:^^^^^\". \n -,\":\"__,-.' '-''. ',:_'''''' ' ' ''''''_\",' '''-' ''-,__\":\"_- \n '-_\"\",__,.' '' '' '.,__-\":_-' \n '...'' ''...' \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n ' \n '''.''''' '''''.''' \n '''' '.-..''' '''..--' '''' \n '.,,,,--.'_^\"::_: ____\"^_'.--,_,,-' \n '.:\"::\":_::\"\"\":^^,''' ' ' ' ' ''',^^:\"\"\"::_:\"::\":-' \n ',_:\"\"\"\"\"^++^++++=+^+:'-^''-_,' ' ' ',_-''\"-'_+^+==+++^++^\"\"\"\"\"::,' \n'._::\"^^^^++++++++=+^++^\"^^^\":,..'''-:_\"' '\"_:-'''..-:\"^^^\"^^+++=++++++++^^^^^::_-'\n ',_::\"^^^^^++^^^^^+^^++^:++::\"\"_-.:^^^, .-,' ',-. -^^^:--_\"\"::++:^++^\"++^^^^++^+^^^\"::_,. \n ',:\"^^+++^^\":_''''''.._+^\"^^++++=++^^::,-,:\":- ' ' -:^:,,,::^^++=++++^^\"^+_..''''''_:\"^^+++^^\":,' \n ':^++++^\"::,. '''''',\"^:+^_:::_^^^^\".'' ''.:^^^^_:::,^+\"^^,.''''' .,:::^++++^:' \n '\"^+++:-'' .::''' '_:^,^^:,-' '-,:^^,^:_' '''::. ''.:+++^\"' \n .::^^:,--.'' '\"^..-_,.'' ''.,_-.'^^' '.--,:^^\":.' \n '-_::::--.' ' ' ' ' '.--_:\":_-' \n ' '.-.'' ''.-.' '' \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n '' '' \n '--.''' '' '' ''.--' \n '.\"^^\"--' '_-'' '.-_' '--:+^^.' \n^\"++^\"^^:'' ''',\"^\"' ' ' '\"^\":'.' '':^^\"^++\"\"\n++=+=+^^+:.''.':=+:'' ' ' '':^=\"'-''.:+^^+=+=++\n++++++\"\"^:' '' ' ' '''' '.-'' ''-.' '''' ' ' '' ':+\"\"^+++++\n^+++++^::' ''.:' '' '' ':.'' '::^+++++^\n\"^^++^^-'' '' - '' '' - '' ''-^^++^^\"\n_++^^^^\",''' '' ' '''-\"^^^^++_\n'^+^^:\"++^-..-.' '.-'.-^++\":^^+^'\n '-^+++^^\".---' '-,-.:^^+++^-' \n '-_,,-''.'' ''.''-,,:-' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n ''' ''' \n ._'.' ' ' '.'_. \n..\"^^_:.''''''--.' '.--''''''-:_^^\"-.\n+^+\"\"::-''' '+^''''' ''''':+' '''-::\":+^+\n==++^::^+- '''' ' ' '''' -^^:_^+++=\n=++++^\"^\"' ' ' ' ' ':^\"^++++=\n+=++++^-- '' '' .-\"++++=+\n^+++++^-' '' '' '-^++^++^\n_^^+^^^+-' ''' ''' '.+^^^+^+_\n.^\":\"^\"++:-'..'' ''..'.:++^^\":\"^.\n '.-\"^+++^\".'.'' ''.'.:^^++^\"-.' \n '.-,-.' ' ' '-,-.' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n ''' '' '' ''' \n ''-' '-'' \n:_-'.' ' ' '.'-_:\n:''':+_'''' ' ' '''',+:''':\n\".'',^+_.'' '._+^,'''\"\n^^,\"+^_'' ''_^+\",^^\n+=+^+^' '^+^+=+\n\"++==^_.'''.'' '.'''.,^==++\"\n:^++=+++_,' ' ' ',,++^==++:\n :^_^^++\":-' ' ' '-_:++^^:^\" \n '''':--\":-' '.:\"--:'''' \n '''' ''''' \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n . .' \n '' ' ' '' \n'.''' '''.'\n' -_-:. ':-,-''\n' '..'.-.' '.-.'..' '\n:::\"^_' ',^\":_:\n+++^:- '' .' .:^+++\n^^++=^-'''' ''''.\"=++^^\n:+++=+++^.' '.^+++==++:\n ,::+^:^^:.' '.:\"^:\"+\":: \n '''.''--' '-,''.''' \n \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n ' ' \n -' '-' \n^\"'' '' '' '':^\n\"\"-- ' ' --\"\"\n++\":' '_\"++\n\"++=+^_.-' '-._^+=++\"\n-\"^+==+^\":_ ,:\"^+==+^^-\n \"^:-_.--' '--._-:^\"' \n '.' '' ''' ''' \n \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n:.' ' ' '':\n::_' '_\":\n+^:.'. .'':^+\n_++++^_--' '--,^++++_\n.::+=+^^\"-.. ..-\"^^+=+\":-\n .+_'.'' ''.'_+. \n '' '. \n \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n.' '.\n=: _=\n:-,.',. '_'',-:\n':,,:--'''''' '''.'',-:__:'\n '.^^^++_.''' '''._++^^^.. \n -'' ''-' \n ' ' \n \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n.' .\n=. ' ' ' '=\n:::'',' ','':::\n'.._:.:,'--',' ','.-',:.::.-'\n '':_++:.''' '''.:+=__'' \n '' '' \n \n \n \n \n \n \n \n \n \n \n ",
" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n "
]
@@ -0,0 +1,41 @@
"use client";
import React from "react";
import { cn } from "@/utils/cn";
import { CoreFlame } from "./core-flame";
interface FlameBackgroundProps {
intensity?: number; // 0-100, like CPU usage
animate?: boolean;
className?: string;
children?: React.ReactNode;
}
export function FlameBackground({
intensity = 0,
animate = false,
className,
children,
}: FlameBackgroundProps) {
// Convert 0-100 to 0-0.3 opacity
const opacity = Math.min((intensity / 100) * 0.3, 0.3);
// Speed increases with intensity
const speed = Math.max(80 - (intensity / 100) * 40, 40);
// Color gets more orange with intensity
const color =
intensity > 80 ? "heat-100" : intensity > 50 ? "heat-40" : "black-alpha-20";
return (
<div className={cn("relative", className)}>
<CoreFlame
className={cn(
"transition-opacity duration-1000",
animate && "animate-pulse",
)}
/>
{children && <div className="relative z-10">{children}</div>}
</div>
);
}
@@ -0,0 +1,50 @@
[
" \n \n \n \n \n . . \n .. ..+ \n .:. \n .. .. .:: \n +.. ..: :. \n .:..::. .. .. \n .--:::. .. ... .:. .. \n .. .:+=-::.:. . ...-.::. .. \n ::.... .:--+::..: ......:+....:. :.. .. \n ....... ::-=:::: ..:-:-...: .--..:: ......... \n .. . . . ..::-:-.. .-+-:::.. ...::::. .: ...::.:.. \n . -... ....: . . .--=+-::. :-=-:.... . .:..:: .:---:::::-::.... \n ..::........::=..... ...:-.. .:-=--+=-:. ..--:..=::.... . .:.. ..:---::::---=:::..:... \n ..........::::.:::::::-::.-.. ...::--==:. ..-::-+==-:... .-::....... ..--:. ..:=+==.---=-+-:::::::-.. \n . .....::......:: ::::-::.---=+-:..::-+==++X=-:. ..:-::-=-== ---.. .:.--::.. .:-==::=--X==-----====--::+:::+... \n ..-....-:..::-::=-=-:-::--===++=-==-----== X+=-:.::-==----+==+XX+=-::.:+--==--::. .:-+X=----+X=-=------===--::-:...:. .... \n ....::::...:-:-==+++=++==+++XX++==++--+-+==++++=-===+=---:-==+X:XXX+=-:-=-==++=-:. .:-=+=- -=X+X+===+---==--==--:..::...+....+ \n ..:::---.::.---=+==XXXXXXXX+XX++==++===--+===:+X+====+=--::--=+XXXXXXX+==++==+XX+=: ::::--=+++X++X+XXXX+=----==++.+=--::+::::+. ::.=... \n .:::-==-------=X+++XXXXXXXXXXX++==++.==-==-:-==+X++==+=-=--=++++X++:X:X+++X+-+X X+=---=-==+=+++XXXXX+XX=+=--=X++XXX==---::-+-::::.:..-..\n",
" \n \n \n \n .. \n . .+. \n \n .: \n : .. :. \n .. ... .. .. \n :...+. . .. :. . \n .=-::... . . ... .. \n .. .--=-::... .....=+.:. . . \n -:.... .:-=:...: .::...... .:. .. . ... \n .. .. . .:: :.:: .:-::.. . .-..:.: ........... \n ..= . .. .::-==.. .-=-::... ..:.. . ..:::.::.:... \n .+.:.:. ..-.:: . . ..:. .:=-==-::. .:--.. .. .. ... .:---:::::--::..... \n .. ..+::.......-::..: . ::--.. ..:::-==-:. ..::.. .:... ..-.. =..:=== ::::-+-=:...+.=.. \n .....= ....:::..:::::- ::=:. ..:=--==+:. .:-::-=-=--:... .:-..... .:-=:...-+==--:+:-+-=-:-:.:+... \n .....:...::.:: ::::-::----=+-::--:---+=XX=-:. ..-=-=::--==X==-:....-.:--::.. .::++-::--+=---:-:---=-=::...-.. \n ....:..:....:=:- ==--=-:--===++====---- -==+==-::--==-:-::--=XX ++=-:::---+===-:. .:-X+ ----=X==----: :=--.--::........... \n .+.:::::-..:-:-===++=++=++++X++=====-.--=X==++==----=--::+:-=+XXXXX++---=-==+++=:. ..:-+++---=+XX+++=-::-===-+=--:...:.......... \n ...::--- ::::--=+=+XXXXXXXX+++=====+===--=---==++==-=+=--::-==++XX+XXX++=+X==+XX+=-::-::--==++X++XXXXXXX=-::-+=++X+=-:::::::::-:=+..... \n ..: :-===----=-=+++++XX+XX-XX++X+==++==+=--:--==.XX+==++.===+++XX.++++XX++=X+=++XX+==--=--===+ +XXXXXXXXX+=--=X++.XX==--=-:---:::::::..:.\n",
" \n \n . \n . \n \n . : \n . .. \n . .. . \n . \n :.. . . \n .::... . \n ==-:: : . \n . . .-.=::. .:: ..+ . .=. \n . :.. ..+:..:-. .:.. .: .+. . .. \n . . .:.:-:. .-=-... ..-. ...:.. .. .. \n . . .. .. :. ..::--:.. .::-. . .: .:::-:....-...:. \n .. ......:: ::. .::+:::: .:=::==:.. ...... ... . . .::----:...::::::.... \n . .:.....:::::=:-. ..::--=-.. :-::--==.::.. . .: .. .::-:..:==--::..:::::.:.....: . \n +.. ...:-+...:.: :=---::..:::::-=+=:.. .::.:::--.++--:.. ..-:..+. .:=-::::==-:::::::-=-:.::... . \n -... .. .. .:---:+:-:-::-=+==--=-::.::-X=--::..::::.:.:--=XX+==--::::.:-+=-:. ..-=-::-::=++-::-::.:---::.. . \n ......... .:::-=+== -==+= =====--=-:::::-+=+=--:.:::..::.:-===XXX+=--::--+==+=-. ..-==-:.:-=XX=----:.:-=-=--:.. .+..... \n ...:::+:...:=:-+++XX++++.=-=-==-- ===-:.:=---=+=-:::--::..:-==++X+X+==--=+==+X+-:.......:-==++==++X:X++=-:::-==+=--:.......::::..+. \n ...:---::+:::--==++-XX++XX+=:=++=--====-:::+--==+=---== ---==++XXX++XX+.+XX===+X+=-:::-:--=====XX+-++XXX+-:--++=+X+--::::.:::::::..... \n ..::.-==---+- ++++.+++XXX-XX++:X+=-=X+==:-::.=+X+XXX+=+X++++XXXXXXXX=XX=- +++-==++X+==-==-=:=++XXXXXXXXXX++==+ ++X X+=---.--.-:.:::.:... \n",
" \n \n . \n : \n . \n . \n . \n \n \n :. . \n :-....:. .- \n ....... .. \n . .. .. :. \n .. ..:.:. .-.. . ...:... \n . .. ..... .:--:.. .::... . . .. ........ .:. . \n .. ..... ...: ..=..::-. .:.:=-:. . ... .. .:.:..:-...-:......... \n . .:: ..::.:.:.. . .:.::-.. ..=.:--==:.. . .::..+==::::+........ . \n .:.......:.:::.....:::..::==:.. :.....::-+X-:::. .-.:... ..-:..::=::-:-...:.:::... \n .. :::.:.-.:::-=---::=::...:--==-:. ..= ....:-++=--=-::.+...==-::. .--:....:=-:-:::.... ::.. \n +.... .::-==+-----------------:::..-==--::. ... ..::-=+:+++=-:::.-- =+=-:. ..=--:::::=++-=::-::.:=-::... .. \n ..:-.... :..:-+++=+=-===-------::--.::..:------:... :....::-=++XX+=--::----+X-:. ..-==-----+X+==-=-:::-=+=-::.. .....:...- \n ...:::::....:-- ==+X+====-----=+::-==-:::::----=-:.::-::::--=+XXXXXX+=-=+=-==++--:-:::.::=--=-==X++++X+=-:=:===++--::......:...... \n ...:----:::--.====+=++X+++======---=+=--:-::=+++X++=-====== ++XXXX.XXX +++-=--=+X+--::--:---==+XX+++XXXX+=--=+=++X+=-:::-::-:::....... \n .. ...:::--==:---++=+=====++ XXXXX++===+X+===+==-+X++++++++X+X++.XXXXXXXXXXXX ++======X+==-=--===+XXXXXXXXXXXX+=+++++X+XX=.--==--::-....:....\n",
" . \n . \n \n \n \n \n \n \n . ..: \n :. . .. \n ::.. \n . .. \n .: :::. :. .. \n .:..:. .:-. ::. . .: .. ..... \n ... . ... . . ... ..::-:. . . ... .:........:. \n .. =.... . ... .. .:.:-+-.. ....-=:....:.=... .. \n ... -..=::... . ......:::-: .::=+-:::+. ::-.. .:..::=:...:.......... \n ....:. ...:....:::=:.:-.. .::=--: ..:==:-::::. . .:---:. .:=:::. :::-.-...:...=. \n ..:.--::::::::::::::--:-:....--:::... ..:-==++---:.=.-::-=+-:. .:::.....--=-:.-.::.--:... \n ....... ..-=---==-=-==-::.-::+:--::..-:---:::....... ..---=+XXX+=-+:::--+X-:.. :--:.::::-++-::::::::==-:..: +.... \n ..:...:....:-=-=+:+=--==::--::-=::--:...:--.:-::....::::---+=+XXX.+=-:-----+=-::..... .::--:---=+++==--:..:-==+-:::.. ......+-. \n ..:-::::..::---=====+=== -:--:--::-==-::-::-==-==----=--::-==+:XXXXXX+==+=--==+=::..:::.::---++.+=+++X+=-:=:===+=--::...:..::...:. \n ....:-------==-=+=====XX+X+==------+=---=:==+=.==+=+++++X+==+XXXXXXXXXXX+++==--==+=-:-:-:---=X XX++XXXXX++===+==+++=------::: :....... \n .....-.:+:--==+======= ++=++X:XXXX+=:==XX==:++=-=++==+X+=+++:+XXXXX:-X.XXXXX+X++==-++++X+======++XXXXX-XXXX--XXX+==+++++X++==.---::::..=:. ..\n",
" \n \n \n \n \n \n \n . \n . . \n :. \n .. . .. \n . .. ::. . . \n . . .:+.. : .. . \n . . . ..-- .. .. ..: \n . .. .. . ...-:... . ...:-.. ... \n . ....-.-. ..:.. .. :: ...-::...... .. .. .:.:::=....:. .. \n -... .....:...:.:-. ...=.:: .:=-::::::. .::--. ....:.:. ..:.. ..: .. \n .::::::.:..+:....:..-=:-:. ..:..::. ..=------::...:..:=-.. ........::-:.. ..::.:. \n . . :::----:::::=::....+:=-:-..-::-::-... . .:-==++++++-:.-::-+-... .::.... .:==::.....:--+:.. . . \n .... .. .:-=-=+==---:-:=:::..::::-:..:-:::::::....=.:..::--=+XXX-=-:-:-:=--::.. :.::..:::-++--::...::==-:.:.. ....+. \n ...-.:.. ...:-==--=+--:-::::..::.:--:..:=:-+-:---::--:::+:-==++X:XX+-==--:--==-::.... ..::::-++==++:-::..:-==-:::.. .....+=. . \n -..:::::::--::--==--++=+==--::::::-=-:: =-=-- --=+=+++=++-==++XXXXX- X+==+-----==::..:-.::--=+.++=++XX++=-:-======:--::.:.-...:... \n ......:=:--=----=- -=-===XX+XX+=----.-+=-:-==-+==-.-=+=====-+ X+XXXXXXX:XXX+========++=------+++XXX+.+X=XXX.XX+======+++---:::-.:.=..:.... \n .......::::-=:===++ ==++=++XXXXXX:++++++=====++=+X++:=++=====++XXXXXXXXXXXXX+X++-===++XXX++=+==+X++XX++X-XXXX-XXX+==+++=+X:===-+-:::=..::....\n",
" \n \n \n \n \n \n \n \n \n . .. \n ...... \n ..::. \n : . . . \n ... : ....+: . . .. \n . .:. .... . .:. .... . ...:. ..-:.. .. \n . . ..- ...=.: ..:. .:-::::..=.. .:-. ...... .:. :.. \n ....::. .. -.......=: :::. ...-.. .- --+::::---.. ..:.. . .. +..::. ...:. \n . .:::::-:....::-........---:-...::..:. ....:--=--===+=:....:-.... .. .. .:-:.. ..::. . \n . .:::--:--::=:::::. ..::-: ..::-....: ..:......:--===+=X=-::.---::-.. ..:....::.==-::.. :---... . .-. \n .. ...::-:-:--::-::-::. ....::-...----:::::.:=:::-:::-====+XX+=--:::=--::. .::::-=== =+=::....:=-:..-. .... \n .+...... :::::-----=.--:-::...=..:=:...:---::.:--==+--=+:--+:+XXXXXXX=-==:+:---:::...:...-:--++===+XX=-::::=----:::...:...... . \n .....:=:- :::-:::-+--++===+=-::-.::-=:::-=+=--:.::-==+-:=++ +XXXXXXXXX++==+=-=+--=--::::::---+++==++XX-X+=+ ==---===--::.+....=.... \n .=....:.::.==--:-==--====XX XXX++=++=== ==--==+X+==---------==+XXXXXXXXXXXX++==== =+==++-=.=-=+++:=++=++XXXXXXXXX+=====++---:-::..:...:.... \n ......:::--=++= ====-===++XX-X XXXXX:X+====-==+XX:+++========+XXXXXXXXXXXXXXX++=++XX +++++=+++X:X+XX.++XXXXX .XX+++=:+++X+=.---::::...:=:...\n",
" \n \n \n \n \n \n \n \n .: . \n . .... \n \n :. ... \n . . ..:. . \n . ..:.. .. .:: .. . \n . .. .:. ..: .:...... .:.. .. :.. .. \n ..-::: .:... .:=:. .. :. ..::=.:: :::=:. . .=. .. . \n ......:.. ..:-.. .. .:-:.. :.... . . .::=-------+-:......:. .. .+.:. .. \n ...::......-:.::. ..:-:....:::.. .: ....:--=--=-=+-:.::::.... .. .::-=-::. :.... \n ..:::.:.::..:..::.. ....--=....=::=:.:...-..:-:.::==--=+++--::---:.... ...::-:--=+:.. .:.:.. . \n .....:: ..:-::--:+:.:-.. .:=..:-..=.--:....::-+-::=-:-.-=++==+XX+=-=-::-::... ... :.::-=-=++X=-:..:::-::.:....... \n ....::.........---====--=-::.:=:.:--=::-=.::....::--::==== XX +-++XX++==.-=-:. :::-..:...:-- =-.==+X++------------:..... .. .. \n .....+.-:::::::-:::+--=X++ ++=====+==---:--=X+=-::.::::--:--=+XXXXXX++X++-====--==:--==--=:-=-=+==+-==+X.XX++++:=----=-::..:.. ....=.. \n . .. ...::+-==------:--==XXXXXXXXXXXX+=------+++X+===--=-----==+XX-XXXXXX:-++==+=++=--==++=++XX++==++==++XXXX:X++++=-===+=--:+::.....=-.... \n ......::--==.+++=+===.=++XXXXXXXXXXXX=++=.=--=+++X+++==+=====+X-X-XXXXXXX X+++++++++++===:==:++XX+XXX++XXXXXXXX.+++++XX+XX+=-=-:::+:::.:.:..\n",
" \n \n \n \n \n \n \n . .. \n . \n .. \n . \n .. .:. ... \n ... ...... .. . \n . .. :. . ..:. .:: :. \n +... . ..:. . . .. .:.:..:=:. . . . \n .. ..: .:: ... . ..::-::::.:.:=-. ... .. ...::... \n .:.... ..+.: . ::. .::.. . ..::.::::==::--:..:.. . . .::-:-:. . \n . ........... .-.. .. .:. .-:...... .. ....::-=-::--=-::.:::. .. ....:+:-==.. .. .. \n :...... .:.-:.+....-....:. ..:: .::-.. ..::-:..=::::--+=--==+==-=-:.:...=. ...::---+=::... :::......... \n .. ...- ......::==----:-::.::=::+:-:.::-:.... ...-:::---=+==+:====+++++=:: . ...:: .-. .=.::--=-+=--::::::=-.:::. \n .......:......:..:--X+==:=+=:--=-=-:::--==+=--:....::-:.::-=XXXX++++X+===-:-::-:::.-:::-:=::-=------==++==-=+==-::+::.. .. .. \n +:......:-- ::-::::--+XXXXX++===--=-=-::-+++++==--::::::::-=+XXXXXXXX++==-==-----::--=++++++===--=---==+X++++====----=-:..... ......... \n .....:::::-=+==-+=+--==+X-XXXXX+=X+==--=---==+XX+==+--------=++X+XXXXX+XX++=-==++==------=++++X+=+++==++XXXX=X++==+===:+== :--:............\n .......:--.-====++.X+=++XXXXXXXXXXXXX+==---.===X+.+X+X++=.++++=XXX.XXXX+XX=X++++ +XX+X+==--====+XX+XXX XXXX XXX X+=+X++++++===-=-: :::=::....\n",
" \n \n \n \n \n \n . \n \n \n . \n .. \n .. ..... .... \n . .. ..: .::. . \n . .: . .... .:-. .. \n . ::. .=.:..::.. .-=. .. .. -.. \n ..: .: :..= .: :-::+:::.:-::-... .. ..:-:-:. \n . . ..:: .. .: .-:.. . ..+..::--:::--:::=:.. . .. ..=::=-. . \n .......::... ..:.=. .. .: .:.......... .-...::-+-:--=--:-:::. .. .=..: :=-.. ..:.. \n . .:.:=::=:..::....::..::: ...:.. ..+::.::.:-:--==:--== ++==:.:...... :... ...:=:-=-::.:.:::-:.-... \n .... .. ...:=+--=--=--:::---:::::::--.... ..:::::::-+X+===--==+===:-:: :...:-:::... ..::::-:-===---:::-- ::::. . \n .......:....::..::-XX+:+++=---------::=++===--:..-::.:.::-=XXXX+.++++=----.::::::+--===---:---:::::==+=======--::+:::.. .. .. \n =.....+::--:::=::+:-=+XXXXXX+=-=::-:-=:--==+X+==--::.:=:::-=+X=XXXX+X+==--=--=- -.::--==XX+==+===---+==+++:-=====--.-=-::.:.. ...-....= \n ....=.::+::---===X==-===XXXXXXXX+=X+---------=++++==++---==+=++XXXXXXXXXXX++====++==-------===+X+==+++++XXXXXX+=+=++====+=-=-=-::..-.::.... \n .... .:.---=:====+X++.+XXXXXXX+.+++++=-- -==++XX++X+XX+++XXXXXXXXXXXX+=+XX.++++=XXXXX+==--=-==++X.XXXXX XX= XXXX++.X+++ += =--=-::::::::.-..\n",
" \n \n \n \n \n \n . \n . \n \n . . \n . . . ..: \n . .:. :: \n .: .+. :: \n .. ... .. . .. ...= .:.:: .. .:. \n .: .. .:.. ....:..::..-.:..:.. ..:::+ \n ..: .. .:. ... .. :-..::..::. . . ...:. . \n .::. :. . : . .. . ...+::--:.:----+.... . . ...:: .. \n ..-:..... .-.....-..::... .::. .:..::..::----.:.:--+-=+:.::. .. .: ...-::... ....:+.. \n . .. ..:=-:--: :-:....:----....:::.. :.:.....:-=++=-:::----:=:.. :...::..-.. . ...::- -+::::::=:.....+ \n ...........:=X=++++==-:..::::=:-+=--:-::.....+..=.:-X.++X+=-===-:--:....:.:: :-=-:-:.-::..::---------.::.:..... .. . \n ......-:::=-:.::-=+XXXXX++--::..::-:==-==+=--:::...:..:-++XXXX++++=--::-:::::.:..::-++=---+---::::--==.==-----::::+:..::. ..... \n ......-::::--==-:-=-=+XX=XX+=--=-:::::::-=:=++=--:=:.:-====+XX:X-XXXX+==----+=.--: ::::====++==-----+=+++++++==------.:----:. . ...... \n ...:..:::::-----==+===++XX XXXX+=+==--:::---=++X+X++==--=++XX X++XXXXXXXXX+++=+XXX++=--:::--==-= ++++ XXXXXXXX:+++X+ ===----=--.:::..::..: \n .....:::--===--==+XX++XXXXXXXXX+++===------==+X+++XXXX:++XXXXXX+++++++=+XXXXX+++XXX-+X+= ----=.=+XXXXXXXXXXXXXX++XXXXX+++=.---==-:--:::-... \n",
" \n \n \n \n . \n . \n \n . . \n . \n : \n . . . \n .. . .: : \n .. . . . .. ... . . \n .. . . ....+ .. ... . . \n .. . . ... . ..::. :...:-. . . \n ... .. .. .:. :.. . .::.. ::.:::.::-. . \n ......... ..: ::..:-. .:. ... .. :-::.....::-.-:.... .: .... .. \n .:--::::..::. .::-:.=....:. ........:.:-=-:...:::::: .:.-....:. . ..... ....+.=...: \n .:.. ..:++=-=X=--:.......::=:---:::+.. ..=.:-==:-==++=:::::::. ....::::-::::.... ..:::=.::-::.... .. \n ...::-: ..:-+X+XX +==--.. ..:-==-----:-:.: .:---++++++++++=-::..:.. .......:====--:::::...::-:--:-::.::+..::..... +.. \n ..+....::--:::.:-=+XXXX++=--:.....::-=---=+--:.:....:-==+++X++=++=-::::::--:::+....-==---=--=:.:::--===+==-:-:::::::::::.. ..... \n :.. ......::+::-=--- ==+XX:XX+=----:-:.:::-==+=++=-:::+:-==+X:X+++X++X++==-==+X++==-:...:-=---=========++X-XXX+=+==----:+:::::......==.. \n ...+.::::---.::-=+XX=+X=:XXXXX+===--=--:=:-- ====X+===-=++=+XX+X++++XXXXX+X.+++++X++==-::=:-+-==++=+++XXXXXXXXX. :XX++++=-:- -==:::::::... \n .....::--=====--=+XXXXXXXXXXXX-+++++X+=---=++++-+XX++++XX++XXXX+++++++=+XX=XXX+XXXXXX++=------==+XXXXXXXXXXXXXXX+XXXXXXX+=---==----::=::... \n",
" \n \n \n \n \n \n \n . \n . \n . \n . . \n . .. \n . . .. .. .. \n : .. ....... . ..:. \n . .-. .. .:. ....:.::. \n . . .. .. .. .. +..:.. ..-::...- . ..= \n .::....:+..: .:. ....... ...........:::::...:.:.. ..- . . ..:... \n . . .-=-:-===--:. .:+::--=.... .:-==---:------::..... .....-.+.:::. . .......... . \n ...... .=.-++==+X++=-.. .:.--:::::.:.. ..-X==-===+==-- ::.... ...-------...... :..:.::::-.+... ..... \n .+..::....:-=++++X++---.. ...--.:-:--:-: .. ..-== +X++====--:..::::........:--:::::::-:.. :::--==++-.::.......... . \n .............::-::: ==+=+XX++--::::....::--==:=--:.=...:-==++:++++====-::-=X+=-=-.-:..+--+:.:-::----=--==++XXX ==-::::::...::.. ...... \n ....::::::-=::=-===-=+:XXXXX+=-=-:::: :..::-=+=+==-.::========++.+XXX:+X++==+==X+==--....:-:---=-=--=+:++X=XXXXXX.+====+::::-::..:+:.... \n .+..::::-==----:+X++++XXXXXX+== ==++=-::--=++X++X+==++X++-++==+++++XXXXXXXX++++X.X++=--::.:::-=++++++=X=XXXXX+XXX+X++XX+--::=--::::::... \n ....:: --=====---=+++XX.X=XXX++=+++X+=====+XX XX+XXXXXXXXXXX++= ++++X+=+XXXXXXXXX+X-XX..=-- =-==XXXXXXXXXXXXXXX+.XXXX-XXX=--------:::::.-. \n",
" \n \n \n \n \n \n \n \n \n \n . . \n . . .. .. \n .: . . .. \n . . .:.. .. .. \n .. . .. .+... . \n .. .::..: .. ... ..............:.+. . .. . \n .:..::::---=. ..:---:. . .=--=-:.-::..=:-:. . .. ..... . . .-. \n . .:==-----+=-:. :--:.=.-..... . .-===::---:-:::::. ..::-:--:. . .:.=.::--.. . \n ..... ..:-=== ===.-:: .::::..:: .:.... ..:==-=+=+=-=-=::.+.:.... .:-:::.. ...+... .:.:::-=++::.. .=.. \n . .. ..-...:..::+====.++=-:+::.... .::::---=::.. ..:::-=+==-+X+===-:.:-+=--::=:: ..::.:. .:..:+::=:::--=+XX+---:...::.... .. \n ..:.+....:--.::-:---=+X++++==-:::::......::==++--:..:==--=-=--==+X==++=---+-=+==--::. .:::: :-::::--+=--=+XXXXX++=---:=-:.:....=.... \n .. ..::::-=-::=++==+XX.XX+X+----==.--::.:-=+X+X+===-=X+===----== XXXX++++==++++++==-:....:::.==-=--==XXX=XXXX-X:++==-++=:::--:::::....= \n ...:.-::-====-.-=++:+XXXXXX+==-==++=----- =XXXXXXXXXXXXX++=---:==+X.XX.+XXXXXX+X+XX++++-:.::--=X++:X =XXXXX++.XX++X++X+++--::--:-::::.:. \n .-..::.-=====-=-=+++XXXXXXXX+:+++XX+=+===+XXXXXXXXXXXXXXX+======++XXX++XX XX.XXXXXX=++X=--====+XXXXXX+XX+X X++++XXXXXX++=--- ---- :::.: .. \n",
" \n \n \n \n \n \n \n \n \n . \n \n .. \n . \n . . .. \n ... : .. ......... .:.:. \n .:..+....-:. .:::-:. .:-:--=:.+.... .. . .:... \n .:==-::::-=:: .:::..... . .:==--:--::.-::.... .-.-:::. ... .::-:. \n . ..--=-------:.. ..::....:::.-. ......==--:--=-:=-+.+....... .:-:....- . .....:-==-:. \n .:......::-==--==--:..........::::=---.. ..:==--==---=-----...:=-+--:......:.... .. ........::--=++=-:.....:.. \n .....-.:::::::::-==+==--:-:.:.:... ...::-++=--:...:=---.-=---+=-==-::+==----.:-::....::..-:....::==::--=+X++=-::::+:-:.... -.. \n .......::-=-:=+=--++X++==-=-::-==::::.:.:-+XX+==---+==--=::::--+X=====--====-=+:--... ....:-=-:-::-=++++++XX++==.----==-::-:...=... \n ..:.::::------====+XX+X.++=---.==-:-::.:-=XXXXXXXX+++=- --:::--+XXXX+++.==++++++==-=-:....--++:===+XXX++XX+++X++==-===--:::::::::.... \n ....::---------==+=+X+++XX+=====+++==-:--=+XXXX+XXXX-++==--::--=+XXXXXXXXXXXXXXXXX+= =-:=:-- +XXXXXXXXXXXXX++=++++++++=--::-- :::....+ \n ....::-=====--=-++++X++++XXXXXXXXXX+++==+XXX:X+XXXXXXXXX=--.-==+XX-+XXXXXXXX+XX+XXX+++++=+++=+XXXXXXXXXXXX+++==+.++XX++=-=--=--- ::..:. . \n",
" \n \n \n \n \n \n \n \n \n . \n \n \n .. .. \n .. . ...:. \n . .:. ...... .:=:-=-:. . . \n .:--::..-.::. ........ .. :-=-::::::.-..... ....:. .....:. \n .::--:=:::::.. ..- ...:.:. .::---:::::::--+.... .... .. .... ::-.. \n .... ...:--:-::-::.::.. .......--::.. ..:=.---:::--:::....:--:-=. . ..... .. ...:..:.:+:--- .. .. \n +...::.=..::-==--:::::::::.... .::=+=-::.....:::--.---=----:..:--::::.:.::...-+...=-.:. ..:==:. ::- ===-::....-:.. \n . .-.:-:--=--:=+X+=--=:::::---:....::-=+X+=--::.--:+:..:+:=+==---:--=--::-::-:... ..-=:::::::-===--=+++==-:::::::::::....... \n .......:: ------==++X+== ---::==-+::.:.:=+XX=X+++==--::....-:-XX+=++++========---:-:. .:-+=---=++++ +=+X+=====-------:::........ \n ... .:::-:---==:===++XX+==---+++X==-:.:-=+XX=X+XXX+==-::.:+:-=XXXXXXXXXXXXXX-++==--:....:--X+XXX-XXXX-XXX+==+====-=---::::::..-=.... \n ..::=.-------=+-===+++XX++==+X.XX+:+---=+XX:XXXXX:++:=-:::-:==+XXXXXXXXXXXX++ X+=====--===:+XXXXXXXXXXXX+==--=++X++.=--::--=:::...... \n .... ::-==--=-=+X+XX+== ++XXXXXXXXXX+++++++X++:XXXXXXX+X+==---=++=XXXXXX:XXX++ ==XXXXXXXXXXX++XXXXXXXXXXXXX++====++XX:+==-= ===--:.....:.:.\n",
" \n \n \n \n \n \n \n \n \n \n \n . . \n . .....: \n . .:-:--::. . \n .::=-... .. .......... .:-::....=. . . . . \n ..:::::....... . ... . :.:-: :.::. .... . . ... . . ... ...... \n .. ..::=:::.......... .-=..:. .:::-:::.::..... .::---:. :. .:.+.:....:::. \n .-....::-=--::.:....:.:. .-.---==-.. ...:..:::::=-:.::..:--::..... =.. ...=-. ..::.....::--+-:... .. \n ..:---::--=X=--:.:.:.:-:-:.....:--=+X=::..=:::.=...:-+=---------::-::..::.......:-:....=.:----::-==-=--:......:::.. . \n .-..:--:::-=--++=-:+::::---=::...:--=+XX+=++=--::.....::+X+++=+X+=====-.-:::::. .:--:::-==-+-=:==+.+---:.::.::::.....- .. \n .......::-::------=+++==-.--=-+X+==:..:-=+XXXX++X==-::..:::-+XXX-XX+XXXX+===+-=-:.. ..:::=X++.XX++++++++++--=--::::::-:-:. ...:... \n ..:::::--::-+==-==-=+++=====++XX=++::.:-++XXX.++++==-:...:--=+XXXXXX X+XX===X+==-+--:--:-=+XXXXXXXXXX+++= ----+==--=-:::--:........ \n ..:---=---==X+++=-===+XX++==+XXX===.===+++ +XX++====+--::--=+XXXXXXXXXX++.==+X-XXX+++===-=XXXXXXXXXXXXX+=----=+X++=-----==-::........ \n ....=:--======+XX+X+=----=+.XX+.XXXXX+=+=++X+=+XX XX+==--=---=+XXXX-X=XXXX++===.=+XXXXX.X.++++XXXXXXXXXXXXX-+++=++X XX++==++===-:..........\n",
" \n \n \n \n \n \n \n \n \n \n \n .. \n :::::.. \n .... . -=...... .::..:...+ \n ..::.... .. .: .. .--:::.+... .. .. . .. . \n . ...:..-... .. .. .=.. .. .:=:::::.:. ... .:::.. .: .. . .... \n . ..:--::...:...::..: .:-:..-:... ....:::::-:....+..:-:.::. .=. .. .:....::::. \n ..:.:..::-=--:..+....::::. ...: --+-:.-. .......::+-::::: ---::::... ... ..:::. . ..::::.. ::--- -.. .. \n ..::-:::---+=-:..::..:::-:....::--=++=-:-:-:::-....:-+=-=+==++=-.-+::..:::..= ..::.....:::-:-=-----=-:......+:...= . \n .-.:::::::--.+===--:::-=:.==-:.+.::-+XX+++++-- :.. ..:-XX++++XX++++==-=-::.. ..=:=+--:++=:=-=====:=-:::...=.::......+ .. \n ......::-:--=----=++==---==-=+XX=-:..:-=+XXX+++:==--:...:--+XX:XXX+XX++=-==+-+-:=...::: +X++XX++=+++ +=+=----=:.::::-::-..... .. \n .:::::-::--+=--=---+X+===-=-=+XX++=:::-=+XXX.+++===-:-..:-=+X:X:XXXXX XX===XX-++=--+-::-=+XX++:XXXXXX++=-::--====---:::--:...+ ... \n ..:-----:-==X++==-.--+XX++==+X=X++==+==+XXXXX+++==----: :-=+XXXXXXXXXX+=====+XXXXX+++=---+XXXXXXXXXXXXX+=----=+X++==--=-=--:.+..=.... \n .+.::--==-==++XXX+=----+=+XXXXXXXXX++==+++++=XXX=XXX==------=+XXXXXXX XX.+===== +X-XXX+XX===+XXX=XX=XXX=XX+.++++XXXXX:+++++==-:.... .....-\n",
" \n \n \n \n \n \n \n \n \n \n \n . ...... \n . .. .. ...=::.. \n .+.. . .:. . .:.. .=... : \n ...:.-.. .: . .:. =.. ..:.....:. . . . \n ...:::..:... ..... .:.....-: ........-+:... ..:.:::. .: .... \n ......-:--::. +.. ... . ..:::::=:...... -..:==:.:::..:=-:::. . .. ... ......: ::... . \n ..:...:-=--::...:.:+:..-::..::.---==-=-:.:.:.. ..:==----:-=+==--::.. ... .::. ...+:::---=:::-:.. .... \n ...:::=::-=--:--=::--::-==::..:.-+++===+=-::.. ..:-X++++++++++=--+-:.. ...:-:::-==------==----::.-... ..... \n .. -..::-:: ::-=------------X=-:....-+XX++====:--: .:=X =XXXXXX+==--=+-::::..:....-+====+===-= ===---::-::....+.....= \n ..+:..::::-=--::::-++==--:::--+X+=-:..:-=+XX+=------:. ..-=+XXXXXXXX+=+=--=X==:=-=::..::+++==+++++++++=--:::==---::::.::.... .. \n .:::::::--=== -:-:-=X:X==::-=++++=-=====+-XX++==-::-:..+:-=+.X+XXXXX+=-=--+XXX-X+==-:::-=XXXXXXXXXXX++=-=:::-++===-:::--::.. ..+ \n ..:---===-==+++=-.:::-++XX+==+XX++==-====+XXXXX+++=---::::-=+XXXXXXXX++=-:-==++XX+++=+=--=X:XXXXXXXX=X+++=-=-=+XXXXX+===--+:... ...= \n ...::.--+==++X+ +++=--.:=++XXXXXXXX+.+==++====++XXXXX+==+=-==+X:X-XXXXXXX+==--+==+XX.+=-=+==++XXX ++XXX-XXX: XX X-XXXXXX+ +=--+:...:......=\n",
" \n \n \n \n \n \n \n \n \n \n . \n . .. . ........ \n .. . . .. ..:.. :. \n .... . .. .. +...-=.. .. \n ....... .. . .. .-. .. ....--:.. . ..: :... \n .. . .:::.. . : .+...::. ..:-:.........::-........:-=-::-.. . .. -... :..... \n ..=...--::-=.....=+....-:..:.....:----:.:... ..:=-:::::--=-==--:. ... ......:.:--....... \n ...:...:--=:..:::-...:::+-...:.:--=-----:::. ..:+X+==++ ++== -=-:... ...:-..---:--:.:---:::=:.. . \n ...-::..::-=:+::=::::::=-+-:....-+X+===----:.. .-+ XXXXX++X==-:-=-::..... ..-=---=---::::---- :=::.:.. ..... \n . ......:::--:.::.-+=--+:..:::-=+--::.:-=X++==----:::...:-=+XXXXX+++=-=-:-==---:.+. .:=+X=:===-.-===--::: :-:::-:........ \n ...:::.::-=---::::-=X+=--::::-==+=--==--+XX++=--=:::....:-=+XXXXX+=+---:--XX-XX+=-::..:-=++++++--XX+=--:..-:-==---:-::-:=.. . \n ..::----:-=-== -::.:=+=++=---===+= --===+XX+++===X---:..:-=+XXXXXX+==--::--=+X++==-=-::-=+XX+XX+X XXX+==-:::-=X++++=----:::.. .+. \n ...:: -----=====+=-::::-=++X++++XXX+=--==+=+:++XX:+++ ==-:--=+=XX=XXXX++==-:-==+X++==--==--+XXXX+=++XXX XX+++++XXXX ++====-.--... +.... \n ...-.:: ----:===.+==X+=-===++X.XXXXX++X+XX+=-==-==+XXXX++====+XX=X XXXX+XXX+======XXX++=-=-===++XXXX++X XXXX+XXX=XXXXX ++++===-:--:..........\n",
" \n \n \n \n \n \n \n \n . \n . \n . ..:: \n . .. ::. . \n . . . .. .: ::. \n +... . .:.. .: .:::. .. ::...:.. . \n ::.. :.... ..::.:.. ..:-::.. .:: --:.:. . . \n =....-::.... . :-::. ..::.::-.::. .:--::.:-::-----+-.. .. .. .....:. . \n .... ::-.:....:.. . ..:=:.:..::::::..::-.. ..=+=--=+X+--=--==:... . ...... :::.. ... ::..... \n .-... ..:-=:...:::......--:=..:=++=--:::::. ..=XX++XX++===:----::.. ..-::.--:........::::-:.. . \n .....-::-...:-+:::::..-...:--::-::==++=--:.::......:-=+XX-++==-=-:::-+-::-:.:. ..:+X+==---:-::-::::.:::............ \n .. .:..---:::...:=+--::...:.:-=-::-==-=+++==-:-=::....:-=XX:X++=-=-::-:=X+=++=-:....:-=========++=-:. ...:-:::::::.:: .. \n .::-:.:::--=--::..:-=+=--:+:----=-=::-+XX.X+==---=-- ..:-+XXXXX++=--:..:--+X+==----:..:-+:+++++++=X+---:..::=X====-::.:::. \n ..:::-:::- ---+=:...:--=++=+=.==++=-:---++X+X+===-=--=:::-+XXX==XXX+=-- ::--=+:+==-:-=:-=+XXXX+=++++XX++== ==++.X+=----:::::. \n ..+...::-----------+=::--=+X+++++==:++====----===X+=+=+=+==-=XXXXXXXXXXXX+===+==+XX+==-:-=-=+++XXX+=:++XXXXXX+X-X+++++=+=---:::+:......... \n .....::::-----------=++==+:XXXX-X.XXXX+X+=-::-:--==XX+X+XX+ ++XX+:+XXXXX-X:+++++.+XXXX=--=.====+XXXX++XXXXXXXXXXXXXX+XX++==--::::--...::.....\n",
" \n \n \n \n \n \n \n . \n .: \n . :. \n . . .. . \n . . .. \n .. ... .. .:. .::- \n :.. .:. ......: .::. ...::-:. \n ::.. .. .-. . . ..... ..:::.. ---:: =---:. .. \n . ..:::. . . ..:-.. .:..:........ .:----=+-+=----::-:. . ..... . ..:. \n .. ....::-:. ..... ..::-:.-====-:.:.... .:+X+=:++=-----:--:.. .:.:.::.... .. .:.. \n .. :.. ....--:..: .. .::..:-=--===::..::: .=.:==X+X+==--- -:.:-:...-: .:-++==--:.:..::........ . . .. \n ..::-::...:.:--:.....:. ..: ::.:-=.===-=-::--:.=.:-=+XXXX+=- -+:::-+=--+-::. .:--=-=-----=+=-:.. .=.::..- .....+. \n ...::....::--:+....:--:::::.::+::::..:-+XX+=-:-::--::..:+XXXXX=+=--::..:-=++==-::.:...:-=+=+=====++=::.....:==-:-::....... \n ....:..::::::--:....:-=---===:--==-:+::=++X++-:-:-:=--::=XXXXX++++=-:::=-:-=++=--::-=::=+XX+X+=-==+X+==-----=++==-:::::..... \n ....-::::::::::::=-..:-==X++=====-==---.::-=+=X+--- -==--:-+ ++XX+X+X+=---==-=+XX==-::-=--=++XXX+==+=+XXXXXX++++==+=--::::...:. ... \n ...-..::::----:::-:-=---=+XX-XX++++====+-:..: ---++=-+=+====+X++=++XXXXXX+===+++X=++=-::--=-==-+X++=+-+XXXXXXXXX++++ +=+=-::.:..:.-...-... \n -.....::----==-------=== +XXXXXXXXXX++-++-::.::====XX++++==+++XX ++++XX X XXXXXX++++++=--==+==-+++XXXXXXXXXXXXXXX++-XXX+++=--:::: :.:-::::.:.\n",
" \n \n \n \n \n \n \n : \n . \n \n . . ... .. \n . ... . :. ::. \n .. .. . .... .::.:..:-.:.. \n .:... .:. . . +..=::===--::-:.. \n ..::. .::......... . . ..:--:--X+=-::::- .. \n .. ..:. . .-...:===--:.... .: ..-XX+====:-::::::.. ... :...:. \n .:.:. .::. .. . .....-==:- -:...:.:....:-+X++++=----::..-:...:.. .-=+=--:..::::... .. . \n .:::... .::. .. .. ::....--+=--::: ..-:...:=+:++X++=-+:::::-=-::.-... .---=----::-+=::.. .... .. \n .. ...::.. :: .=.::...........:=+X+=-:.:..--:::-+XXXX+===-::..:--+X=--::... ..:-+==+=---==--::.....:+:....+ .... \n ...... ..:.:.::. .:----::-=:.:=:::::.-=+X=-:. .::::..:-+=XXX+==-::.=.--:-++=--::.::.:-=++ X=----=+=--:::+-==-:::.+.. . \n ..............:-....:--=++=---==---:::::--=X=-:.:::---:::-X+++XX+==-::::==-=++==-:.:- --==+X++=---==XX++XX+==-==--=:::... . \n .-....:-:-:-::+::::-::--=+XX++=======-=::..:---+=--:==-----.+X+==+XX+X+=--===.XX:==::..:-=-===X+==-= =+XXXXXXXX+==+==+--:........ .... \n .... ::: . ------:=::--=+XXXXX-X++X==---:....:-=+++=-==-----++X+==++XXXXXXXXXXX++++=-::.:-=--==+XX+=++++XXXXXXXX++XX+:++=--:.:......:::.... \n ......::--======.----==+XXXX-XXXXX-====--:::::===+XX ++====+++X++XX++-+X=XX+XXX+:++X+=- -=.====+XX:XX+XXXXXXXXXX++++.XXX+==--::::::::::::.+.\n",
" \n \n \n \n \n . \n . \n . \n \n .. ..+ \n . . . .::.::..:: \n . . :. ..:--=-:::.. \n .. . . ..-..:--=-:::::. \n . .. .....-. . .-::+::--=:::.:.. \n . .. .:-=--:...... .:...-=XX+=-==-:.:+:.. .. :+...... \n .. .. . ..:-:==-:::. .. :..:-=++======-:.:. ....: :--=-::.-..::. . \n .... :. ..= .....:--==--:... ..:..::-+++=====-...:::--:.. .-:=--:-=-:--:::.. \n .... ::....+:. .. ...:-+==-..... ..:..:-++++==--:...:--=X+=-::. .. ..--=---=----.:::. -....:.. \n . .. ..::==::.::=........:=++=-... .:....-+XXX+=--:....:---== =-:....:..:-+===-::-:--.:.:::::-=::+.. \n ......... .:. ..::-===-:::-=--:..:.--++--:..:.::::.:=+X++X+---:...:=--===:--..::--:-=++=---::-=X=-=++======--:..:. \n .......::.:.::...::..:-===-++=-:===---:..:.:-=+-::.--:.:::-=++==+X===--::-==++X+=-....:----=.++-::.-==XX+X++XX+=====-::....: \n ....::::::-:::-:.::--+XXXXXX++==+=--:-...:.::-=X=-::-:::::-==+===++XX++==X++X+-++-:..=.------++=======+XX.XXXX++++==--=::.:. ....:..... \n ...:.:::-==- -=.::--=+XXXXXXXX+++:---:. .::=-:-+X+=-----:=.====++++XXXX+++.X++==++=:::-= --==+XXXX.+++XXX:XXX-XX++++=++=-:- .....::::.... \n ......::--==+.==+=--=++XXXXXXXXXXX==---::: ==X+===+XX++==+++.==++++XX+=+.XX++:+==++XX+=-=+=.===++XXXXX-X.XX=XX++==-++X+XX+==.-::: ::-:::=...\n",
" \n \n \n \n \n \n \n \n . .. ..- .... \n .::-:::. . \n . :..::-:.. \n .. .......::.::... \n .... .. ..::==--::-:.... \n .:---.-.. ...-=+:+==-::::..+.. .. .. \n . :::=-::... .=.---+==----+:..:. .. ::=-:....::.. . \n . . . . .:--=--:: .-..--=+===---:..::.::.. :::=-:.::-::.... \n . ....= . . ..-=--:. . .:-=X+==--:.. .:-=+++=-.. .:=:--:.:::::.. . . \n ..:--:=.....=. .-.== -:.. -... .-=+XX=--::. :-+===--:.. .-. ..-==--::.:=:...-.::::-::.. \n . . ...::-----.. :--. +..:====-..:..:.+..:-==+X=-:::..=.:=--==--:...::-:: +===-::..:--:: =---==--:.-.. \n ........... .=...::---==--:::=-:-:.....:-+=-:..::.....:==++.+=---::.:==-++X=-:. ..-::-:=++=-=:::-++======X= =--::..... \n +:........::::..-.:=XXX+XXX=--::--:=:. .=..:-++--..::...:---====+==+--:=:=+ +++=:. .-----=X+==----==X++++=+++==--:::::... ..... \n .=.:=::::=-:--::.:-=+XXXXX=+==--:::.. .:.:--=+X=-::::.------==+XXXX++==+ ====++-:...-=:-=--=XX++====+XXXX+++X+++==-==-:::.. ....::.... \n ....::.--==--=-----=+XXXXX-X++=+--:::::..--=X+-=+X+====--=--===++XXXXXX++=-++= =++=: :-----==++XX+=++XXXXXXX++ ++=+++++=-- ::...:::::.=. \n ......::-===+=+++===++XXXXXXXXX+++===-:::.-=+XX+==+XXX===:+X==+XX+XXXXX+XX++======++X+=---== ++-=+XXXXXXXXXXXX++==+.+XXXX+=+==--::--::::....\n",
" \n \n \n \n \n \n \n . .... . \n ..:--::.. \n ....::.. \n .. . ..-:... \n ... ..:--:--:.::: . \n .::::.. . ...:---+=--:..... . . \n .::-+-.... ...==++=----::.... .--:.. . .. \n .-::---:.. ..:--==.---::..::=..... .=.--:..=.::=.. \n .. . .::--::. . .:-=+==--::=.. ::--:-::. .:::-:....::.. \n ..::. .. . .:-=--... .:-=X+=--::. .:-:++=--.. . .::--:::..::. ...=... \n ..:::-:.:..::-. ..:+=--:.-. .. .-==X+=-::.. .::====--:... ::.::==--::..:::....::::-=-:.. \n .. ........::-=---:.:-:::. .:-=+--:..:+.....: ==+X=-:: ....=--==+=-:=....::--+==-::..::=--:-::-=+--::.... \n .....+.::... ..:-== =-==--:..:::-:. ...:=+-::..... :-=-=.=+=--+.::-=--++X=-:. .:----+X=-:+:---++====.++==--:..:::.. . \n ........:-:::...:-+X XXXXX+--: :::.. .-.::-=X=-:..:..---:--==+X++.=----:== ++-:. .-:-:--+X++=---=++++++==++++--: :-::.. ...... \n .=..:::-:------..:-+XXXXXXX+==-::.... ..:-=+=++==---::.--:-=:=+.XXX++==-===--++-:..:-::----=+X+==-==XXXXX+=++ ++==--==::::. ...+:..... \n ....::-:-== ==-=--==+XXXXXX-+++=--::.:..:--+X+.==++=======- -=+++XXXXXXX+=======++=-::-:--.==++.X+=++XXXXXX++=+++=X+-++=-=--::..:::::... \n .....::-=-=+=++++++++XXXXXXXX++X+ +=--::--=+XX+==+XX+===.=+=++XX.++++XXXXX+====:==+X+= --==-+X==++XXXXXXXXXX++==.++:XXXX++===--.:--::::-.. \n",
" \n \n \n \n \n ... \n ......::.. \n .:..= \n .. \n . ...... .. \n ....=..--:+:. . .. \n .:--:.. ..-=--=-:.... . . \n ..::--:. .---==--:: .. .: .:-::.. \n ..:--.: ..---=-=--::....-=.... . .:::....:.. \n .:--::. .:-===-:::=.. ..:-::-:.. ..:::- ..... \n ..- . . ..==-:... ..-=+=-:-... ..==++-::. . ::-::..-... ..... \n .:::: .. .:+=-::. .. .-=++=-:... .:---=-::+......:.:X--:..:.....= ....:--::. \n .........-::::..:+::.. ..-==::.-.. .:===++=- ... :-:--==-::... .::-+==::...::-:..-..:----:.. \n .:.=. ..:--+=-----:::....:. .::=+-.=. ::::--+=-:: ..:-::-==+-:. :.::-++= --::-+X=-=----=---:.. .... \n .-... .:::: ..:=+XX++==+=:.:..... ..:-++::::.-. .:::--=XX==-::::.:--+X-:. .:.: :-=X=--:::-=+= ==--==+--:.-..::.. ...=. \n ........::=-::..:-++XX++-+=--:.-. .:-++++-:.:--..::::--=+XXX+=--::-+--==-:. .::.::: :-=+--::-=+X+-+==:==+=-::::-::... . .... \n .+..::::-:---:-:::-+ XXXX++==+-::...=. .::-+X ===-::::-------==++X.XX+==--=---=+=:..:..::::--==+=--+XXXXX+=====++=-==----::...:.::.:. \n .:.::--=====++====XXXXXXX+==+==+=-:::::-==++==++==--=::--==++==+XXX-XX++.======X+-::.::=-=++==++++XXXXXXX+==-=+=++ ++==----::::::::.=. \n .. ..::-===+==++XX+XXXXXXXXX++=++X+X+=-=== +=++=++XX:==---=+++++++=+++XXXX++=====-+XX==-===-+X==++XXXXXXXXXX+=-==++X+X+-+===+=-----::.... \n",
" \n \n \n .:. \n \n ...=. \n \n \n .:.... \n .-..::... \n .:---::. .:-=--:=.... .. . \n .:=::. ::.:=-- ::.-. ..::-:.. \n ..-:.. .-::=--:.... ..:... ..-... . \n ..=::. .::---::.... :--::.. ...-:... . \n .. ..+-:.. .-=--::. :--+-:... .::+:...... ... \n :..:.-. ..==:.. .. :-++==-::. .---:=-:.=. .::+-:..=. .:::... \n .:::..:.... ... .-==-. . .::-=+=--:. .:: :-=--... ..::=+--::...:........:::-:. \n .......::::+-:.::.... . ::==:..: .::--=X=-::...::.::-==-:. ...:-=+=---::-+==-:::::--::.. ... \n . ..-:.=.:-++++=--+-:-.. .:-+-:...... ...::-=X+=--:...:.:=+=-:. ...:.::==-:::+-==:----::--=-:. ...... .. \n . . ...:: =...-==+X++===-:-.... .:=++=:...::. ..:---=+XX+=-::..::--==:. .:... ...:=-::.::=+=====--==+-:.....:+.. ..+.. \n .......::::::..:-=+XX+=.=-==:::... ..:-==+=-:.....:..:---==++XXX=-:---::-+=::. . ..=:+:-.-::.=+XXXX====-=+=-:-:::::.. ...... \n ..:::------=--- =+X:X++==-== =++=:.:.:::--=:===-::....:+:-= ==++XXXX+==--===.X+-:.. ...:-=---=--=+XXX.XX+=--====+=+=----:.......... \n . .=.:------==+X++++XXXX++.==-==+XX+-+-=-===-=.=+++==::::-=X++++=++XXXXXX+=--==-=+X=-::--:-=X===+=++XXXXX=X+=-=+=+XX+=====-=--:::::=... \n . . ...:--======+++XXXXXXXXXX++++++XXX++===:+++==+XXX.++=---=+XXXXX+-+++XXXXX+=======+X+====++=+.++XXXXXXXXXXXX==+++XXXX++ ======---:.::..- .\n",
" . \n .. \n \n \n \n \n . \n .... \n . ..--....... . \n ..:==+::. .::--+.. . . .. \n .+.-.+. ...:--.:. .. =..-:... \n ..-:. .:-:---.. . . . .:... \n :+-. ..:--+... .:-::.. ..:-:. \n .==:.. ..-+--:... .::-=-.. .:=-.. \n .:-.... .-+-:. ..:++--::.:. .::----:. ..==:::. ....: \n .::...... .--=:. .:-=+--::.. .....::--:.. ..-+-::.. .. .. ...+::.. \n .. ...:-::.... . .:-:.. . .. ..:-=XX-::. ......:=--.. ..:-=:-:..:==-:::.=.-:::.. \n +..:..:-=:=+--:::.... .:==:... .. ...--=X+--:.. ..:==--:. ..-:..::--=-- ::-:-::-:. . \n .......:--=+==-:-:::... .:--==-.. . ..:--=+XX+=-::...:-=+-:. .. . ..:-::..:--==-==::.:=-:... ..=. \n .........:....::=:=X+=-----:--:.:. ..:-=.=:.-. -...:--==+XXX=-::--::+=+-:. .:::+:...:-=X++X=----==-:::::..-. ..+. \n .+..:::::-::::.:-=+X+==-:----+==+=:+..::=--==---::. ..:-=:--==:++XX-=-:--==-=X=:. .:--::-:::-=+=XXX++=--=======- :::....=:... \n .. :--:-:--==+++=+X+X+==-- :--=+XX=-:---=--= ====--...::-+==+XX++XXX:X=--::- -=+=:..:-:::-==--=-==+XXX:++=+-===+XX==.-----::::=...: \n . ..+:----=--==+++=XXXXX+==++===-+++=---=+==-+XXXXX+=-::--==+ XX+XXXXXXX++=---=--=++-::--==-=X=:+++XXXXXXXX++-== +X-X++=---- -:::+::.. \n . ..:::--=++= =+=+=XX+XXX:X++XX++++++==++X++==+XXXX-X++=-==++XXXX:X X-XXXXXX+=+===+-++==+-===+X+X:XXXXXXXX.X+===+XX++XXX+==.--=--:::+:+....\n",
" \n \n \n \n \n \n . \n .-:. .:----:.. \n .:--:=.. ...::=-:.. .:. \n . ::.. .:--::. ..... \n .-=:. ..:--::. . ..::. \n :=:.. ..==::. .:--::. .:-:.. \n ... .-=:. :..++-:.. . .:.:-::. .-:.. \n ... :=:. ..=+-::... . ...::--: .--:+.. .. \n ...=... .::. ..--+==-.. .:. ..::-::. :-::.. . . .... \n .. ::-.. . .::. .:-=++-:.. ..==::. .::... :--::.....::.-.. \n . ...+:::--:::..= ..---.. ..:-=++-::. ..==-:.. ..::. .=::-:::::-.. ::.. \n . . ..::::----.::::.-. . .- --:. ..:--=++=-:....+.-=+-.. ...:. ..::-----+-:.:-:: .... \n -...... ..:---==-::.::-:::.::. +..:==--:.. .:=::--=+++X--:::::::=+-.. .:..:.-...:=+===+=-:.--:::...... \n ...::..:.:: ---=++=-::.::-++--:==:..::-:-+--:-.. ..:-::--++++ +=:..::--:=+-.. . ..:-..:::::-+X=++=--::-==-==-:=........=:. \n ..:::::::--:-=-++X+=--:::::--=====-::-= --=--=-:. ..:-=---=+.++XX+=::...-::-=:. ..:::.:-::=::--=+XX+====-=--=++=-+::::.. ....- \n ..:=------:-=-=+X+X+=--=-----=.=--:--X--=+X+++==-:..::-==++XXXXXXXX+=-::::-:--=-.. .:-:--+--=-=++X+XX+++==---=++==---=:::=:::...+ \n +..::--=---===+==+++++++=-++========-=XX+==+XXXXXX+=:::--=+XXXXXXXXXXXXX+-----====-::-:-==+XXX-XXXXX.XX X+=---==+++-=------:::::..=. \n ..: :..:::-==.=--=+X++++XX+-XXXXXX+:==++++XX+++=+XXXXXXXX=--=+XX-XXXX XXX.XX+++ ++==+XX++==-=-=+ X-XXXX+XXXXXX+==.++=++XXX=----=--::=:::: ...\n",
" \n \n \n \n \n .. ...... . \n .::.. ..::=+-::. \n .--... .. ...-:.. ..- \n ..:-. ..-:.+. .:. \n .-..- ..=-:. .. ..: \n -:. ..+-:.. .:---::. .:.. \n ..- ::. ..-+:::. .:=::. :... \n .-. ..-+=--:. . .+.-:.. ::.. \n . .. .:. .--====:.. ..=-:. .:+.. ... \n ...... ..-. .:-==-: : .+=:.. ... ::- .....:.... . \n ... ..... . ..:::. .:.:-===-:. ...-+-:. .. .:.:-:....:...:.. \n .......::........ .:=-:: .::.::==++=:=..:..--=-. ... . .::-:: :--:.:-.:. .. \n . . ..::::---:. ..:.:....--:.....::--:--: ..:+-:-:-==++=::...:::-=:. . ........:-+-:-=-:::::...=.. \n -...-......-::--+=-:....:---.-=--=:+:-=--:-::.. . .:-::::=====+-:. .: :--. .. . . ....+..-++===--::::--::-:..+. \n .-.::...:...::-=+=-::.-.:.:---.=--::-+-:---:-:.. . .:-=--== == ==:.. ..:--. ..:..:......:-+++===--::::-=-:-:... . :.. \n .::::::::.::--==+=--:::+ :::-----::-++==++=---:......:-+X++X+++X++=-:......--:. ..::=-:.:--=++++==--=: :-==---:::......... \n ..:::-::+:--=--====+===+==--------:-=X++ XX+++X++=-:..:-=+ XXXXXXX X++-:::: -.--:..:.:-=+X+++ +=+XX+X++==:::--= ==--.:::::..... . \n ....::---.::-++===.+XX:XXXX+++--=====+++==++.XXXX++==-::-==+XXXXXXXXXXXX+=.==-==+==+--:--=+XXXXX-XX.XXXX=+=--==-==++=-:.:--:: ..:.:... \n .+...::-:--===--=++=+++++XXXXXXXXX+===+X+++++=+=+XX=XX++====++XXXXXXXXXXXX=++==+=+XX-XX++=-:==++XXXXXXXXXXXXXX+==+X=++XX+=-:-=-:::: :::=..:.\n",
" \n \n \n \n ... ..+-:.... \n ::. ...::-::.. \n .::. . ..-... . \n .. .-=:: .. \n : .:=-:.. ... .. \n :. .-+:::. .::-=::. . \n . .:X-::-. .. ...:... . \n .. .:=--:-.. ..-:. . \n . ::=-=--.. .+-: . . \n .... ...:---=-:.. . .-=:.. . ..::. .. \n . ..:... .-:..:--==-.. ..+.:+-:. ... ...::.. ..... \n ..... . .:::=-:. .::...-=-==:. ..::-=:. ... .:.::.:..:...:. \n ....--:..... .. ..-:.. :=::.::. . :--:.:----=-:. ...-:. . . . ..-::.:.::..::-.. . \n .. ..::-::. ........-:-=::==::..:..: .:--::--=----.. ..::. .. . .-=----::....::.::.. \n .... .. .:::-=-:.. ...::-:-=-:-::=-:=::.. .:-=---=====-. . .-.. . .. ..-==---:.:-....::::... \n ...........::--=-:::..-::.:-::--:.:-+==--:..-... .:-++XX++++==-:. . .::.. ..:.=. ...:-=++- -::::..:--:-:=.. . . . \n ....::.:..:-::--=:=----:+:-=--:-:.:-++XXX+=-=+=---:...:-=+XXXX=XX+==:....+:::::. ....:=+==-::--==+XX==--:..::----::. ......=.. \n ....:-:::::+=----==++X++====+-:----.==+=+++X++=+==--=:.:--=+X=XXX=XXX++-:+::--=-:--:..:-+XXX+:++==+XX XX=-:-.----+=-::..::......... \n .. .. :::----:::-=--= =+XXXXXXXXX+=--===+-====++XX++=+=-----==+++XXXXXXXX+++==+=XXXX++=-::--=+X+X++X++XXXXXXX=-==+===++=-::::::::...::.... \n ......::-===+=-=-====++-+XXXXXXX..++==+X+======+XXX-+=+=--=+X++++XXXXXXXX++======XXXXXX++-===+++=XXXXXX=XXXXX++==-++XX+XX+= ---::::::::::. .\n",
" \n \n .. :+:. \n ... .--::. . \n . ..--:. \n .. .--:. \n .. .:=:+.. \n . .--...:. .-:.. \n :=:..:... .::-:. . \n .=:. .. . :-:. \n .::::::. .:=:. \n .. ::-::::--: .. .--.. .. \n .. .-. .:..::+:--.. . .:+:. . ..... \n . .:...: .. :.:=::: ..:=:. . .... . .. \n .. .. .--::.:. :.:. ::.:::.. .::. ..:.. ..... \n .:...+ ::...:=-.....:. ..-=..::-:=::. ..: .:-....... .-... \n . ..:.. ..:-:::..-:...=.. .:--:::::=-:. :. .-=::::... .:... .. \n .. ....:::. .+.:+:::-.:-::-:. .. .:-=--:::--.. ... .. .-=-:-:.... ........ \n ..... ..:..::---:+.. ..:==:::- ..:==-::.. .-.:. ..:-=+=====--:. . ..:.. ..:. :--++-::.... .::..- \n ..:..- :...::---=-::...:-+:::-:..:-++X++-::--:-::. ..-+XXXXXXXX+=-. . ....::. .:--=-:..::--=++-:::....:-:-:.. . \n :..::......:.::-=X++==--:-++-::-::---=+++X==-----:-:..:-++X=XXXXX:+=-.-..:::::::.....=+XX+=---=--=+X++=-:: :-:-=-:.. .. ..+. \n ..+::-::..:::=:--=+XXXXX+=+X=-:--:=:---==+XX+===--::-----==XXXXXXXXX+=-+-=+X++++--:..:=-++X+=====.=+XXXX+=---=--==-:....: ........ \n .. ..:=---==::-:--====+XXXXXXXX+==-=- ==+---=+XX.+==--:=--===++XXXXXXXXX+==-==+XXXXX+=--:--.++XX++++.X+XXXXX+=--=++++++=-:.:::::...+:..-. \n ....::--=++=+=---.==++.+XXXXXXXXX++=+==+-====:XXXX+++=--====+XX-XX:XXXXX+====:==+=XXXXX+==-=++XXX-X==XXXXXXXX++==+X=XXX++=-:::::::::.::...-\n",
" \n . .:: \n .+. .-::. \n .:--:. . \n ..--.. \n . .:-:.. \n .-:...::. .:. \n :-....... .--:.. \n .-.. .. ..:-.. \n .:+:=.. .:-:. \n . ::+::..:=: .-:.. \n . . ..:.:::-. :-.. . .:.. \n .:..-. . ..:.... .:.-. .... \n .. .:-:... . .. :.::... .-:. ..... . \n .. -.. ..-::.... ..:-..-.::+.. .:. .::.. . .. \n .. ..:..:..-::.. .. +:::.=.:.:::. . .--:. .... ... \n . +.... ....:: :-:.:..-:=.. .::-:::..:-:. :. .-=::::... .. .. \n .. ..:::=.. ..-=:::::.:.:--... . .:--------::. ..:. .. .--=--:. ....-. \n . .. ..+..=:::.... ..:+-:-::.+.:=+=--:. .:.:.. ..:=+++++++=--. ..... :...:. ..:-=+-::.-.. .:::.. \n ..::.. ....:-==--::.=.:-=-=:::..:-=++XX=---=:-::. .:-+X+XX.XXX+=-.:. ....... . .-++==:..=::--==--::..:..::-:.. \n ..-.::... ..::::-=XX X+=---+=-::::-- -=+++X++==---::-:::-++:XXXXXX-+==:..:-=--:-:.. .:==+X+=------== +++=:::--::--:.. .... ..-. \n . ...:::-:...::-- ==+X-XXX:+++=-:---.-::-==+X+X+=---: ----==+ XXXXXXX:==---+XXXX-X=-:..:-++ X+====-=++XXX+X=::--= =-=-:.............. \n .. .:.--====--::---==-++XXXXXXX+==-=--==----+XXXX+==--:==--=++XX.XXX-XX++=---=+XXXX-++=-:--+++X+++ ++XX=XXX++=--+XXX+++=-:.....::.+.:..+. \n ......:--==+===-:--=++XX+XXXXXXXXX++++=====++== XXX-++=---===++XX+:XXXXXX+=======+XXXX++=====+XXX++XXXXXXXXXXX++==+XXXXX++=-::::::-:::.....=\n",
" ::.. \n .-:. \n .::. \n ..: .. \n : .:.: .. \n :.. . .::. \n :. .--.. \n .::. . .::. \n . :. . .. .:. \n -...:...:: :.. \n ..: . .:. .... \n .:-::. . . ..-. .:. . \n .::. .. ..:.. .. .. \n ... .:.... ....:....... .. :.. \n .:..:...: .:. ..=:.. ..... . .:-...+. \n .. .:=::::... .:. .::::. ..... .. . .=:.. \n .=... .-=:....... ::.. . .::::::--:.. .. .:-=-:.. ... \n . ...::.+. .-::......::-.--:.......... .-==+==--=+=:. .... ..:--:.. . :..:. \n .. ..::-+=-:.. ..=-::-...-==+==+-:--::-.....:++XX+:+ XX=-:. . .:==-=:. ...::---.. ..:...::. \n ..:.. ..+:-+XX++=::::-=-:....:---==++===--:::::::-=+XXXXXXXX+=-:... :=:-.. . .--==+=-::..:::-===+=:.:::::+-:. ... \n . ....::.. .:.::=++XX++==-==- ::-::--::==++=+=--:-=: ::--=XX=XX-XX++=--:-XX+===+-....:=+=++--::----=++=.++::::---:-:. ..... \n . ..::-----:..-::--==+X-X+++==--::--:-::-:-+X+=+=--::-=:::-==XX X-XXX++=+--=+XX+ ++=--..:-=++ ==--==+XXX++-+=--+++++==-:.............. \n ....::--====--::==++XXXXXXXXX++==-=--::-=++=X++=+.==-::-:--==+X+XXX:XX+===--=++XXXX++==----+XX+====+XXXXX=X++= =+XX+XX+=-:..:::::::..-.. \n .....+::--======--= XXXXXX.X-XX+XXXX+=---===+XX+=+XX.++=--===-++XXXXXX=X.-++====++XXX.X++---=++X=X=++XXXXX-XX.XX++++XXXXX+==-::===-- :::.....\n",
" -.. \n .. . \n .. .. \n . ... \n . .:: \n .:. :. \n ..: .. \n ... : \n .:.. .: .. \n ..:.. .. . \n .:::.. . . . \n . .. .. . :. \n .. ... .. . .. .. . :. \n .:-:... .. :.. . .-. \n .-::.... ...-..:=:. .-:. \n . ..::.. . . ..-.. .. ..:....::-=:. .::.+.. . \n ..:-:. .-:. . .::=:--:..::.+.....:-=-----::-=:.. =.. ..::.. . . \n .. .:-++=-.. .-:.. ::-.-:-::=-:......:=+X:X+===+X+-:. .::--. . .:::-:.........: \n ... ...:=XX+==-:.:..-::.. .:--------=-:::...::-+XXXX++++X+--... ... .-----:......-: --+=:.......:. . \n .:... ..::-=+++=--:.:-::..:::::--==+=---:::-..=:=:=++XXX+.++==--::=+==-::.:. .:-+-=--:..:::.-==-==+=::.:::.:. . \n ....:::::....:::--=+X++====-::..::...:-:-+X=--=:-:-==:::--=+XXXX+X+==--:-++XX====-:....-==+=-:::--++++====-====-==--:. . ....... \n ..::::-:--:::=X=== =+X++:+++--:.::.-.:-==+-+=-==-::.::::--=+X-XX++++==--:-=++XX+====-:.:-=XX=--::-=+XXX++===.=XX+==+=-:..:-::.....-. \n ......::-----==--+X+=+X:XXX+++XXX==--:::.-==+XX++==++.-:: -- ==+X+XX+XX++=+=-==++.XX+=+=-:--=+++=--==+XXXXXXXX+===++X+++==:::-=-:::: :.... \n ...+..:::-=+=----===+++XXXX X+XXX+X+=---- ++=++X++XXXXX+=====+X+++++XX+XXX+++===+XXXXX+==---=+.++X++XXXXXXXXXXXX++++XXX++=--------::-- ::....\n",
" \n . \n . .-. \n . \n . . \n .. . \n .. . \n .:. . \n .:.... \n .. \n . . . \n .:::.-.. . .. \n .:.. .:.. . \n ...... . . ....::. .:. \n .. .:. . .:..= ..... ...:.+..:=+. . ... \n .::-:. ..: .:..=:..:--:......::-:::-+:::-:.. ..- ..= \n ..--=--. . . .::..:..::.. . .:=++XX+==-=-=:. ..... :..-:. . \n .:==== -::.. ....-. .::.:-::.+::.-....:-=++XX=====--:. .. .::..: . .:::---:. . \n . . . .:=====-::.::++......:--+-=-:.:::......::=++XX+=+==--::.:==--:. ..-.:-::. . ..:--------:..+:... \n .. .........-:.:::-====-=:--:.. .....:::-==-:.::::::::::--++XX+==-=+--::==+XX=--::.. .:--==-:+..::--==------=-::--:.. .. \n ......::::..--=-:---=+==-===-:.. .. ..:====+-::-::..=:+:::-+XXX===+==-::-=+++X+=---::..--=+=-:...:-=.+++===-=+++=--=-:..:::.-.. .. \n ...:::::---:-==--=++X+-++=+==-::.:..::-=+X++=-=--=-..::-::-=+XX+=++=+=--:-=++XX+=-=-::-:=+==-::::-=+XXXXX+===++X+=:=---::-::...:.... \n ......::-.---:----==++XXX XXX++:++=--:::----=++XX.++++=:::-:-==+==+XX+XX++-=-==+XX++===-::=== -======+XXXXXXXXX+==+XX++=---=::-::=-::::=.. \n .. ..::-=----::-:===++.XXXX-XXX=XXXX==--.====+=++XXX.X++====+.++++ +X++XXX:+=.=++XX+:++==--==+ +:XX-XX-XXXXXX:XX++XXXX++=- ::-:-::::::::..=.\n",
" \n \n . \n \n \n .: \n \n ... \n . \n -.... \n ::.. \n .. .... . \n . .. . ...:--: \n . . .. ..-.::.. ......:::-: .. \n .::=. .. . .::.. -...:-:--::..... . . ..: \n .::=:--:. . ...... ... .:-===+=:: :::. . . ....::. \n .::::::.. ... .. ...::=. .:. .:----+++--=::-.. ... . ...::::.. . \n . .:------....... . .::::-:...:. .:-==++==------::..-.--:. ...:::. . .:::::--:. ..-. \n .. :....:.:-----:.... .:-::--:.:. ... . .::-=+X+=------::+=+==+-::. .::--:.. ..:::-:::-:-:+..... \n ......:-..:::::::---=---=---:. . .:=+-=-:::....-.:.:::-+X+=----- -::-++=++--:::.+..::-.-:. .::-==++-=-====-::-::...... \n ...:...+::+::::::-+++ +=----::.......::-=+==-=-::-:. ...::-+++=- -=+=-:::-==++=-::-::=:.-=--::...:-++XXX++=-==++=--:::............ \n .. .+.:::::.::+----=XXXXXX++======:::..:--+-++.++-==--:.:---:-===++==++.=----=+X+==+--::-=-----------+XXXX:XX+====+-+=--::.:::..::::..= \n ......::---::.::-- -=+X=XXXX+:++++XX+-::-=====++XXXXX+=----==:====+XXX:XX X==-=++X==-+=-::=--=-=+X++++XX-XXX:XX+==+XX+==-::::::::::::::.. \n ......::--==---::-==+.+++XXXXXXXXXXXXX+==++X+ ==++XXXXXX+-==.+++XX ++XX+XXXXX++=+=+X++X+=-: --=++XXXXXXXXXXXXXXX+-+XXXXX+=--::-.-::-::::.....\n",
" \n \n \n . \n . \n \n \n .. \n ..... \n . \n .=. \n . .. ..::: \n .:.. . .::. .... .: . \n .... . .. .:::::.. .. . \n . ..... . . . .-::---:..+. .... \n ......... . .:+: . ..-::-----:::... .. . . ..+.:. \n .....:.. ...::...+ .::--=+=-:::::-:...:::-: ::. ...::-:.. . \n ....:::::::- . . .:=:.::.... .::-=X+--:-::-=-==:--:-:.. ...::.. ..:-:::::...... \n :.. .. -...:.:---::::.::. ..----:::: ........:-=X=-::::.::.:==-----:.+. .:-::. ...: ------.--:.::. . \n ...:...:.....:-=+X+=--::.:.. .- -=-.-:..-::. . ..:=== --::-=-:..:-==+=-:..::-...=::::.. ..-====+==-----=-:::::... . . \n .......::.::.::=++XX-X+=-+--=-::. ..:-=-=+===--:::.. ::-:-=====- ===-:.::-+X=-- ::::-=:::--.:..:-+XXXXXXX+= ===--::::............. \n .....:: :::..--::-==++XX:X====+++==:.=.:-==.=+XXX+=--::::-----==+XXXX-++=-::=++=.---::..::.--=++=--=+XXXXXXX++===++=--::.::........... \n ....:::----::-:--.===-++XXX++X++X+X++-:-=++:===+XX-X+==--===++++++XXXXXXXX+=--==+==++=-:.-::==+XX.-XX:XXXXXXX++=++X++X+-:::::::::::..+... \n .....::---===---=+XX:+++X+++XXXXXXXX++===+XX+-==+XXXXXX=+++XXX:XX.X+XX+:XXX++=++==+XXX+=--:--=+++XX:XX-+XXXXX:++=+XXX+XX+=-:-===-:::::.=....\n",
" \n \n \n \n \n .. \n \n \n \n :. .. \n :. . .... \n . . .... . \n .. ...:.. . . \n . .. ....=::.. . .. \n . .. . .:-:-:....... :.. .. ..:. \n ..: ... .:..... .::+:-=-::..:::+.....=:. .. .=.:. \n ...::.+... ..--:.. .. .::--=-::....::=-:--:.:. ... -.....:..-. .. \n .. ..:-=--:: .. .. ...--::+:. .. ..:-=--:.. ..:.:--::-::. . ..::.. ....::..::..-.... \n ..........:+==--.:.:..:. .:.:-+-::.. ... .:--=--+:::::. .:--+=-:...:. ..::.. ..::--==--::::::..... \n :......:..:...:-+=:==+--:::+=:.. ...-==+==-:.-.. ...:---===---=-:-..:-=+-:...=.:::::::::....-=+X++++.+=--+-::...... .. \n :...+....::-::-=====:++=======+-:. ..:-==+.XX++-:....:+::--==+XX++-=-::.:==+--::+......:--++=-::-++XXX.++X ==+=--::..::=:+....... . \n ...+.::--:::+===+= ==+++X+=+.+==++=:.::-====+XX++==--::--==++=+XX.XX=XX+-::--==-==--.....:-=XXX +=++XXXX.+X+++++=== =::.::-::........ \n ....::+:-=----=+ ====+:++=+++:++++= =---+X+==++XX++====-=+XXXXX+XXX.X-XXX++--+==+X++=-:..::-++XX++XX+XXXXXX++==++++=+X=--:-:-::..:...... \n . ....::--=+++==+X=++=+===+++XXXXX++:==+++++XX++XXXX+++++-+X.XXXXXXXX+-XXX:++++X+=XXXX-+=-:--=++ XXXXXXXXXXXX++=-+++XXXXXX=-==---+::...:. ..\n",
" \n \n \n \n \n \n \n . . \n .. \n .. \n .. \n . . . \n . . .:.. . \n .. . .--:...:. .. =.. .. \n . .. .::... . ..-::....-: .. . . . \n .:-::...: --::. ..-:-:.... .:.:...=. . . -.... \n .--:::::.. .:--:. .::-:.::.. .....:-::-:. . .. ........ \n . ..:- --:--....=. .:-+-:.. .::--:-:..... =:::-:.. . .. =..:-=-:.:..... \n .. ...... -----=--:::--.. .:-=+++-:.. ..:-----+::-:. .::--:.. ......:::.. .:=:===--=+-::::.. ... \n .+.. :...:--:::---+==+=--==--=-. ..:-=+XX+==:. .::.-=-=XX++=--:.. .: -- ::.. .+.:-+X=-:.:--=++-===+==-=--:..+...::.. \n ......::..::=-------====:-+===-==-.. .:---=+X+==-::::: --==X=XX XXXXX=-:.:------:::. ..::+X+ ==--=++X++=++==+=--:--:..:::..... . \n .....::=--::=------==-= ====++==:--:::=+===++X+==--:-::=++XX -:XXXXXXX+-::---=++=-:.. .:-=++==+===+XXXXX+===++=--=+=-::-::.+.....: \n ....::- ===--======-+-===++XXXX+=---+====+X++:XX++++=--=+XXXXXXXXXXXXXX++==+X++XXXX+-::.:--==+X++++=XXXX.++=====++ XX++=:--::::..+.. .. .\n ....:::-:==++==== ==+-===+=+XXXXX+==-= =+==XXXXXXXX++XX===+XXXXXXXXXXXXXXX+==+++-XXXXX =--===++XXX=X.X.XXXXX++:==++XX+XX+=.--:::.:.........\n",
" \n \n \n \n \n \n \n \n . \n \n \n ..- \n .. .::. . . \n .:. .::.......... \n .:... -:..+ . .:...=. .. \n .::..:.. .:-:. ..=::.:... ..........: . \n ..: -:::::....-. .:--:.. ..::---:..... ::..-:. . .::...:. \n ....-::--:-=:::.::. ..:.==::. .::----::: .. .:.::..: .... .::--:::-:... . \n ...::::..::=-=---.--:--:. .:--XX+=-:.. ..::-=====--:=:. ..:-::.+. ...:==-:-..:- -=----==-:::...-.. .. \n . ......::-:::::::- -=---.---=-:. ..:--+X+==--::. ..::-=+X+XXXXX+=:....:-----:.. ..:-XX==-::---====-=+=---:::......:... \n .-....:-::-:--:--: --==---.--==-- .. :-=--==++=--::::::--==+X XXXXX-X+-:::=--:=-:::. ..:-=+=--=--==+++==++==+=-+--=-:.::..... \n ....:::---:-----=--=====-===-+==----+--=++==+X====-::--=++XXXXXXXXXXXX+=+--==+XX+=-:....:-==+====+=+XX+X+==+==-==.===-::::::.:.... \n ...+:---===---.======-===+X:XX++==-----==+XXXXXX+==----==+XXXXXXXXXXXXX++=+X+ +XXXX+=-:+:--=+++++X++XXXXX+++=====+X++.==-::-::......... .\n .....::-==++.==-==-++=+==+++XXXXXX+========XXXXXXXX-+++++XX-X:XXX+XXXXXX ++=++==+XXXX:X+==.=++XXXXXXXXXXXXXXX+===:+XX=XX+=--:-::::.........\n",
" \n \n \n \n \n \n \n \n \n \n . .. \n . .. \n . ..+ \n .. ..:.. ....... \n ....-..- . :-:. . ...::.=. .. ... \n ...=..+.::. .::. : :.. ..::-::....... . ..: .... \n .. ........:::::..::. ..:-=-:.. ..:-----::..-... .... :... .:.:.....:. .. \n ........:.+.::.::.:-:. ...:=+==-:..- ..:-=+X+=--::..+ . ....:. .:.:=-:.. .:..::::::-:. ... . \n ............::::::--::-=-.. ..:-=++=--:::... ..::-=+:XX+-+=-:.....::-::.. .:==-:-::+::::----.-=-::..:.....+. \n .....:..::::::::::--:+::::--.=:..-:-:-==:=+=-:..+.:.::-==+XXX++XX+-:..::-==--:.. ..:-==--:::-=-==-=--=--+-::::::.... . \n ......: ::.::::.---.-=---.=-+-----:::::--++====--:..-::-==++XXXXXXXX+=--::-=+XX+=-:.....:--=------==X==:==--=-==-----:......... \n .:.:::.---::.--+:--+-==++======-:--::---=+X-+++==-.::-=++++:XXX:XXXXX+======++XXX+=-::.::-========++X++++==+====++==--::... -..... \n .....:--:===+------=--====+XX.++X+=-==---=+XXXXXXX+==----=++:X:X=XXXXXXX+=++===+X.X++==---=++XX+++ XXXX==XX++=-==+XXX++=-::::::.......... \n ......::-=.=+ ==--=++++====+X=XX:XXX+ +X==++++X XXXXX++===-+XXX.XXXXX.-XX++=+==.=+XX-+++:+=++XXXXXX.XX.XXXXX+.X+=+++XXX X+=--::--::.........\n",
" \n \n \n \n \n \n \n \n .. \n \n \n \n .: .-.. \n . .... .. ::.. \n ..... :.. .::. ...::::......... \n . ...:.. ..:. ..= ::.. ....:--==-- ::.. ... ... . .. \n ... .......::. ..:----::=..= ..:-+XX= =--:... ..=.::. .=.:.:.. .. ..=...::. \n . +......:.........:: . ..:-----=::.. ..:--=XX+====::.=....::::. ..:--::=....-....:::::::.... .. \n .... .. .=.::-::::::....:::..=.::-------::....:.:---=++===++==-:..:-==--::.. .:----::::::::--::--+:::::....=.. \n ...............::----- -:: ::::.:=:::-===----:.=..::-=-=+X+X++++==-:::=-+XX+=--. ...:::--:::=::-=--=---:----.::.::. . \n ....::::....::::+:---:==---=-=-::+::::--=X+===-:+:..::-==++X XXXXX+==:--=-=+X+=+=-::..::------:--==+=++==---====---::.-.... \n ...:::--:-:::::------===++====++-----++-=+X+X+=:=-:.:::-=+-+XXXXXXX++++-==-=+-+===---:: -=+++== =:+++XX+++===+=+X+==--::... .-... \n ..+...:----------=========XX=X++-+X+===---==++=XX+==+==--==+++XXXXXXXXX+++==--==+X++===---++XXXX+.++XXXXXXXXX++===+XX++==--:::::.-.......- \n ....=..::-========-=:++++ +++XXXXXXXX++=====+-=++XXXXX+++++XX++XX:XXXXXX.X+======+XX.+++++++==+X:XXXXXXXX+X:XXX-X+++XXXX++==--:--:::::.......\n",
" \n \n \n \n \n \n \n \n \n \n . \n .. ..... \n . .::..:. \n .. .:.. .. ..-:-:::.. .. \n . .-. ....... . ..:::=---::::.. ... . ... \n ... . ... .:::...... .. ..:--++=-----:. ...:.. .. ...... . . \n :..: ..:. ... ..:=--::.... . .:: -=++==-=---:=....:::... .:::..... . +....... \n ..:-:.::.. .. ..-...:-:-- :::.....::---+====- ----::..:--=:-:.. .:--::.......+=:.:::........ \n . . ..:-:::::+.....+=.::-...-=--::::....:.::- -+=====+=+-::+:-=-++=--:. ..:-::.:.. ..::=::-::=-:::::..... \n ...............::------+::::::..::::::-=+==-::... .::--=+X+++ +X+=-: :--++==---::...::=--::.=.::--====--:--==-::.... \n ..:-::::..-.::-:-=-- =+==--:--=-:::::---=++=-::::....::-=++XXXX++++=+=-.--===.---: ::.:-=++=--:-==+++++-==-=-=+==--::.......... \n ...:::.:::::-:----- ==+XX+=====++---.:--=++++=---=-+::---=+XXXXXXXX+==--:---=++===- ::---+X++=-===++XXXX++=-==+++==--::::.:...... .:. \n ....:.::----------===-===+XXX X++ +X+===--+-==:+XX+==:====++=+++XX.XXXXX+===--==+XX+= ==--===+XX+++++X=XX XXXXX+++++X++==-.-:+:::.......... \n ..=...::---.-==+=+==++++++++-=XXXXXX X++++:+==:=++XXX++ +XXXX+XXXXXXX+.+XX++===+XXXX=X+.==--:==+XXXXXXXXXXXXXXXXX+++XX=++++-=-----.::........\n",
" \n \n \n \n \n \n \n \n \n :... \n .:..: \n .. .:.:..+. \n .. . .. ...::.:.. . .. \n . .:-:-:--:...:. . . \n .. . .:..... . .::+===--:.::-:=.. ...:.. . \n .. . .:=-:::.. -.. .::-+===--::---:.....::+.. .:.... . ... \n .:...... ...::--::-:..... ..::==-=--::-:-.::.::--:::... .--::.. . .... . \n .::::........ ......----... .. .:+::==-------=-::::-===-::::. .::--:. .. ......::........ \n .. ..+.::-::--:..:.:...+..:..::==-:....: .::::-++========::.:-+=.=-::::.-..:-.-:::. ...::-----:: ---::.. \n .....-.+..:.:.:::--+==--::::-:-:..:::::-=--:+...+...:+:-=++X++== =--:::-+--=-:::..:..:-==--::.:--=+++==-----==--::.. . \n .....-.=..:::-:::--=-=+=-----:-=::.: ::--==--:::::..::+--+XXXX-====--:.::-=-==---::.::::=+==-----=+XX++-==:-===.--::........ \n ....-:::::::.------= -=+X++===+=.==-- --:--==++=-+--::-:====+XXXX+-+==-::=--=++==---:.:--==++==--=++ XXXXX++===+++===-::-:::....+:... \n ....-.::-:------====+=.=++:XX+X++++ ===+ ======+XX+==-== ++++++X+XXXXX=+==-==++XXXX==--::+--==XX++-+X XXXXXX:++++++++====-+-::::::........ \n ...=.::--=-=== ===++XXX++XXXXXXXXXX+XXX:++======+X XX+.++X++XX+++X+XX++XXX++=++ XXXX+===--:--=++XXXXXXXXXXXXXX++++XX+X+++.=-------::::......\n",
" \n \n \n \n \n \n \n :.... \n . . \n . ....- \n .:..:::::.. \n .:..+.::... . \n =::::.::... . +...::.. \n .. .:-==-:-:....::... .::. \n .:-::... .:-+=---::..:::......::.. ....+ . \n . . :..:-:::.... .:--=--::::::::-:.::+:-:.. :=-::. .. \n ....... ..::-:::.. .:::=--::::::.-:---=---:...:.:.:--:...- ..::..=. ..... \n . ..:::::::..... .. ...-:-::. ...-::-+=-=-::::::.:-+--=--::.....::-::... -..:------:.:.:... \n .. . ....:=::-::-:-...:..:..::..:--::. . .:::--+ +=----::.=.:-=:--::.+....+.-=::.=...:-=:==--+- ---::=... \n ............::::::--:-+-::=:::-:..=:...:--:::.... ..+---=++X+=-=-::..:.:==:=--:....::::==--::---=++.====--=---=:-:..: ... \n .....::::-::.:--::---====-------::.:::.---==--:.: ..-==--==-++====-::...::==----=::.:.::-==-::--=+=++++++======----: :. .... \n ....-:=::::: --:==----=+==++=+==+=- --=.=+=.==X+=--:: -===+==+++X+-+==-::--==.++=-:::. ..:-==++===+++XX+++X====+===----:::::.:...:.. \n ....::---------==:++==+++++++++X+=++XX+=======+XX++ ===+==-++==++X XXX++==++++++++=--:.:.::-==XX++.XXXXXXXX++=++:XX++==--::+: :::.... . \n .. .::---=========+X++XXX+XXXXXXX++:XXXX+=+=== =+X=X++X+-+.+++++XXXX++XXXX+++==++XX+==----- -=+XXXXXXXXXXXXX+++++X-XXX++=--------.:::......\n",
" \n \n \n \n \n \n . \n . . \n :. .:.. \n .. .. ..::. \n . .....:: ..:. \n .-:...-.:.. . .. .. \n . .:-=-::... ...+. :+.. \n ... .:-+=-::::. ........:--.. ..-.. \n . .=-:.... .::==::::......-::::..-:. .::... \n .... ..::=. .:-==:=:.......::---::-. .:-::::. ........ \n ........ . ..=.:. .=::==--:+......:-+.--::. .+. ..::-:. .:-::::-:::.:. \n .....:-:.::-.. .. .. :. ..:.. .::--+=--:. .. ..:=::=::.......::-:.. ..:-----:::-=-:.:.:. \n ...... .::::::::.::--...... .....:-:.. .::--=+=.=::.... ..:=::.:-..:....::--+....:-=+=--==-==-::::::... \n ....-:.....::::::::::--- :::::.=....::--==-:.. . ..:=--+====--::.......-+-:.::+.. ...:=-:..::-.=======+-==----:::...: .. \n ......::...:-::--::--------==-=-:::---=++==++=-::-..:--:-=:==++===-:..::::==- ::.... .--=+.---=+X===+==+==+= ----::........ \n ...=:::-::::--=-==-=+==--====+++-:===-=-==.++X+=----=---======+XXXX+=-:-===++==--::. . .:--X+===.:XX+.+=+===+++====-::.::-:.+.... \n ...:::------+=-==+.++X+=++XXXXXX++=+X+===+=-==+XX++=+=++ =====+XXXXXX +===+==++X+==::....::-++X ++XXXXXXX+===-=++-+++=-=--=::::::=.-.... \n ...:-----=----==+X++XXXXXXXXXXX+XX+++++==+==-==+XX.+=++-=====+++XX:XXX-X+=+++=+XXX+=--======+++XXXXXXXXXX+===+++XX++++=+------::::=:.....\n"
]
@@ -0,0 +1,68 @@
"use client";
import { useEffect, useRef } from "react";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./hero-flame-data.json";
export default function HeroFlame() {
const ref = useRef<HTMLDivElement>(null);
const ref2 = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let index = 0;
const interval = setIntervalOnVisible({
element: wrapperRef.current,
callback: () => {
index++;
if (index >= data.length) index = 0;
if (ref.current) {
ref.current.innerHTML = data[index];
}
if (ref2.current) {
ref2.current.innerHTML = data[index];
}
},
interval: 85,
});
return () => interval?.();
}, []);
return (
<div
className="cw-686 h-190 top-408 absolute flex gap-16 pointer-events-none select-none lg-max:hidden"
ref={wrapperRef}
>
<div className="flex-1 overflow-clip relative">
<div
className="text-black-alpha-20 font-ascii absolute bottom-0 -left-380 fc-decoration"
dangerouslySetInnerHTML={{ __html: data[0] }}
ref={ref}
style={{
whiteSpace: "pre",
fontSize: "9px",
lineHeight: "11px",
}}
/>
</div>
<div className="flex-1 overflow-clip relative">
<div
className="text-black-alpha-20 font-ascii absolute bottom-0 -right-380 -scale-x-100 fc-decoration"
dangerouslySetInnerHTML={{ __html: data[0] }}
ref={ref2}
style={{
whiteSpace: "pre",
fontSize: "9px",
lineHeight: "11px",
}}
/>
</div>
</div>
);
}
+7
View File
@@ -0,0 +1,7 @@
export { CoreFlame } from "./core-flame";
export { AsciiExplosion } from "./ascii-explosion";
export { default as HeroFlame } from "./hero-flame";
export { SubtleExplosion } from "./subtle-explosion";
// Convenience wrapper for dashboard usage
export { FlameBackground } from "./flame-background";
@@ -0,0 +1,17 @@
[
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ ░ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ ▒ │ ░ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ░ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ ▓ │ ▒ │ ░ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▒ │ ░ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ░ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ ▒ │ ▓ │ ▒ │ ░ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▓ │ ▒ │ ░ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▒ │ ░ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ░ │ │ │ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ ░ │ ▒ │ ▓ │ ▒ │ ░ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▒ │ ▓ │ ▒ │ ░ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▓ │ ▒ │ ░ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▒ │ ░ │ │ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ ░ │ ▒ │ ▓ │ ▒ │ ░ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ░ │ ▒ │ ▓ │ ▒ │ ░ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▒ │ ▓ │ ▒ │ ░ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▓ │ ▒ │ ░ │ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ ░ │ ▒ │ ▓ │ ▒ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ ░ │ ▒ │ ▓ │ ▒ │ ░ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ░ │ ▒ │ ▓ │ ▒ │ ░ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ▒ │ ▓ │ ▒ │ ░ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ │ ░ │ ▒ │ ▓ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ ░ │ ▒ │ ▓ │ ▒ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ ░ │ ▒ │ ▓ │ ▒ │ ░ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ ░ │ ▒ │ ▓ │ ▒ │ ░ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ │ │ ░ │ ▒ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ ░ │ ▒ │ ▓ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ ░ │ ▒ │ ▓ │ ▒ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ ░ │ ▒ │ ▓ │ ▒ │ ░ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ │ │ │ ░ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ ░ │ ▒ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ ░ │ ▒ │ ▓ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ ░ │ ▒ │ ▓ │ ▒ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ ░ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ ░ │ ▒ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ ░ │ ▒ │ ▓ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ ░ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ ░ │ ▒ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ ░ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘",
"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ │ │ │ │ │ │\n└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘"
]
@@ -0,0 +1,65 @@
"use client";
import { HTMLAttributes, useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./grid-data.json";
interface SlateGridProps extends HTMLAttributes<HTMLDivElement> {
interval?: number;
color?: string;
}
export function SlateGrid({
interval = 200,
color = "text-black-alpha-12",
className,
...attrs
}: SlateGridProps) {
const ref = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const frameIndex = useRef(0);
useEffect(() => {
const animate = () => {
if (ref.current) {
ref.current.innerHTML = data[frameIndex.current];
frameIndex.current = (frameIndex.current + 1) % data.length;
}
};
// Initialize first frame
animate();
const cleanup = setIntervalOnVisible({
element: wrapperRef.current,
callback: animate,
interval,
});
return () => cleanup?.();
}, [interval]);
return (
<div
ref={wrapperRef}
{...attrs}
className={cn(
"absolute inset-0 pointer-events-none select-none overflow-hidden",
className,
)}
>
<div className="absolute inset-0 flex items-center justify-center">
<div
ref={ref}
className={cn("font-mono fc-decoration", color)}
style={{
whiteSpace: "pre",
fontSize: "10px",
lineHeight: "12px",
}}
/>
</div>
</div>
);
}
@@ -0,0 +1,68 @@
"use client";
import { HTMLAttributes, useEffect, useRef } from "react";
import { cn } from "@/utils/cn";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./explosion-data.json";
interface SubtleExplosionProps extends HTMLAttributes<HTMLDivElement> {
interval?: number;
delay?: number;
opacity?: number;
}
export function SubtleExplosion({
interval = 80,
delay = 30,
opacity = 0.08,
className,
...attrs
}: SubtleExplosionProps) {
const ref = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let index = -delay;
const animate = () => {
index++;
if (index >= data.length) index = -delay;
if (index < 0) return;
if (ref.current) {
ref.current.innerHTML = data[index];
}
};
const cleanup = setIntervalOnVisible({
element: wrapperRef.current,
callback: animate,
interval,
});
return () => cleanup?.();
}, [interval, delay]);
return (
<div
ref={wrapperRef}
{...attrs}
className={cn(
"absolute inset-0 pointer-events-none select-none",
className,
)}
>
<div
ref={ref}
className="text-black-alpha-20 font-mono absolute inset-0 flex items-center justify-center fc-decoration"
dangerouslySetInnerHTML={{ __html: data[0] }}
style={{
whiteSpace: "pre",
fontSize: "10px",
lineHeight: "12.5px",
opacity,
}}
/>
</div>
);
}
@@ -0,0 +1,45 @@
"use client";
import React, { useEffect, useRef } from "react";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
import data from "./wave-data.json";
export default function SubtleWave({ className = "" }: { className?: string }) {
const containerRef = useRef<HTMLDivElement>(null);
const frameIndex = useRef(0);
useEffect(() => {
const animateWave = () => {
if (containerRef.current) {
containerRef.current.innerHTML = data[frameIndex.current];
frameIndex.current = (frameIndex.current + 1) % data.length;
}
};
// Initialize first frame
animateWave();
// Start animation when visible
const cleanup = setIntervalOnVisible({
element: containerRef.current,
callback: animateWave,
interval: 150, // Slower for subtlety
});
return () => {
cleanup?.();
};
}, []);
return (
<div
ref={containerRef}
className={`font-mono text-white/10 whitespace-pre select-none fc-decoration ${className}`}
style={{
fontSize: "10px",
lineHeight: "1",
letterSpacing: "0.05em",
}}
/>
);
}
@@ -0,0 +1,34 @@
[
"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░",
"▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒",
"▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒",
"▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒",
"░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░",
"░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░",
"░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░",
"░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░",
"░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░",
"░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░",
"░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░",
"░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░",
"░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░",
"░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░",
"░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░",
"░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░",
"░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░",
"░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░"
]
+3
View File
@@ -0,0 +1,3 @@
// Effect Components
export { CoreFlame } from "./flame/core-flame";
export { AsciiExplosion } from "./flame/ascii-explosion";
@@ -0,0 +1,71 @@
"use client";
import React, { useEffect, useRef } from "react";
import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible";
export default function SubtleAsciiAnimation({
className = "",
}: {
className?: string;
}) {
const containerRef = useRef<HTMLDivElement>(null);
// Simple ASCII pattern for subtle animation
const asciiFrames = [
"░░░░░░░░░░░░░░░░",
"▒░░░░░░░░░░░░░░░",
"▒▒░░░░░░░░░░░░░░",
"░▒▒░░░░░░░░░░░░░",
"░░▒▒░░░░░░░░░░░░",
"░░░▒▒░░░░░░░░░░░",
"░░░░▒▒░░░░░░░░░░",
"░░░░░▒▒░░░░░░░░░",
"░░░░░░▒▒░░░░░░░░",
"░░░░░░░▒▒░░░░░░░",
"░░░░░░░░▒▒░░░░░░",
"░░░░░░░░░▒▒░░░░░",
"░░░░░░░░░░▒▒░░░░",
"░░░░░░░░░░░▒▒░░░",
"░░░░░░░░░░░░▒▒░░",
"░░░░░░░░░░░░░▒▒░",
"░░░░░░░░░░░░░░▒▒",
"░░░░░░░░░░░░░░░▒",
];
useEffect(() => {
let frameIndex = 0;
const animateAscii = () => {
if (containerRef.current) {
containerRef.current.innerHTML = asciiFrames[frameIndex];
frameIndex = (frameIndex + 1) % asciiFrames.length;
}
};
// Initialize first frame
animateAscii();
// Start animation when visible
const cleanup = setIntervalOnVisible({
element: containerRef.current,
callback: animateAscii,
interval: 150, // Slightly slower for subtlety
});
return () => {
cleanup?.();
};
}, []);
return (
<div
ref={containerRef}
className={`font-mono text-white/20 whitespace-pre select-none ${className}`}
style={{
fontSize: "10px",
lineHeight: "1",
letterSpacing: "0.05em",
}}
/>
);
}

Some files were not shown because too many files have changed in this diff Show More