mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
✨ feat: Add game card with better colors
This commit is contained in:
@@ -56,6 +56,33 @@ export default component$(() => {
|
|||||||
// teams: 10
|
// teams: 10
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Card
|
||||||
|
game={{
|
||||||
|
// release_date: 1478710740000,
|
||||||
|
// compatibility: 'playable',
|
||||||
|
name: 'Grand Theft Auto V',
|
||||||
|
id: 271590,
|
||||||
|
// teams: 10
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
game={{
|
||||||
|
// release_date: 1478710740000,
|
||||||
|
// compatibility: 'playable',
|
||||||
|
name: 'Apex Legends',
|
||||||
|
id: 1172470,
|
||||||
|
// teams: 10
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
game={{
|
||||||
|
// release_date: 1478710740000,
|
||||||
|
// compatibility: 'playable',
|
||||||
|
name: "Tom Clancy's Rainbow Six Siege",
|
||||||
|
id: 359550,
|
||||||
|
// teams: 10
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -13,45 +13,39 @@ export const Card = component$(({ game }: Props) => {
|
|||||||
const ringColor = useSignal<string | undefined>(undefined);
|
const ringColor = useSignal<string | undefined>(undefined);
|
||||||
const imgRef = useSignal<HTMLImageElement>();
|
const imgRef = useSignal<HTMLImageElement>();
|
||||||
|
|
||||||
// Function to extract dominant color
|
|
||||||
const extractColor = $((img: HTMLImageElement) => {
|
const extractColor = $((img: HTMLImageElement) => {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx) return "rgb(200,200,200)"; // Fallback light gray
|
||||||
|
|
||||||
canvas.width = img.naturalWidth;
|
canvas.width = img.naturalWidth;
|
||||||
canvas.height = img.naturalHeight;
|
canvas.height = img.naturalHeight;
|
||||||
ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
|
ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
|
||||||
|
|
||||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||||
const data = imageData.data;
|
let r = 0, g = 0, b = 0, count = 0;
|
||||||
|
|
||||||
let r = 0, g = 0, b = 0;
|
for (let i = 0; i < imageData.length; i += 4) {
|
||||||
|
// Only consider brighter pixels
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
if (imageData[i] > 150 || imageData[i + 1] > 150 || imageData[i + 2] > 150) {
|
||||||
r += data[i];
|
r += imageData[i];
|
||||||
g += data[i + 1];
|
g += imageData[i + 1];
|
||||||
b += data[i + 2];
|
b += imageData[i + 2];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r = Math.floor(r / (data.length / 4));
|
if (count === 0) return "rgb(200,200,200)"; // Fallback if no bright pixels found
|
||||||
g = Math.floor(g / (data.length / 4));
|
|
||||||
b = Math.floor(b / (data.length / 4));
|
|
||||||
|
|
||||||
return `rgb(${r},${g},${b})`;
|
r = Math.floor(r / count);
|
||||||
});
|
g = Math.floor(g / count);
|
||||||
|
b = Math.floor(b / count);
|
||||||
|
|
||||||
// Function to darken a color
|
// Ensure the color is light enough
|
||||||
const darkenColor = $((color: string | undefined, amount: number) => {
|
const minBrightness = 100;
|
||||||
if (!color) return color;
|
r = Math.max(r, minBrightness);
|
||||||
|
g = Math.max(g, minBrightness);
|
||||||
const rgb = color.match(/\d+/g);
|
b = Math.max(b, minBrightness);
|
||||||
if (!rgb || rgb.length !== 3) return color;
|
|
||||||
|
|
||||||
const darkenChannel = (channel: number) => Math.max(0, channel - amount);
|
|
||||||
const r = darkenChannel(parseInt(rgb[0]));
|
|
||||||
const g = darkenChannel(parseInt(rgb[1]));
|
|
||||||
const b = darkenChannel(parseInt(rgb[2]));
|
|
||||||
|
|
||||||
return `rgb(${r},${g},${b})`;
|
return `rgb(${r},${g},${b})`;
|
||||||
});
|
});
|
||||||
@@ -61,19 +55,14 @@ export const Card = component$(({ game }: Props) => {
|
|||||||
|
|
||||||
const img = imgRef.value;
|
const img = imgRef.value;
|
||||||
if (img) {
|
if (img) {
|
||||||
|
const processImage = async () => {
|
||||||
|
backgroundColor.value = await extractColor(img);
|
||||||
|
};
|
||||||
|
|
||||||
if (img.complete) {
|
if (img.complete) {
|
||||||
const extractedColor = await extractColor(img);
|
await processImage();
|
||||||
backgroundColor.value = extractedColor;
|
|
||||||
ringColor.value = await darkenColor(extractedColor, 30);
|
|
||||||
} else {
|
} else {
|
||||||
await new Promise<void>((resolve) => {
|
img.onload = processImage;
|
||||||
img.onload = async () => {
|
|
||||||
const extractedColor = await extractColor(img);
|
|
||||||
backgroundColor.value = extractedColor;
|
|
||||||
ringColor.value = await darkenColor(extractedColor, 30);
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -82,12 +71,10 @@ export const Card = component$(({ game }: Props) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: backgroundColor.value,
|
backgroundColor: backgroundColor.value,
|
||||||
"--tw-ring-color": ringColor.value
|
|
||||||
}}
|
}}
|
||||||
class="bg-gray-200/70 min-w-[250px] backdrop-blur-sm ring-gray-300 select-none w-full group dark:ring-gray-700 ring dark:bg-gray-800/70 group rounded-3xl dark:text-primary-50/70 text-primary-950/70 duration-300 transition-colors flex flex-col">
|
class="min-w-[250px] group cursor-pointer backdrop-blur-sm select-none w-full group rounded-3xl text-primary-950/70 duration-300 transition-colors flex flex-col">
|
||||||
<header class="flex gap-4 justify-between p-4">
|
<header class="flex gap-4 justify-between p-4">
|
||||||
<div
|
<div class="flex relative pr-[22px] overflow-hidden overflow-ellipsis whitespace-nowrap" >
|
||||||
class="flex relative pr-[22px] overflow-hidden text-white overflow-ellipsis whitespace-nowrap" >
|
|
||||||
<h3 class="overflow-hidden overflow-ellipsis whitespace-nowrap">{game.name}</h3>
|
<h3 class="overflow-hidden overflow-ellipsis whitespace-nowrap">{game.name}</h3>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -95,9 +82,9 @@ export const Card = component$(({ game }: Props) => {
|
|||||||
<img
|
<img
|
||||||
ref={imgRef}
|
ref={imgRef}
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
class="rounded-2xl shadow-2xl shadow-gray-900"
|
class="rounded-2xl ring-2 aspect-[2/3] w-full max-w-[90%] ring-gray-900/70 group-hover:scale-105 duration-200 transition-transform shadow-2xl shadow-gray-900"
|
||||||
width={270}
|
width={250}
|
||||||
height={215}
|
height={200}
|
||||||
alt={game.name}
|
alt={game.name}
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user