🎉 Initial version

This commit is contained in:
2024-04-23 00:15:21 +02:00
commit 0d9074bd4a
14 changed files with 2220 additions and 0 deletions

24
.gitignore vendored Normal file
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?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["svelte.svelte-vscode"]
}

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Guess the Color!</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

1830
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "color-guess",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@tsconfig/svelte": "^5.0.2",
"svelte": "^4.2.12",
"svelte-check": "^3.6.7",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"vite": "^5.2.0",
"vite-plugin-singlefile": "^2.0.1"
}
}

237
src/App.svelte Normal file
View File

@ -0,0 +1,237 @@
<script lang="ts">
import Button from "./Button.svelte";
function randomRgbColor(): number[] {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return [r, g, b];
}
function cmykToRgb(cmyk: number[]): number[] {
return [
Math.round(255 * (1 - cmyk[0] / 100) * (1 - cmyk[3] / 100)),
Math.round(255 * (1 - cmyk[1] / 100) * (1 - cmyk[3] / 100)),
Math.round(255 * (1 - cmyk[2] / 100) * (1 - cmyk[3] / 100)),
];
}
function rgbToCmyk(rgb: number[]): number[] {
let r = rgb[0] / 255;
let g = rgb[1] / 255;
let b = rgb[2] / 255;
let k = 1 - Math.max(r, g, b);
let c = Math.round(((1 - r - k) / (1 - k)) * 100);
let m = Math.round(((1 - g - k) / (1 - k)) * 100);
let y = Math.round(((1 - b - k) / (1 - k)) * 100);
return [c || 0, m || 0, y || 0, Math.round(k * 100)];
}
enum Mode {
Rgb,
Cmyk,
}
let mode: Mode = Mode.Rgb;
let targetColor = randomRgbColor();
let guessColor = [0, 0, 0];
let guessCmykColor = [0, 0, 0, 0];
let guessScore: number | undefined = undefined;
let sideBySide = false;
let realtimeScore = false;
$: {
if (mode == Mode.Cmyk) guessColor = cmykToRgb(guessCmykColor);
else if (mode == Mode.Rgb) guessCmykColor = rgbToCmyk(guessColor);
}
$: if (realtimeScore) guessScore = score(targetColor, guessColor);
// From: https://github.com/susam/myrgb/blob/main/myrgb.html#L206
function score(rgb1: number[], rgb2: number[]) {
const maxRErr = Math.max(rgb1[0], 15 - rgb1[0]);
const maxGErr = Math.max(rgb1[1], 15 - rgb1[1]);
const maxBErr = Math.max(rgb1[2], 15 - rgb1[2]);
const maxDist = Math.sqrt(
maxRErr * maxRErr + maxGErr * maxGErr + maxBErr * maxBErr
);
const rErr = Math.abs(rgb2[0] - rgb1[0]);
const gErr = Math.abs(rgb2[1] - rgb1[1]);
const bErr = Math.abs(rgb2[2] - rgb1[2]);
const dist = Math.sqrt(rErr * rErr + gErr * gErr + bErr * bErr);
return Math.max(0, Math.floor(100 * (1 - dist / maxDist)));
}
</script>
<main>
<div class="content">
<!-- Title -->
<h1>
Guess the <span class="gradient">color</span>
</h1>
<!-- Color preview -->
<div class="color_grid">
{#if sideBySide}
<div
class="color"
style:background-color="rgb({guessColor.join(",")})"
></div>
{/if}
<div class="color" style:background-color="rgb({targetColor.join(",")})">
{#if guessScore != undefined}
<div class="score">{guessScore}%</div>
{/if}
</div>
</div>
<!-- Sliders -->
<div class="slider_container">
{#if mode == Mode.Rgb}
<!-- RGB Color selection -->
{#each guessColor as color, i}
<span style:color={["red", "green", "blue"][i]}>
{["R", "G", "B"][i]}
</span>
<span class="slider_value">{color.toString().padStart(3, "0")}</span>
<input type="range" min="0" max="255" bind:value={color} />
{/each}
{:else if mode == Mode.Cmyk}
<!-- CMYK Color selection -->
{#each guessCmykColor as color, i}
<span style:color={["cyan", "magenta", "yellow", "gray"][i]}>
{["C", "M", "Y", "K"][i]}
</span>
<span class="slider_value">{color.toString().padStart(3, "0")}%</span>
<input type="range" min="0" max="100" bind:value={color} />
{/each}
{/if}
</div>
<!-- Buttons -->
<div class="buttons">
<Button
text="Check input"
emoji="✅"
callback={() => {
guessScore = score(targetColor, guessColor);
}}
/>
<Button
text="New Color"
emoji="🔄"
callback={() => {
if (confirm("Generate new color?")) {
targetColor = randomRgbColor();
guessColor = [0, 0, 0];
guessCmykColor = [0, 0, 0, 0];
}
}}
/>
<Button
text="Change inout method"
emoji="🔀"
callback={() => {
if (mode == Mode.Rgb) mode = Mode.Cmyk;
else mode = Mode.Rgb;
}}
/>
<hr />
<Button
text="Show solution"
emoji="⁉️"
callback={() => {
if (confirm("Realy show solution?")) {
if (mode == Mode.Rgb) {
guessColor = structuredClone(targetColor);
} else if (mode == Mode.Cmyk) {
guessCmykColor = rgbToCmyk(targetColor);
}
}
}}
/>
<Button
text="Toggle side by side view"
emoji="⏯️"
callback={() => {
sideBySide = !sideBySide;
}}
/>
<Button
text="Update score in realtime"
emoji={realtimeScore ? "⏸️" : "▶️"}
callback={() => {
realtimeScore = !realtimeScore;
}}
/>
</div>
</div>
</main>
<style>
main {
display: flex;
justify-content: center;
padding: 30px 15px;
}
h1 {
margin-top: 0px;
}
.gradient {
background: linear-gradient(
to right,
#0073e6 0%,
#80d9e6 50%,
#c0e0e6 75%,
#d8f0e6 100%
);
background-clip: text;
-webkit-text-fill-color: transparent;
}
.content {
width: 100%;
max-width: 500px;
}
.color_grid {
display: flex;
flex-direction: row;
gap: 10px;
}
.color {
width: 100%;
height: 30vh;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.score {
font-size: 25px;
opacity: 0.7;
color: black;
font-weight: bold;
}
.slider_container {
margin-top: 40px;
display: grid;
grid-template-columns: min-content min-content auto;
grid-template-rows: 1fr 1fr 1fr;
gap: 10px;
row-gap: 20px;
}
input {
width: 100%;
}
.buttons {
margin-top: 20px;
}
</style>

32
src/Button.svelte Normal file
View File

@ -0,0 +1,32 @@
<script lang="ts">
import type { MouseEventHandler } from "svelte/elements";
export let emoji = "";
export let text = "";
export let callback: MouseEventHandler<HTMLButtonElement>;
</script>
<button class="button" on:click={callback}>
<span>{emoji}</span>
<span>{text}</span>
</button>
<style>
.button {
display: block;
outline: unset;
background: unset;
color: unset;
border: unset;
cursor: pointer;
padding: unset;
font-size: unset;
padding: 5px 5px;
margin: 5px 0px;
}
.button:hover {
background-color: rgb(46, 46, 46);
border-radius: 10px;
}
</style>

5
src/app.css Normal file
View File

@ -0,0 +1,5 @@
body {
background-color: #000;
color: white;
font-family: Arial, Helvetica, sans-serif;
}

8
src/main.ts Normal file
View File

@ -0,0 +1,8 @@
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')!,
})
export default app

2
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

7
svelte.config.js Normal file
View File

@ -0,0 +1,7 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}

20
tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true
},
"include": ["vite.config.ts"]
}

8
vite.config.ts Normal file
View File

@ -0,0 +1,8 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import { viteSingleFile } from "vite-plugin-singlefile"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte(), viteSingleFile()],
})