🎉 Initial version
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal 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
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["svelte.svelte-vscode"]
|
||||||
|
}
|
12
index.html
Normal file
12
index.html
Normal 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
1830
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
package.json
Normal file
22
package.json
Normal 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
237
src/App.svelte
Normal 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
32
src/Button.svelte
Normal 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
5
src/app.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
body {
|
||||||
|
background-color: #000;
|
||||||
|
color: white;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
8
src/main.ts
Normal file
8
src/main.ts
Normal 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
2
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
|
/// <reference types="vite/client" />
|
7
svelte.config.js
Normal file
7
svelte.config.js
Normal 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
20
tsconfig.json
Normal 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
10
tsconfig.node.json
Normal 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
8
vite.config.ts
Normal 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()],
|
||||||
|
})
|
Reference in New Issue
Block a user