MVP Complete
This commit is contained in:
parent
0f56648a93
commit
70b3d7327a
19
app/components/ActionButton.tsx
Normal file
19
app/components/ActionButton.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ActionButtonProps {
|
||||||
|
onClick: () => void;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActionButton: React.FC<ActionButtonProps> = ({ onClick, label }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded transition-colors duration-300"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionButton;
|
116
app/components/ColorSchemeCard.tsx
Normal file
116
app/components/ColorSchemeCard.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { ColorScheme } from '../utils/colorSchemes';
|
||||||
|
import { generateYAML } from '../utils/yamlExport';
|
||||||
|
import { Highlight, themes } from 'prism-react-renderer';
|
||||||
|
|
||||||
|
interface ColorSchemeCardProps {
|
||||||
|
scheme: ColorScheme;
|
||||||
|
isSelected: boolean;
|
||||||
|
onSelect: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ColorSchemeCard: React.FC<ColorSchemeCardProps> = ({ scheme, isSelected, onSelect }) => {
|
||||||
|
const codeExample = `
|
||||||
|
// User object and function
|
||||||
|
const user = {
|
||||||
|
name: 'DWS',
|
||||||
|
power: 8999
|
||||||
|
};
|
||||||
|
|
||||||
|
class Shape {
|
||||||
|
constructor(color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async data fetch simulation
|
||||||
|
async function fetchData() {
|
||||||
|
return await new Promise(resolve => setTimeout(() => resolve('Data loaded'), 500));
|
||||||
|
}
|
||||||
|
const [even, odd] = [2, 4, 6, 8].reduce(([e, o], n) => n % 2 ? [e, [...o, n]] : [
|
||||||
|
[...e, n], o
|
||||||
|
], [
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Logging here
|
||||||
|
*/
|
||||||
|
fetchData().then(data => console.log(data)); `;
|
||||||
|
|
||||||
|
const handleDownload = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const yaml = generateYAML(scheme);
|
||||||
|
const blob = new Blob([yaml], { type: 'text/yaml' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${scheme.name.replace(/\s+/g, '_').toLowerCase()}.yaml`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`w-96 bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden hover:shadow-xl transition-all duration-300 ease-in-out cursor-pointer ${isSelected ? 'ring-4 ring-blue-500' : ''}`}
|
||||||
|
onClick={onSelect}
|
||||||
|
>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-xl font-semibold">{scheme.name}</h2>
|
||||||
|
<button
|
||||||
|
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors duration-300"
|
||||||
|
onClick={handleDownload}
|
||||||
|
>
|
||||||
|
<Image src="/download-icon.svg" alt="Download" width={24} height={24} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-md mb-4 transition-colors duration-300">
|
||||||
|
<Highlight theme={themes.dracula} code={codeExample} language="javascript">
|
||||||
|
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
||||||
|
<pre className={`${className} text-sm overflow-x-auto`} style={{ ...style, backgroundColor: scheme.colors.primary.background }}>
|
||||||
|
{tokens.map((line, i) => (
|
||||||
|
<div key={i} {...getLineProps({ line, key: i })}>
|
||||||
|
{line.map((token, key) => {
|
||||||
|
let color = scheme.colors.primary.foreground;
|
||||||
|
if (token.types.includes('keyword')) color = scheme.colors.normal.blue;
|
||||||
|
if (token.types.includes('string')) color = scheme.colors.normal.green;
|
||||||
|
if (token.types.includes('number')) color = scheme.colors.normal.magenta;
|
||||||
|
if (token.types.includes('comment')) color = scheme.colors.bright.black;
|
||||||
|
if (token.types.includes('function')) color = scheme.colors.normal.yellow;
|
||||||
|
if (token.types.includes('operator')) color = scheme.colors.normal.cyan;
|
||||||
|
if (token.types.includes('class-name')) color = scheme.colors.bright.magenta;
|
||||||
|
if (token.types.includes('constant')) color = scheme.colors.bright.red;
|
||||||
|
if (token.types.includes('punctuation')) color = scheme.colors.bright.cyan;
|
||||||
|
if (token.types.includes('variable')) color = scheme.colors.bright.yellow;
|
||||||
|
return <span key={key} {...getTokenProps({ token, key })} style={{ ...getTokenProps({ token, key }).style, color }} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</Highlight>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-8 gap-2">
|
||||||
|
{Object.values(scheme.colors.normal).concat(Object.values(scheme.colors.bright)).map((color, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="w-full pt-full rounded-sm transition-colors duration-300 relative group"
|
||||||
|
style={{backgroundColor: color}}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-black bg-opacity-50 text-white text-xs">
|
||||||
|
{color}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColorSchemeCard;
|
43
app/components/Settings.tsx
Normal file
43
app/components/Settings.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface SettingsProps {
|
||||||
|
isDarkMode: boolean;
|
||||||
|
onToggleDarkMode: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Settings: React.FC<SettingsProps> = ({ isDarkMode, onToggleDarkMode, isOpen, onClose }) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-xl">
|
||||||
|
<h2 className="text-xl font-bold mb-4">Settings</h2>
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<span>Dark Mode</span>
|
||||||
|
<button
|
||||||
|
onClick={onToggleDarkMode}
|
||||||
|
className={`w-12 h-6 rounded-full p-1 transition-colors duration-300 ease-in-out ${
|
||||||
|
isDarkMode ? 'bg-blue-600' : 'bg-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-4 h-4 rounded-full bg-white transform transition-transform duration-300 ease-in-out ${
|
||||||
|
isDarkMode ? 'translate-x-6' : ''
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="mt-4 bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded transition-colors duration-300"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Settings;
|
16
app/components/SettingsIcon.tsx
Normal file
16
app/components/SettingsIcon.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
interface SettingsIconProps {
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsIcon: React.FC<SettingsIconProps> = ({ onClick }) => {
|
||||||
|
return (
|
||||||
|
<button className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" onClick={onClick}>
|
||||||
|
<Image src="/settings-icon.svg" alt="Settings" width={24} height={24} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsIcon;
|
@ -17,7 +17,7 @@
|
|||||||
body {
|
body {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: var(--font-geist-sans), Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
@ -25,3 +25,7 @@ body {
|
|||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pt-full {
|
||||||
|
padding-top: 100%;
|
||||||
|
}
|
||||||
|
193
app/page.tsx
193
app/page.tsx
@ -1,101 +1,110 @@
|
|||||||
import Image from "next/image";
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import ColorSchemeCard from "./components/ColorSchemeCard";
|
||||||
|
import ActionButton from "./components/ActionButton";
|
||||||
|
import SettingsIcon from "./components/SettingsIcon";
|
||||||
|
import Settings from "./components/Settings";
|
||||||
|
import { ColorScheme, knownSchemes, generateRandomScheme, crossSchemes, generateSchemeFromGeneticAlgorithm } from './utils/colorSchemes';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
const [schemes, setSchemes] = useState<ColorScheme[]>([]);
|
||||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
const [selectedSchemes, setSelectedSchemes] = useState<number[]>([]);
|
||||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||||
<Image
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
className="dark:invert"
|
const [likedSchemes, setLikedSchemes] = useState<ColorScheme[]>([]);
|
||||||
src="https://nextjs.org/icons/next.svg"
|
const [dislikedSchemes, setDislikedSchemes] = useState<ColorScheme[]>([]);
|
||||||
alt="Next.js logo"
|
|
||||||
width={180}
|
|
||||||
height={38}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
|
||||||
<li className="mb-2">
|
|
||||||
Get started by editing{" "}
|
|
||||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
|
||||||
app/page.tsx
|
|
||||||
</code>
|
|
||||||
.
|
|
||||||
</li>
|
|
||||||
<li>Save and see your changes instantly.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
useEffect(() => {
|
||||||
<a
|
generateNewSchemes();
|
||||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
setIsDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
const storedLikedSchemes = localStorage.getItem('likedSchemes');
|
||||||
target="_blank"
|
const storedDislikedSchemes = localStorage.getItem('dislikedSchemes');
|
||||||
rel="noopener noreferrer"
|
if (storedLikedSchemes) {
|
||||||
>
|
setLikedSchemes(JSON.parse(storedLikedSchemes));
|
||||||
<Image
|
}
|
||||||
className="dark:invert"
|
if (storedDislikedSchemes) {
|
||||||
src="https://nextjs.org/icons/vercel.svg"
|
setDislikedSchemes(JSON.parse(storedDislikedSchemes));
|
||||||
alt="Vercel logomark"
|
}
|
||||||
width={20}
|
}, []);
|
||||||
height={20}
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.classList.toggle('dark', isDarkMode);
|
||||||
|
}, [isDarkMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('likedSchemes', JSON.stringify(likedSchemes));
|
||||||
|
}, [likedSchemes]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('dislikedSchemes', JSON.stringify(dislikedSchemes));
|
||||||
|
}, [dislikedSchemes]);
|
||||||
|
|
||||||
|
const generateNewSchemes = () => {
|
||||||
|
const newSchemes = [
|
||||||
|
knownSchemes[Math.floor(Math.random() * knownSchemes.length)],
|
||||||
|
generateRandomScheme(),
|
||||||
|
likedSchemes.length > 0 ? generateSchemeFromGeneticAlgorithm(likedSchemes, dislikedSchemes) : generateRandomScheme()
|
||||||
|
];
|
||||||
|
setSchemes(newSchemes);
|
||||||
|
setSelectedSchemes([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSchemeSelect = (index: number) => {
|
||||||
|
setSelectedSchemes(prev => {
|
||||||
|
if (prev.includes(index)) {
|
||||||
|
return prev.filter(i => i !== index);
|
||||||
|
} else if (prev.length < 2) {
|
||||||
|
return [...prev, index];
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAction = () => {
|
||||||
|
if (selectedSchemes.length === 2) {
|
||||||
|
const crossedScheme = crossSchemes(schemes[selectedSchemes[0]], schemes[selectedSchemes[1]]);
|
||||||
|
setLikedSchemes(prev => [...prev, schemes[selectedSchemes[0]], schemes[selectedSchemes[1]]]);
|
||||||
|
setSchemes([crossedScheme, knownSchemes[Math.floor(Math.random() * knownSchemes.length)], generateSchemeFromGeneticAlgorithm(likedSchemes, dislikedSchemes)]);
|
||||||
|
setSelectedSchemes([]);
|
||||||
|
} else {
|
||||||
|
setDislikedSchemes(prev => [...prev, ...schemes]);
|
||||||
|
generateNewSchemes();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDarkMode = () => {
|
||||||
|
setIsDarkMode(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)] dark:bg-gray-900 dark:text-white transition-colors duration-300">
|
||||||
|
<header className="flex justify-between items-center mb-8">
|
||||||
|
<h1 className="text-2xl font-bold">Terminal Color Scheme Generator</h1>
|
||||||
|
<SettingsIcon onClick={() => setIsSettingsOpen(true)} />
|
||||||
|
</header>
|
||||||
|
<main className="flex flex-col gap-8 items-center">
|
||||||
|
<div className="flex flex-wrap justify-center gap-8">
|
||||||
|
{schemes.map((scheme, index) => (
|
||||||
|
<ColorSchemeCard
|
||||||
|
key={index}
|
||||||
|
scheme={scheme}
|
||||||
|
isSelected={selectedSchemes.includes(index)}
|
||||||
|
onSelect={() => handleSchemeSelect(index)}
|
||||||
/>
|
/>
|
||||||
Deploy now
|
))}
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Read our docs
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ActionButton
|
||||||
|
onClick={handleAction}
|
||||||
|
label={selectedSchemes.length === 2 ? 'Mix' : 'Shuffle'}
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
<Settings
|
||||||
<a
|
isDarkMode={isDarkMode}
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
onToggleDarkMode={toggleDarkMode}
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
isOpen={isSettingsOpen}
|
||||||
target="_blank"
|
onClose={() => setIsSettingsOpen(false)}
|
||||||
rel="noopener noreferrer"
|
/>
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="https://nextjs.org/icons/file.svg"
|
|
||||||
alt="File icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Learn
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="https://nextjs.org/icons/window.svg"
|
|
||||||
alt="Window icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Examples
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="https://nextjs.org/icons/globe.svg"
|
|
||||||
alt="Globe icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Go to nextjs.org →
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
150
app/utils/colorSchemes.ts
Normal file
150
app/utils/colorSchemes.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
type Color = string; // Hex color code
|
||||||
|
type ColorScheme = {
|
||||||
|
name: string;
|
||||||
|
colors: {
|
||||||
|
primary: { background: Color; foreground: Color };
|
||||||
|
normal: {
|
||||||
|
black: Color; red: Color; green: Color; yellow: Color;
|
||||||
|
blue: Color; magenta: Color; cyan: Color; white: Color;
|
||||||
|
};
|
||||||
|
bright: {
|
||||||
|
black: Color; red: Color; green: Color; yellow: Color;
|
||||||
|
blue: Color; magenta: Color; cyan: Color; white: Color;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
import knownSchemesData from '../../formatted_themes.json';
|
||||||
|
|
||||||
|
const knownSchemes: ColorScheme[] = knownSchemesData;
|
||||||
|
|
||||||
|
function generateRandomColor(): Color {
|
||||||
|
return '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateCreativeName(): string {
|
||||||
|
const adjectives = ['Cosmic', 'Neon', 'Mystic', 'Retro', 'Cyber', 'Ethereal', 'Vibrant', 'Dreamy', 'Futuristic', 'Nostalgic'];
|
||||||
|
const nouns = ['Sunset', 'Aurora', 'Galaxy', 'Ocean', 'Forest', 'Desert', 'Nebula', 'Horizon', 'Oasis', 'Metropolis'];
|
||||||
|
return `${adjectives[Math.floor(Math.random() * adjectives.length)]} ${nouns[Math.floor(Math.random() * nouns.length)]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRandomScheme(): ColorScheme {
|
||||||
|
let x = {
|
||||||
|
name: generateCreativeName(),
|
||||||
|
colors: {
|
||||||
|
primary: { background: generateRandomColor(), foreground: generateRandomColor() },
|
||||||
|
normal: {
|
||||||
|
black: generateRandomColor(), red: generateRandomColor(), green: generateRandomColor(), yellow: generateRandomColor(),
|
||||||
|
blue: generateRandomColor(), magenta: generateRandomColor(), cyan: generateRandomColor(), white: generateRandomColor()
|
||||||
|
},
|
||||||
|
bright: {
|
||||||
|
black: generateRandomColor(), red: generateRandomColor(), green: generateRandomColor(), yellow: generateRandomColor(),
|
||||||
|
blue: generateRandomColor(), magenta: generateRandomColor(), cyan: generateRandomColor(), white: generateRandomColor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
x.colors.primary.background = x.colors.normal.black;
|
||||||
|
x.colors.primary.foreground = x.colors.bright.white;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function crossTitles(title1: string, title2: string): string {
|
||||||
|
const words1 = title1.split(' ');
|
||||||
|
const words2 = title2.split(' ');
|
||||||
|
const firstWord = Math.random() < 0.5 ? words1[0] : words2[1];
|
||||||
|
const secondWord = Math.random() < 0.5 ? words2[0] : words1[1];
|
||||||
|
return `${firstWord} ${secondWord}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function crossSchemes(scheme1: ColorScheme, scheme2: ColorScheme): ColorScheme {
|
||||||
|
const crossColor = (color1: Color, color2: Color): Color => {
|
||||||
|
const r = Math.round((parseInt(color1.slice(1, 3), 16) + parseInt(color2.slice(1, 3), 16)) / 2);
|
||||||
|
const g = Math.round((parseInt(color1.slice(3, 5), 16) + parseInt(color2.slice(3, 5), 16)) / 2);
|
||||||
|
const b = Math.round((parseInt(color1.slice(5, 7), 16) + parseInt(color2.slice(5, 7), 16)) / 2);
|
||||||
|
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: crossTitles(scheme1.name, scheme2.name),
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
background: crossColor(scheme1.colors.primary.background, scheme2.colors.primary.background),
|
||||||
|
foreground: crossColor(scheme1.colors.primary.foreground, scheme2.colors.primary.foreground)
|
||||||
|
},
|
||||||
|
normal: {
|
||||||
|
black: crossColor(scheme1.colors.normal.black, scheme2.colors.normal.black),
|
||||||
|
red: crossColor(scheme1.colors.normal.red, scheme2.colors.normal.red),
|
||||||
|
green: crossColor(scheme1.colors.normal.green, scheme2.colors.normal.green),
|
||||||
|
yellow: crossColor(scheme1.colors.normal.yellow, scheme2.colors.normal.yellow),
|
||||||
|
blue: crossColor(scheme1.colors.normal.blue, scheme2.colors.normal.blue),
|
||||||
|
magenta: crossColor(scheme1.colors.normal.magenta, scheme2.colors.normal.magenta),
|
||||||
|
cyan: crossColor(scheme1.colors.normal.cyan, scheme2.colors.normal.cyan),
|
||||||
|
white: crossColor(scheme1.colors.normal.white, scheme2.colors.normal.white)
|
||||||
|
},
|
||||||
|
bright: {
|
||||||
|
black: crossColor(scheme1.colors.bright.black, scheme2.colors.bright.black),
|
||||||
|
red: crossColor(scheme1.colors.bright.red, scheme2.colors.bright.red),
|
||||||
|
green: crossColor(scheme1.colors.bright.green, scheme2.colors.bright.green),
|
||||||
|
yellow: crossColor(scheme1.colors.bright.yellow, scheme2.colors.bright.yellow),
|
||||||
|
blue: crossColor(scheme1.colors.bright.blue, scheme2.colors.bright.blue),
|
||||||
|
magenta: crossColor(scheme1.colors.bright.magenta, scheme2.colors.bright.magenta),
|
||||||
|
cyan: crossColor(scheme1.colors.bright.cyan, scheme2.colors.bright.cyan),
|
||||||
|
white: crossColor(scheme1.colors.bright.white, scheme2.colors.bright.white)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mutateColor(color: Color): Color {
|
||||||
|
const r = parseInt(color.slice(1, 3), 16);
|
||||||
|
const g = parseInt(color.slice(3, 5), 16);
|
||||||
|
const b = parseInt(color.slice(5, 7), 16);
|
||||||
|
|
||||||
|
const mutateComponent = (component: number) => {
|
||||||
|
const mutation = Math.floor(Math.random() * 51) - 25; // Random number between -25 and 25
|
||||||
|
return Math.max(0, Math.min(255, component + mutation));
|
||||||
|
};
|
||||||
|
|
||||||
|
const newR = mutateComponent(r);
|
||||||
|
const newG = mutateComponent(g);
|
||||||
|
const newB = mutateComponent(b);
|
||||||
|
|
||||||
|
return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSchemeFromGeneticAlgorithm(likedSchemes: ColorScheme[], dislikedSchemes: ColorScheme[]): ColorScheme {
|
||||||
|
if (likedSchemes.length === 0) {
|
||||||
|
return generateRandomScheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentScheme = likedSchemes[Math.floor(Math.random() * likedSchemes.length)];
|
||||||
|
const newScheme: ColorScheme = JSON.parse(JSON.stringify(parentScheme)); // Deep copy
|
||||||
|
|
||||||
|
// Mutate colors
|
||||||
|
Object.keys(newScheme.colors).forEach((colorGroup) => {
|
||||||
|
Object.keys(newScheme.colors[colorGroup]).forEach((colorName) => {
|
||||||
|
if (Math.random() < 0.3) { // 30% chance of mutation
|
||||||
|
newScheme.colors[colorGroup][colorName] = mutateColor(newScheme.colors[colorGroup][colorName]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Avoid similarities with disliked schemes
|
||||||
|
dislikedSchemes.forEach(dislikedScheme => {
|
||||||
|
Object.keys(newScheme.colors).forEach((colorGroup) => {
|
||||||
|
Object.keys(newScheme.colors[colorGroup]).forEach((colorName) => {
|
||||||
|
if (newScheme.colors[colorGroup][colorName] === dislikedScheme.colors[colorGroup][colorName]) {
|
||||||
|
newScheme.colors[colorGroup][colorName] = mutateColor(newScheme.colors[colorGroup][colorName]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
newScheme.name = generateCreativeName();
|
||||||
|
return newScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { ColorScheme };
|
||||||
|
export { knownSchemes, generateRandomScheme, crossSchemes, generateSchemeFromGeneticAlgorithm };
|
32
app/utils/yamlExport.ts
Normal file
32
app/utils/yamlExport.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { ColorScheme } from './colorSchemes';
|
||||||
|
|
||||||
|
export function generateYAML(scheme: ColorScheme): string {
|
||||||
|
return `colors:
|
||||||
|
# Default colors
|
||||||
|
primary:
|
||||||
|
background: '${scheme.colors.primary.background}'
|
||||||
|
foreground: '${scheme.colors.primary.foreground}'
|
||||||
|
|
||||||
|
# Normal colors
|
||||||
|
normal:
|
||||||
|
black: '${scheme.colors.normal.black}'
|
||||||
|
red: '${scheme.colors.normal.red}'
|
||||||
|
green: '${scheme.colors.normal.green}'
|
||||||
|
yellow: '${scheme.colors.normal.yellow}'
|
||||||
|
blue: '${scheme.colors.normal.blue}'
|
||||||
|
magenta: '${scheme.colors.normal.magenta}'
|
||||||
|
cyan: '${scheme.colors.normal.cyan}'
|
||||||
|
white: '${scheme.colors.normal.white}'
|
||||||
|
|
||||||
|
# Bright colors
|
||||||
|
bright:
|
||||||
|
black: '${scheme.colors.bright.black}'
|
||||||
|
red: '${scheme.colors.bright.red}'
|
||||||
|
green: '${scheme.colors.bright.green}'
|
||||||
|
yellow: '${scheme.colors.bright.yellow}'
|
||||||
|
blue: '${scheme.colors.bright.blue}'
|
||||||
|
magenta: '${scheme.colors.bright.magenta}'
|
||||||
|
cyan: '${scheme.colors.bright.cyan}'
|
||||||
|
white: '${scheme.colors.bright.white}'
|
||||||
|
`;
|
||||||
|
}
|
9485
formatted_themes.json
Normal file
9485
formatted_themes.json
Normal file
File diff suppressed because it is too large
Load Diff
29
package-lock.json
generated
29
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "14.2.8",
|
"next": "14.2.8",
|
||||||
|
"prism-react-renderer": "^2.4.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18"
|
||||||
},
|
},
|
||||||
@ -507,6 +508,12 @@
|
|||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/prismjs": {
|
||||||
|
"version": "1.26.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz",
|
||||||
|
"integrity": "sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.12",
|
"version": "15.7.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||||
@ -1271,6 +1278,15 @@
|
|||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@ -4128,6 +4144,19 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prism-react-renderer": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/prismjs": "^1.26.0",
|
||||||
|
"clsx": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
11
package.json
11
package.json
@ -9,18 +9,19 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"next": "14.2.8",
|
||||||
|
"prism-react-renderer": "^2.4.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18"
|
||||||
"next": "14.2.8"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.2.8",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"eslint": "^8",
|
"typescript": "^5"
|
||||||
"eslint-config-next": "14.2.8"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,15 @@ const config: Config = {
|
|||||||
background: "var(--background)",
|
background: "var(--background)",
|
||||||
foreground: "var(--foreground)",
|
foreground: "var(--foreground)",
|
||||||
},
|
},
|
||||||
|
animation: {
|
||||||
|
float: 'float 3s ease-in-out infinite',
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
float: {
|
||||||
|
'0%, 100%': { transform: 'translateY(0)' },
|
||||||
|
'50%': { transform: 'translateY(-10px)' },
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
75
themes_downloader.py
Normal file
75
themes_downloader.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Base URL for the Gogh JSON files in the GitHub repository
|
||||||
|
base_url = "https://raw.githubusercontent.com/Gogh-Co/Gogh/master/json/"
|
||||||
|
|
||||||
|
# GitHub API URL to list the contents of the "json" directory
|
||||||
|
api_url = "https://api.github.com/repos/Gogh-Co/Gogh/contents/json"
|
||||||
|
|
||||||
|
# Function to scrape all theme filenames from the GitHub API
|
||||||
|
def fetch_theme_filenames():
|
||||||
|
response = requests.get(api_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
files = response.json()
|
||||||
|
# Filter JSON files and return their names
|
||||||
|
return [file["name"] for file in files if file["name"].endswith(".json")]
|
||||||
|
else:
|
||||||
|
print(f"Failed to fetch theme filenames from GitHub API, status code: {response.status_code}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Helper function to map Gogh colors to the desired format
|
||||||
|
def map_theme_colors(theme_data):
|
||||||
|
return {
|
||||||
|
"name": theme_data.get("name", "Unknown Theme"),
|
||||||
|
"colors": {
|
||||||
|
"primary": {
|
||||||
|
"background": theme_data.get("background", "#000000"),
|
||||||
|
"foreground": theme_data.get("foreground", "#FFFFFF"),
|
||||||
|
},
|
||||||
|
"normal": {
|
||||||
|
"black": theme_data.get("color_01", "#000000"),
|
||||||
|
"red": theme_data.get("color_02", "#000000"),
|
||||||
|
"green": theme_data.get("color_03", "#000000"),
|
||||||
|
"yellow": theme_data.get("color_04", "#000000"),
|
||||||
|
"blue": theme_data.get("color_05", "#000000"),
|
||||||
|
"magenta": theme_data.get("color_06", "#000000"),
|
||||||
|
"cyan": theme_data.get("color_07", "#000000"),
|
||||||
|
"white": theme_data.get("color_08", "#000000"),
|
||||||
|
},
|
||||||
|
"bright": {
|
||||||
|
"black": theme_data.get("color_09", "#000000"),
|
||||||
|
"red": theme_data.get("color_10", "#000000"),
|
||||||
|
"green": theme_data.get("color_11", "#000000"),
|
||||||
|
"yellow": theme_data.get("color_12", "#000000"),
|
||||||
|
"blue": theme_data.get("color_13", "#000000"),
|
||||||
|
"magenta": theme_data.get("color_14", "#000000"),
|
||||||
|
"cyan": theme_data.get("color_15", "#000000"),
|
||||||
|
"white": theme_data.get("color_16", "#000000"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# List to hold all the themes
|
||||||
|
themes = []
|
||||||
|
|
||||||
|
# Fetch all theme filenames from the GitHub API
|
||||||
|
theme_filenames = fetch_theme_filenames()
|
||||||
|
|
||||||
|
# Loop through the list of theme filenames and fetch each theme
|
||||||
|
for filename in theme_filenames:
|
||||||
|
theme_url = base_url + filename
|
||||||
|
response = requests.get(theme_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
theme_data = response.json()
|
||||||
|
formatted_theme = map_theme_colors(theme_data)
|
||||||
|
themes.append(formatted_theme)
|
||||||
|
else:
|
||||||
|
print(f"Failed to fetch {filename}")
|
||||||
|
|
||||||
|
# Output the formatted themes as a JSON array
|
||||||
|
output_file = "formatted_themes.json"
|
||||||
|
with open(output_file, "w") as out_file:
|
||||||
|
json.dump(themes, out_file, indent=2)
|
||||||
|
|
||||||
|
print(f"Formatted themes saved to {output_file}")
|
Loading…
Reference in New Issue
Block a user