Test Commit

This commit is contained in:
Possible
2025-01-29 23:49:27 +01:00
commit b363df97b5
25 changed files with 4689 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"template": "bolt-vite-react-ts"
}
+8
View File
@@ -0,0 +1,8 @@
For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production.
By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them.
Use icons from lucide-react for logos.
Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags.
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+28
View File
@@ -0,0 +1,28 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
);
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+4051
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
{
"name": "vite-react-typescript-starter",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^0.344.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.18",
"eslint": "^9.9.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^15.9.0",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.3",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
+114
View File
@@ -0,0 +1,114 @@
import React, { useState } from 'react';
import { Sparkles } from 'lucide-react';
import { ImageUpload } from './components/ImageUpload';
import { CharacterSelect } from './components/CharacterSelect';
import { Preview } from './components/Preview';
import { Character, TransformedImage } from './types';
function App() {
const [selectedImage, setSelectedImage] = useState<File | null>(null);
const [selectedCharacter, setSelectedCharacter] = useState<Character | null>(null);
const [transformedImage, setTransformedImage] = useState<TransformedImage | null>(null);
const [isLoading, setIsLoading] = useState(false);
const handleImageSelect = (file: File) => {
setSelectedImage(file);
setTransformedImage(null);
};
const handleCharacterSelect = (character: Character) => {
setSelectedCharacter(character);
};
const handleTransform = async () => {
if (!selectedImage || !selectedCharacter) return;
setIsLoading(true);
// Simulate transformation with a delay
setTimeout(() => {
const fakeTransformed: TransformedImage = {
original: URL.createObjectURL(selectedImage),
transformed: selectedCharacter.image,
character: selectedCharacter,
timestamp: new Date()
};
setTransformedImage(fakeTransformed);
setIsLoading(false);
}, 2000);
};
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
<div className="flex items-center">
<Sparkles className="h-8 w-8 text-blue-500" />
<h1 className="ml-2 text-2xl font-bold text-gray-900">
Naruto Character Generator
</h1>
</div>
</div>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
<div className="space-y-8">
{/* Step 1: Upload Image */}
<section>
<h2 className="text-lg font-semibold text-gray-900 mb-4">
1. Upload Your Photo
</h2>
<ImageUpload onImageSelect={handleImageSelect} />
</section>
{/* Step 2: Select Character */}
{selectedImage && (
<section>
<h2 className="text-lg font-semibold text-gray-900 mb-4">
2. Choose Your Character
</h2>
<CharacterSelect
selectedCharacter={selectedCharacter}
onSelect={handleCharacterSelect}
/>
</section>
)}
{/* Transform Button */}
{selectedImage && selectedCharacter && (
<section className="text-center">
<button
onClick={handleTransform}
disabled={isLoading}
className={`
px-8 py-3 rounded-full text-white font-medium
${isLoading
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-500 hover:bg-blue-600'
}
transition-colors
`}
>
{isLoading ? 'Transforming...' : 'Transform Image'}
</button>
</section>
)}
{/* Preview */}
{transformedImage && (
<section>
<Preview transformedImage={transformedImage} />
</section>
)}
</div>
</main>
</div>
);
}
export default App;
+38
View File
@@ -0,0 +1,38 @@
import React from 'react';
import { characters } from '../data/characters';
import { Character } from '../types';
interface CharacterSelectProps {
selectedCharacter: Character | null;
onSelect: (character: Character) => void;
}
export function CharacterSelect({ selectedCharacter, onSelect }: CharacterSelectProps) {
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{characters.map((character) => (
<div
key={character.id}
onClick={() => onSelect(character)}
className={`
relative rounded-lg overflow-hidden cursor-pointer
transform transition-all duration-200
${selectedCharacter?.id === character.id
? 'ring-2 ring-blue-500 scale-105'
: 'hover:scale-105'
}
`}
>
<img
src={character.image}
alt={character.name}
className="w-full h-48 object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-3">
<h3 className="text-white font-medium text-sm">{character.name}</h3>
</div>
</div>
))}
</div>
);
}
+36
View File
@@ -0,0 +1,36 @@
import React, { useState, FormEvent } from 'react';
import { Send } from 'lucide-react';
interface ChatInputProps {
onSendMessage: (message: string) => void;
}
export function ChatInput({ onSendMessage }: ChatInputProps) {
const [message, setMessage] = useState('');
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
if (message.trim()) {
onSendMessage(message);
setMessage('');
}
};
return (
<form onSubmit={handleSubmit} className="flex gap-2 p-4 bg-white border-t">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type a message..."
className="flex-1 px-4 py-2 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="submit"
className="p-2 text-white bg-blue-500 rounded-full hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<Send className="w-5 h-5" />
</button>
</form>
);
}
+32
View File
@@ -0,0 +1,32 @@
import React from 'react';
import { Message } from '../types';
import { formatDistanceToNow } from '../utils';
interface ChatMessageProps {
message: Message;
isOwnMessage: boolean;
}
export function ChatMessage({ message, isOwnMessage }: ChatMessageProps) {
return (
<div className={`flex ${isOwnMessage ? 'justify-end' : 'justify-start'} mb-4`}>
<div
className={`max-w-[70%] rounded-lg px-4 py-2 ${
isOwnMessage
? 'bg-blue-500 text-white rounded-br-none'
: 'bg-gray-200 text-gray-800 rounded-bl-none'
}`}
>
<div className="flex items-center gap-2">
<span className="font-medium text-sm">
{isOwnMessage ? 'You' : message.sender}
</span>
<span className="text-xs opacity-75">
{formatDistanceToNow(message.timestamp)}
</span>
</div>
<p className="mt-1">{message.text}</p>
</div>
</div>
);
}
+45
View File
@@ -0,0 +1,45 @@
import React, { useRef } from 'react';
import { Upload } from 'lucide-react';
interface ImageUploadProps {
onImageSelect: (file: File) => void;
}
export function ImageUpload({ onImageSelect }: ImageUploadProps) {
const fileInputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
fileInputRef.current?.click();
};
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
onImageSelect(file);
}
};
return (
<div
onClick={handleClick}
className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer
hover:border-blue-500 hover:bg-blue-50 transition-colors"
>
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept="image/*"
className="hidden"
/>
<Upload className="w-12 h-12 mx-auto text-gray-400 mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Upload your photo</h3>
<p className="text-sm text-gray-500">
Drop your image here or click to browse
</p>
<p className="text-xs text-gray-400 mt-2">
Supports: JPG, PNG, WEBP
</p>
</div>
);
}
+42
View File
@@ -0,0 +1,42 @@
import React from 'react';
import { TransformedImage } from '../types';
interface PreviewProps {
transformedImage: TransformedImage;
}
export function Preview({ transformedImage }: PreviewProps) {
return (
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-semibold mb-4">Your Transformation</h2>
<div className="grid grid-cols-2 gap-6">
<div>
<h3 className="text-sm font-medium text-gray-500 mb-2">Original</h3>
<img
src={transformedImage.original}
alt="Original"
className="w-full h-64 object-cover rounded-lg"
/>
</div>
<div>
<h3 className="text-sm font-medium text-gray-500 mb-2">
As {transformedImage.character.name}
</h3>
<img
src={transformedImage.transformed}
alt="Transformed"
className="w-full h-64 object-cover rounded-lg"
/>
</div>
</div>
<div className="mt-4 text-center">
<button
className="bg-blue-500 text-white px-6 py-2 rounded-full hover:bg-blue-600
transition-colors font-medium"
>
Download
</button>
</div>
</div>
);
}
+82
View File
@@ -0,0 +1,82 @@
export const characters = [
{
id: '1',
name: 'Naruto Uzumaki (Baryon Mode)',
image: 'https://wallpapers.com/images/featured-full/naruto-baryon-mode-epz28fsvrytiv5le.jpg'
},
{
id: '2',
name: 'Sasuke Uchiha (Rinnegan)',
image: 'https://static.wikia.nocookie.net/naruto/images/6/67/Sasuke_Rinnegan.png'
},
{
id: '3',
name: 'Sakura Haruno (Byakugou Seal)',
image: 'https://preview.redd.it/any-figures-of-sakura-using-byakugou-seal-v0-yv8jueh9v7od1.jpeg?width=640&crop=smart&auto=webp&s=c8a0c85d9cce90b94e5e00651921450f7b0cb790'
},
{
id: '4',
name: 'Kakashi Hatake (Dual Mangekyō Sharingan)',
image: 'https://static.wikia.nocookie.net/naruto/images/5/5f/Kakashi_Dual_Mangekyo_Sharingan.png'
},
{
id: '5',
name: 'Madara Uchiha (Ten Tails Jinchūriki)',
image: 'https://static.wikia.nocookie.net/naruto/images/3/3d/Madara_Ten_Tails_Jinchuriki.png'
},
{
id: '6',
name: 'Obito Uchiha (Ten Tails Jinchūriki)',
image: 'https://static.wikia.nocookie.net/naruto/images/1/1a/Obito_Ten_Tails_Jinchuriki.png'
},
{
id: '7',
name: 'Itachi Uchiha (Edo Tensei)',
image: 'https://static.wikia.nocookie.net/naruto/images/9/95/Itachi_Edo_Tensei.png'
},
{
id: '8',
name: 'Hashirama Senju (Sage Mode)',
image: 'https://static.wikia.nocookie.net/naruto/images/9/9b/Hashirama_Sage_Mode.png'
},
{
id: '9',
name: 'Minato Namikaze (Six Paths Sage Mode)',
image: 'https://static.wikia.nocookie.net/naruto/images/3/3b/Minato_Six_Paths_Sage_Mode.png'
},
{
id: '10',
name: 'Gaara (Shukaku Jinchūriki)',
image: 'https://static.wikia.nocookie.net/naruto/images/6/68/Gaara_Shukaku_Jinchuriki.png'
},
{
id: '11',
name: 'Boruto Uzumaki (Kāma Seal)',
image: 'https://static.wikia.nocookie.net/naruto/images/6/6b/Boruto_Kama_Seal.png'
},
{
id: '12',
name: 'Kawaki (Kāma Seal)',
image: 'https://static.wikia.nocookie.net/naruto/images/2/2a/Kawaki_Kama_Seal.png'
},
{
id: '13',
name: 'Sarada Uchiha (Mangekyō Sharingan)',
image: 'https://static.wikia.nocookie.net/naruto/images/1/1e/Sarada_Mangekyo_Sharingan.png'
},
{
id: '14',
name: 'Mitsuki (Sage Transformation)',
image: 'https://static.wikia.nocookie.net/naruto/images/4/4b/Mitsuki_Sage_Transformation.png'
},
{
id: '15',
name: 'Momoshiki Ōtsutsuki (Fused Form)',
image: 'https://static.wikia.nocookie.net/naruto/images/4/4f/Momoshiki_Fused_Form.png'
},
{
id: '16',
name: 'Isshiki Ōtsutsuki (Full Power Form)',
image: 'https://static.wikia.nocookie.net/naruto/images/c/cd/Isshiki_Full_Power_Form.png'
}
];
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
+12
View File
@@ -0,0 +1,12 @@
export interface Character {
id: string;
name: string;
image: string;
}
export interface TransformedImage {
original: string;
transformed: string;
character: Character;
timestamp: Date;
}
+37
View File
@@ -0,0 +1,37 @@
export function formatDistanceToNow(date: Date): string {
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
if (diffInSeconds < 60) return 'just now';
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
return `${Math.floor(diffInSeconds / 86400)}d ago`;
}
export function getSystemResponse(message: string): string {
const lowerMessage = message.toLowerCase();
if (lowerMessage.includes('hello') || lowerMessage.includes('hi')) {
return 'Hello! How can I help you today?';
}
if (lowerMessage.includes('how are you')) {
return "I'm doing great, thanks for asking! How about you?";
}
if (lowerMessage.includes('bye') || lowerMessage.includes('goodbye')) {
return 'Goodbye! Have a great day!';
}
if (lowerMessage.includes('thank')) {
return "You're welcome! Let me know if you need anything else.";
}
if (lowerMessage.includes('help')) {
return "I'm here to help! Feel free to ask any questions.";
}
if (lowerMessage.includes('weather')) {
return "I'm sorry, I don't have access to real-time weather data. You might want to check a weather service for that information.";
}
if (lowerMessage.includes('time')) {
return `The current time is ${new Date().toLocaleTimeString()}.`;
}
return "I'm not sure how to respond to that. Could you please rephrase or ask something else?";
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+8
View File
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
+10
View File
@@ -0,0 +1,10 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
optimizeDeps: {
exclude: ['lucide-react'],
},
});