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 {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-geist-sans), Arial, sans-serif;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@ -25,3 +25,7 @@ body {
|
||||
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() {
|
||||
return (
|
||||
<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)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="https://nextjs.org/icons/next.svg"
|
||||
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>
|
||||
const [schemes, setSchemes] = useState<ColorScheme[]>([]);
|
||||
const [selectedSchemes, setSelectedSchemes] = useState<number[]>([]);
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const [likedSchemes, setLikedSchemes] = useState<ColorScheme[]>([]);
|
||||
const [dislikedSchemes, setDislikedSchemes] = useState<ColorScheme[]>([]);
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
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"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="https://nextjs.org/icons/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
useEffect(() => {
|
||||
generateNewSchemes();
|
||||
setIsDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
const storedLikedSchemes = localStorage.getItem('likedSchemes');
|
||||
const storedDislikedSchemes = localStorage.getItem('dislikedSchemes');
|
||||
if (storedLikedSchemes) {
|
||||
setLikedSchemes(JSON.parse(storedLikedSchemes));
|
||||
}
|
||||
if (storedDislikedSchemes) {
|
||||
setDislikedSchemes(JSON.parse(storedDislikedSchemes));
|
||||
}
|
||||
}, []);
|
||||
|
||||
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>
|
||||
<ActionButton
|
||||
onClick={handleAction}
|
||||
label={selectedSchemes.length === 2 ? 'Mix' : 'Shuffle'}
|
||||
/>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?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/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>
|
||||
<Settings
|
||||
isDarkMode={isDarkMode}
|
||||
onToggleDarkMode={toggleDarkMode}
|
||||
isOpen={isSettingsOpen}
|
||||
onClose={() => setIsSettingsOpen(false)}
|
||||
/>
|
||||
</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",
|
||||
"dependencies": {
|
||||
"next": "14.2.8",
|
||||
"prism-react-renderer": "^2.4.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
@ -507,6 +508,12 @@
|
||||
"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": {
|
||||
"version": "15.7.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||
@ -1271,6 +1278,15 @@
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@ -4128,6 +4144,19 @@
|
||||
"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": {
|
||||
"version": "15.8.1",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "14.2.8",
|
||||
"prism-react-renderer": "^2.4.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"next": "14.2.8"
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.8",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.8"
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,15 @@ const config: Config = {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
animation: {
|
||||
float: 'float 3s ease-in-out infinite',
|
||||
},
|
||||
keyframes: {
|
||||
float: {
|
||||
'0%, 100%': { transform: 'translateY(0)' },
|
||||
'50%': { transform: 'translateY(-10px)' },
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
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