✨ feat: Add /home (#111)
BIN
apps/www/public/portal/play_button_disabled_bg.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
apps/www/public/portal/play_button_focused_bg.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
apps/www/public/portal/play_button_idle.png
Normal file
|
After Width: | Height: | Size: 1022 KiB |
BIN
apps/www/public/portal/play_button_intro.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
apps/www/public/portal/play_icon_exit.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
apps/www/public/portal/play_icon_intro.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
apps/www/public/portal/play_icon_loop.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
apps/www/public/portal/portal_background_placeholder.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@@ -38,7 +38,7 @@ export default component$(() => {
|
||||
lang="en">
|
||||
<RouterOutlet />
|
||||
{/* {!isDev && <ServiceWorkerRegister />} */}
|
||||
<ServiceWorkerRegister />
|
||||
<ServiceWorkerRegister />
|
||||
</body>
|
||||
</QwikCityProvider>
|
||||
</Fonts>
|
||||
|
||||
@@ -3,8 +3,8 @@ import { HomeNavBar, Card } from "@nestri/ui";
|
||||
|
||||
function getGreeting(): string {
|
||||
const hour = new Date().getHours();
|
||||
if (hour < 12) return "Good Morning";
|
||||
if (hour < 18) return "Good Afternoon";
|
||||
if (hour >= 5 && hour < 12) return "Good Morning";
|
||||
if (hour >= 12 && hour < 18) return "Good Afternoon";
|
||||
return "Good Evening";
|
||||
}
|
||||
|
||||
@@ -13,78 +13,95 @@ export default component$(() => {
|
||||
<>
|
||||
<HomeNavBar />
|
||||
<section class="flex flex-col gap-4 justify-center pt-20 items-center w-full text-left pb-4">
|
||||
<div class="flex flex-col gap-4 mx-auto max-w-xl w-full">
|
||||
<h1 class="text-5xl font-bold font-title">{getGreeting()}, Wanjohi</h1>
|
||||
<div class="flex flex-col gap-4 mx-auto max-w-2xl w-full">
|
||||
<h1 class="text-5xl font-bold font-title">{getGreeting()}, <span>Wanjohi</span></h1>
|
||||
<p class="dark:text-gray-50/70 text-gray-950/70 text-xl">What will you play today?</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="flex flex-col gap-4 justify-center pt-10 items-center w-full text-left pb-4">
|
||||
<div class="flex gap-4 mx-auto max-w-xl lg:max-w-2xl w-full">
|
||||
{/* <GameCard
|
||||
game={{
|
||||
release_date: 1478710740000,
|
||||
compatibility: 'playable',
|
||||
name: 'World of Tanks Blitz',
|
||||
appid: '444200',
|
||||
teams: 10
|
||||
}}
|
||||
/><GameCard
|
||||
game={{
|
||||
release_date: 1478710740000,
|
||||
compatibility: 'playable',
|
||||
name: 'World of Tanks Blitz',
|
||||
appid: '444200',
|
||||
teams: 10
|
||||
}}
|
||||
/> */}
|
||||
</div>
|
||||
<div class="gap-4 w-full max-w-xl lg:max-w-4xl grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card
|
||||
game={{
|
||||
// release_date: 1478710740000,
|
||||
// compatibility: 'playable',
|
||||
name: 'The Lord of the Rings: Return to Moria™',
|
||||
id: 2933130,
|
||||
// teams: 10
|
||||
}}
|
||||
/><Card
|
||||
game={{
|
||||
// release_date: 1478710740000,
|
||||
// compatibility: 'playable',
|
||||
name: 'Control Ultimate Edition',
|
||||
id: 870780,
|
||||
// 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>
|
||||
<ul class="gap-4 relative list-none w-full max-w-xl lg:max-w-4xl grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 after:pointer-events-none after:select-none after:bg-gradient-to-b after:from-transparent after:dark:to-gray-900 after:to-gray-100 after:fixed after:left-0 after:-bottom-[1px] after:z-10 after:backdrop-blur-sm after:h-[100px] after:w-full after:[-webkit-mask-image:linear-gradient(to_top,theme(colors.primary.100)_50%,transparent)] after:dark:[-webkit-mask-image:linear-gradient(to_top,theme(colors.primary.900)_50%,transparent)]">
|
||||
<li class="col-span-full">
|
||||
<Card
|
||||
size="large"
|
||||
titleWidth={55.61}
|
||||
titleHeight={100}
|
||||
game={{
|
||||
name: 'Control Ultimate Edition',
|
||||
id: 870780
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Card
|
||||
size="small"
|
||||
titleWidth={56.30}
|
||||
titleHeight={69.79}
|
||||
game={{
|
||||
name: 'Black Myth: Wukong',
|
||||
id: 2358720,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Card
|
||||
size="small"
|
||||
titleWidth={34.09}
|
||||
titleHeight={98.26}
|
||||
game={{
|
||||
name: 'The Lord of the Rings: Return to Moria™',
|
||||
id: 2933130,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Card
|
||||
size="small"
|
||||
titleWidth={48.77}
|
||||
titleHeight={100}
|
||||
game={{
|
||||
name: 'Grand Theft Auto V',
|
||||
id: 271590,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Card
|
||||
titleWidth={31.65}
|
||||
titleHeight={82.87}
|
||||
size="small"
|
||||
game={{
|
||||
name: 'Apex Legends',
|
||||
id: 1172470,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Card
|
||||
size="small"
|
||||
titleHeight={99.75}
|
||||
titleWidth={73.44}
|
||||
game={{
|
||||
name: "Tom Clancy's Rainbow Six Siege",
|
||||
id: 359550,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<nav class="w-full flex justify-center h-[100px] z-50 items-center gap-4 bg-transparent fixed -bottom-[1px] left-0 right-0">
|
||||
{/* <nav class="flex gap-4 w-max px-4 py-2 rounded-full shadow-2xl shadow-gray-950 bg-neutral-200 text-gray-900 dark:text-gray-100 dark:bg-neutral-800 ring-gray-300 dark:ring-gray-700 ring-1">
|
||||
<button class="text-xl font-title">
|
||||
<span class="material-symbols-outlined">
|
||||
home
|
||||
</span>
|
||||
</button>
|
||||
<button class="text-xl font-title">
|
||||
<span class="material-symbols-outlined">
|
||||
home
|
||||
</span>
|
||||
</button>
|
||||
</nav> */}
|
||||
</nav>
|
||||
</>
|
||||
)
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
import { Link, type DocumentHead } from "@builder.io/qwik-city";
|
||||
import { HeroSection, Cursor, MotionComponent, transition } from "@nestri/ui/react"
|
||||
import { NavBar, Footer } from "@nestri/ui"
|
||||
import { HeroSection, Cursor, MotionComponent, transition } from "@nestri/ui/react"
|
||||
import { NavBar, Footer, Modal } from "@nestri/ui"
|
||||
import { BasicImageLoader } from "@nestri/ui/image";
|
||||
|
||||
const features = [
|
||||
@@ -90,22 +90,70 @@ export default component$(() => {
|
||||
<>
|
||||
<NavBar />
|
||||
<HeroSection client:load>
|
||||
<button class="w-full max-w-xl rounded-xl flex items-center justify-between hover:bg-gray-200 dark:hover:bg-gray-800 transition-all gap-2 px-4 py-3 h-[45px] ring-2 ring-gray-300 dark:ring-gray-700 mx-auto text-gray-900/70 dark:text-gray-100/70 bg-white dark:bg-black">
|
||||
<span class="flex items-center gap-3 h-max justify-center overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-[18] flex-shrink-0" height="18" width="18" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2"><circle cx="11.5" cy="11.5" r="9.5" /><path stroke-linecap="round" d="M18.5 18.5L22 22" /></g></svg>
|
||||
Search for a game to play...
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-2 text-base font-title font-bold">
|
||||
<kbd class="ring-2 ring-gray-300 dark:ring-gray-700 px-2 py-1 rounded-md">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-5 flex-shrink-0" width="20" height="20" viewBox="0 0 256 256"><path fill="currentColor" d="M180 144h-20v-32h20a36 36 0 1 0-36-36v20h-32V76a36 36 0 1 0-36 36h20v32H76a36 36 0 1 0 36 36v-20h32v20a36 36 0 1 0 36-36m-20-68a20 20 0 1 1 20 20h-20ZM56 76a20 20 0 0 1 40 0v20H76a20 20 0 0 1-20-20m40 104a20 20 0 1 1-20-20h20Zm16-68h32v32h-32Zm68 88a20 20 0 0 1-20-20v-20h20a20 20 0 0 1 0 40" /></svg>
|
||||
</kbd>
|
||||
<span class="px-2 py-0.5 rounded-md ring-2 ring-gray-300 dark:ring-gray-700">
|
||||
K
|
||||
</span>
|
||||
<Modal.Root class="w-full">
|
||||
<Modal.Trigger class="w-full max-w-xl focus:ring-primary-500 duration-200 outline-none rounded-xl flex items-center justify-between hover:bg-gray-200 dark:hover:bg-gray-800 transition-all gap-2 px-4 py-3 h-[45px] ring-2 ring-gray-300 dark:ring-gray-700 mx-auto text-gray-900/70 dark:text-gray-100/70 bg-white dark:bg-black">
|
||||
<span class="flex items-center gap-3 h-max justify-center overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-[18] flex-shrink-0" height="18" width="18" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2"><circle cx="11.5" cy="11.5" r="9.5" /><path stroke-linecap="round" d="M18.5 18.5L22 22" /></g></svg>
|
||||
Search for a game to play...
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-2 text-base font-title font-bold">
|
||||
<kbd class="ring-2 ring-gray-300 dark:ring-gray-700 px-2 py-1 rounded-md">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-5 flex-shrink-0" width="20" height="20" viewBox="0 0 256 256"><path fill="currentColor" d="M180 144h-20v-32h20a36 36 0 1 0-36-36v20h-32V76a36 36 0 1 0-36 36h20v32H76a36 36 0 1 0 36 36v-20h32v20a36 36 0 1 0 36-36m-20-68a20 20 0 1 1 20 20h-20ZM56 76a20 20 0 0 1 40 0v20H76a20 20 0 0 1-20-20m40 104a20 20 0 1 1-20-20h20Zm16-68h32v32h-32Zm68 88a20 20 0 0 1-20-20v-20h20a20 20 0 0 1 0 40" /></svg>
|
||||
</kbd>
|
||||
<span class="px-2 py-0.5 rounded-md ring-2 ring-gray-300 dark:ring-gray-700">
|
||||
K
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</Modal.Trigger>
|
||||
<Modal.Panel class="w-full max-w-xl backdrop:backdrop-blur-sm backdrop:bg-black/10 dark:backdrop:bg-white/10 bg-white dark:bg-black ring-2 backdrop-blur-md ring-gray-300 dark:ring-gray-700 rounded-xl text-gray-900 dark:text-gray-100">
|
||||
<div class="p-4 gap-2 items-center justify-between flex">
|
||||
<div class="relative box-border" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-4 text-gray-500" width="20" height="20" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11.5" cy="11.5" r="9.5" /><path stroke-linecap="round" d="M18.5 18.5L22 22" /></g></svg>
|
||||
</div>
|
||||
<input type="text" class="w-full max-w-[50%] mx-auto whitespace-nowrap outline-none flex-grow text-sm text-[16px] bg-transparent placeholder:text-gray-400 dark:placeholder:text-gray-600" placeholder="Search for a game to play..." />
|
||||
<Modal.Close class="relative box-border rounded-full outline-none focus:ring-primary-500 hover:ring-primary-500 focus:ring-2 hover:ring-2 transition-all duration-200" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-5 text-gray-500" width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2s10 4.477 10 10" opacity=".5" /><path fill="currentColor" d="M8.97 8.97a.75.75 0 0 1 1.06 0L12 10.94l1.97-1.97a.75.75 0 1 1 1.06 1.06L13.06 12l1.97 1.97a.75.75 0 0 1-1.06 1.06L12 13.06l-1.97 1.97a.75.75 0 0 1-1.06-1.06L10.94 12l-1.97-1.97a.75.75 0 0 1 0-1.06" /></svg>
|
||||
</Modal.Close>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
<div class="h-max py-8 w-full px-4 flex flex-col">
|
||||
<div class="flex w-full grow flex-col items-center text-center justify-center gap-2 h-full text-sm font-medium">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
class="text-gray-600 dark:text-gray-400 size-10 mb-1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g
|
||||
id="layer1">
|
||||
<path
|
||||
d="M 2.2673611,5.0942341 H 21.732639 V 6.1658185 H 2.2673611 Z m 0,6.3699749 H 21.732639 v 1.071583 H 2.2673611 Z m 0,6.369972 H 21.732639 v 1.071585 H 2.2673611 Z"
|
||||
style="font-size:12px;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:currentColor;stroke-width:3.72245;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
<span class="text-gray-600 dark:text-gray-400 text-lg font-title font-medium">
|
||||
This is not implemented yet
|
||||
</span>
|
||||
<span class="text-gray-600/70 dark:text-gray-400/70 text-sm">
|
||||
Try logging in to Steam to see if we can find your game
|
||||
</span>
|
||||
<button class="bg-gray-300 hover:scale-110 focus:scale-110 outline-none mt-1 focus:ring-primary-500 hover:ring-primary-500 font-title dark:bg-gray-700 ring-2 ring-gray-400 dark:ring-gray-600 hover:bg-gray-400/70 dark:hover:bg-gray-600/70 transition-all duration-200 py-1 px-2 text-gray-950/70 dark:text-gray-50/70 rounded-lg text-sm">
|
||||
Log in to Steam
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="flex items-center bg-gray-100 dark:bg-gray-900 backdrop-blur-md justify-between gap-2 w-full border-t-2 border-gray-300 dark:border-gray-700 pt-4 px-4 pb-4 min-h-[45px]">
|
||||
<div class="text-sm text-gray-700/70 dark:text-gray-300/70 py-1 px-2 rounded-md">
|
||||
0 games indexed
|
||||
</div>
|
||||
<div class="text-xs text-gray-700/70 dark:text-gray-300/70 bg-white/10 ring-1 ring-gray-400 dark:ring-gray-600 dark:bg-black/10 py-1 px-2 rounded-md">
|
||||
ALPHA V1
|
||||
</div>
|
||||
</footer>
|
||||
</Modal.Panel>
|
||||
</Modal.Root>
|
||||
</HeroSection>
|
||||
<MotionComponent
|
||||
initial={{ opacity: 0, y: 100 }}
|
||||
|
||||
@@ -26,17 +26,19 @@
|
||||
"@builder.io/qwik-react": "0.5.0",
|
||||
"@fontsource/bricolage-grotesque": "^5.0.7",
|
||||
"@fontsource/geist-sans": "^5.0.3",
|
||||
"@nestri/core": "*",
|
||||
"@nestri/eslint-config": "*",
|
||||
"@nestri/typescript-config": "*",
|
||||
"@nestri/core": "*",
|
||||
"@types/eslint": "^8.56.5",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/react": "^18.2.28",
|
||||
"@types/react-dom": "^18.2.13",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"body-scroll-lock-upgrade": "^1.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^8.57.0",
|
||||
"focus-trap": "^7.5.4",
|
||||
"framer-motion": "^11.3.24",
|
||||
"nprogress": "^0.2.0",
|
||||
"postcss": "^8.4.41",
|
||||
|
||||
@@ -1,33 +1,113 @@
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
import { $, component$, useVisibleTask$ } from "@builder.io/qwik";
|
||||
import { Modal, Portal } from "@nestri/ui";
|
||||
import { cn } from "@nestri/ui/design"
|
||||
|
||||
type Props = {
|
||||
game: {
|
||||
name: string;
|
||||
id: number;
|
||||
}
|
||||
},
|
||||
size: "small" | "large";
|
||||
titleWidth: number;
|
||||
titleHeight: number;
|
||||
}
|
||||
|
||||
export const Card = component$(({ game }: Props) => {
|
||||
const imageUrl = `http://localhost:8787/image/cover/${game.id}.avif`
|
||||
export const Card = component$(({ titleWidth, titleHeight, game, size }: Props) => {
|
||||
|
||||
const modalUrl = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${game.id}/library_hero.jpg`;
|
||||
const imageUrl = size == "large" ? `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${game.id}/header.jpg` : `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${game.id}/library_600x900_2x.jpg`;
|
||||
const modalTitleUrl = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${game.id}/logo.png`
|
||||
const loadModalImages = $(() => {
|
||||
return Promise.all([modalUrl, modalTitleUrl].map(url => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = url;
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
// eslint-disable-next-line qwik/no-use-visible-task
|
||||
useVisibleTask$(async () => {
|
||||
await loadModalImages();
|
||||
// imagesLoaded.value = true;
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
class="min-w-[250px] group hover:ring-primary-500 focus:ring-primary-500 outline-none cursor-pointer backdrop-blur-sm select-none w-full group rounded-[20px] duration-200 flex flex-col ring-gray-900/70 transition-all gap-2 px-4 py-3 ring-[3px] ring-neutral-300 dark:ring-gray-700 text-gray-900/70 dark:text-gray-100/70 bg-gray-300/70 dark:bg-gray-700/70">
|
||||
<header class="flex gap-4 max-w-full justify-between p-4">
|
||||
<div class="flex relative pr-[22px] overflow-hidden overflow-ellipsis whitespace-nowrap" >
|
||||
<h3 class="overflow-hidden overflow-ellipsis whitespace-nowrap">{game.name}</h3>
|
||||
<Modal.Root class="w-full">
|
||||
{size === "large" ? (
|
||||
<Modal.Trigger
|
||||
class="min-w-[250px] h-[350px] lg:flex-row flex-col group overflow-hidden hover:ring-primary-500 focus:ring-primary-500 outline-none cursor-pointer backdrop-blur-sm select-none w-full group rounded-[20px] duration-200 flex transition-all gap-2 px-4 py-3 ring-[3px] ring-gray-200 dark:ring-gray-800 text-gray-900/70 dark:text-gray-100/70 bg-gray-200/70 dark:bg-gray-800/70">
|
||||
<header class="flex gap-4 lg:w-1/3 h-full w-full justify-between p-4">
|
||||
<div class="flex flex-col gap-4 text-left h-full justify-between relative overflow-hidden p-4" >
|
||||
<div class="flex flex-col gap-2">
|
||||
<h3 class="text-base">Continue Playing</h3>
|
||||
<h2 class="font-semibold font-title text-3xl">{game.name}</h2>
|
||||
</div>
|
||||
<div class="flex pt-2 flex-col justify-center gap-2">
|
||||
<div class="flex -space-x-2">
|
||||
{[1, 2, 3, 4, 5].map((_, index) => (
|
||||
<div key={index} class="inline-block size-6 rounded-full ring-[3px] ring-gray-300/70 dark:ring-gray-700/70 bg-gray-700" style={{ zIndex: 5 + index }}>
|
||||
<img src={`https://nexus.nestri.workers.dev/image/avatar/avatar-${index + 1}.png`} height={32} width={32} class="rounded-full size-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<span class="text-sm max-w-[70%]">
|
||||
<span class="font-semibold font-title">JD the Smith</span>
|
||||
{"and"}
|
||||
{"15 others"}
|
||||
{"are playing"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section class="flex justify-center lg:left-1/3 lg:absolute relative items-center lg:w-3/4 w-full lg:top-10 lg:right-0">
|
||||
<img
|
||||
src={imageUrl}
|
||||
class="lg:rounded-tr-none rounded-t-xl lg:m-0 mx-4 ring-2 aspect-[92/43] w-full ring-gray-900/70 group-hover:scale-105 group-focus:scale-105 duration-200 transition-transform shadow-2xl shadow-gray-950"
|
||||
width={250}
|
||||
height={400}
|
||||
alt={game.name}
|
||||
/>
|
||||
</section>
|
||||
</Modal.Trigger>) : (
|
||||
<Modal.Trigger
|
||||
class="min-w-[250px] group hover:ring-primary-500 focus:ring-primary-500 outline-none cursor-pointer backdrop-blur-sm select-none w-full group rounded-[20px] duration-200 flex flex-col ring-gray-800/70 transition-all gap-2 px-4 py-3 ring-[3px] ring-neutral-200 dark:ring-gray-800 text-gray-900/70 dark:text-gray-100/70 bg-gray-200/70 dark:bg-gray-800/70">
|
||||
<header class="flex gap-4 max-w-full justify-between p-4">
|
||||
<div class="flex relative pr-[22px] overflow-hidden overflow-ellipsis whitespace-nowrap" >
|
||||
<h3 class="overflow-hidden overflow-ellipsis whitespace-nowrap">{game.name}</h3>
|
||||
</div>
|
||||
</header>
|
||||
<section class="flex justify-center items-center w-full pb-7">
|
||||
<img
|
||||
src={imageUrl}
|
||||
class="rounded-2xl ring-2 aspect-[2/3] w-full max-w-[90%] ring-gray-900/70 group-hover:scale-105 group-focus:scale-105 duration-200 transition-transform shadow-2xl shadow-gray-950"
|
||||
width={250}
|
||||
height={200}
|
||||
alt={game.name}
|
||||
/>
|
||||
</section>
|
||||
</Modal.Trigger>
|
||||
)}
|
||||
<Modal.Panel
|
||||
class="w-full overflow-hidden max-w-3xl h-full max-h-[396px] aspect-[1536/496] backdrop:backdrop-blur-sm backdrop:bg-black/10 dark:backdrop:bg-white/10 bg-white dark:bg-black ring-2 backdrop-blur-md ring-gray-700 rounded-xl text-gray-900 dark:text-gray-100 after:pointer-events-none after:select-none after:bg-gradient-to-b after:from-transparent after:to-gray-900 after:fixed after:left-0 after:bottom-0 after:z-10 after:backdrop-blur-sm after:h-[200px] after:w-full after:[-webkit-mask-image:linear-gradient(to_top,theme(colors.primary.900)_50%,transparent)]">
|
||||
<div
|
||||
style={{
|
||||
'--before-url': `url('${modalUrl}')`,
|
||||
'--title-url': `url('${modalTitleUrl}')`,
|
||||
'--after-width': `${titleWidth}%`,
|
||||
'--after-height': `${titleHeight}%`
|
||||
}}
|
||||
class={cn(
|
||||
"w-full h-full relative flex",
|
||||
"before:absolute before:left-0 before:top-0 before:w-full before:animate-zoom-out before:h-full before:bg-cover before:bg-center before:bg-no-repeat before:bg-[url:var(--before-url)]",
|
||||
"after:absolute after:w-[var(--after-width)] after:bg-contain after:h-[var(--after-height)] after:left-0 after:m-auto after:right-0 after:bottom-0 after:top-0 after:bg-center after:bg-no-repeat after:bg-[url:var(--title-url)]"
|
||||
)} />
|
||||
<div class="flex flex-col w-full absolute bottom-0 left-0 z-50 right-0 items-center justify-center gap-4 p-4">
|
||||
<Portal />
|
||||
</div>
|
||||
</header>
|
||||
<section class="flex justify-center items-center w-full py-7">
|
||||
<img
|
||||
src={imageUrl}
|
||||
class="rounded-2xl ring-2 aspect-[2/3] w-full max-w-[90%] ring-gray-900/70 group-hover:scale-105 group-focus:scale-105 duration-200 transition-transform shadow-2xl shadow-gray-950"
|
||||
width={250}
|
||||
height={200}
|
||||
alt={game.name}
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
</section>
|
||||
</button>
|
||||
</Modal.Panel>
|
||||
</Modal.Root >
|
||||
)
|
||||
});
|
||||
@@ -50,7 +50,7 @@ export const GithubBanner = component$(() => {
|
||||
</Link>
|
||||
<div class="min-w-max min-h-max w-full relative overflow-hidden rounded-[8px] flex justify-center items-center group">
|
||||
<div class="animate-multicolor before:-z-[1] -z-[2] absolute -right-full left-0 bottom-0 h-full w-[1000px] [background:linear-gradient(90deg,rgb(232,23,98)_1.26%,rgb(30,134,248)_18.6%,rgb(91,108,255)_34.56%,rgb(52,199,89)_49.76%,rgb(245,197,5)_64.87%,rgb(236,62,62)_85.7%)_0%_0%/50%_100%_repeat-x]" />
|
||||
<Link class="select-none m-0.5 relative justify-center items-center min-w-max flex z-[2] px-3 rounded-md h-8 w-full bg-white dark:bg-black group-hover:bg-transparent transition-all duration-200" rel="noopener noreferrer" href="/join" target="_blank">
|
||||
<Link class="select-none m-[3px] relative justify-center items-center min-w-max flex z-[2] px-3 rounded-md h-8 w-full bg-white dark:bg-black group-hover:bg-transparent transition-all duration-200" rel="noopener noreferrer" href="/join" target="_blank">
|
||||
<span class="text-sm dark:text-white text-black group-hover:text-white w-full transition-all duration-200">
|
||||
<div class="flex justify-around items-center w-full p-1 h-max">
|
||||
Join Waitlist
|
||||
|
||||
@@ -30,13 +30,13 @@ export const HomeNavBar = component$(() => {
|
||||
<div class="relative flex items-center">
|
||||
<hr class="w-[1px] h-7 bg-neutral-700 dark:bg-neutral-300 mx-3 rotate-[16deg]" />
|
||||
<button class="rounded-full outline-none focus:ring-primary-500 focus:ring-2 transition-all flex items-center duration-200 px-3 h-8 gap-2 select-none cursor-pointer hover:bg-neutral-300/70 dark:hover:bg-neutral-700/70" >
|
||||
<img src="http://localhost:8787/image/avatar/the-avengers.png" height={16} width={16} class="size-4 rounded-full" alt="Avatar" />
|
||||
<img src="https://nexus.nestri.workers.dev/image/avatar/the-avengers.png" height={16} width={16} class="size-4 rounded-full" alt="Avatar" />
|
||||
<p class="whitespace-nowrap [text-overflow:ellipsis] overflow-hidden max-w-[20ch]">The Avengers</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={16} height={16} viewBox="0 0 21 21"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m7.5 8.5l3-3l3 3m-6 5l3 3l3-3" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<button class="ml-auto focus:ring-primary-500 focus:ring-2 outline-none rounded-full transition-all flex items-center duration-200 px-3 h-8 gap-1 select-none cursor-pointer hover:bg-gray-300/70 dark:hover:bg-gray-700/70" >
|
||||
<img src="http://localhost:8787/image/avatar/wanjohi.png" height={16} width={16} class="size-4 rounded-full" alt="Avatar" />
|
||||
<img src="https://nexus.nestri.workers.dev/image/avatar/wanjohi.png" height={16} width={16} class="size-4 rounded-full" alt="Avatar" />
|
||||
<p class="whitespace-nowrap [text-overflow:ellipsis] overflow-hidden max-w-[20ch]">Wanjohi</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={16} height={16} viewBox="0 0 21 21"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m7.5 8.5l3-3l3 3m-6 5l3 3l3-3" /></svg>
|
||||
</button>
|
||||
|
||||
@@ -8,4 +8,6 @@ export * from "./team-counter"
|
||||
export * from "./tooltip"
|
||||
export * from "./footer"
|
||||
export * from "./router-head"
|
||||
export * from "./card"
|
||||
export * from "./card"
|
||||
export * as Modal from "./modal"
|
||||
export { default as Portal } from "./portal"
|
||||
13
packages/ui/src/modal/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
//Copied from https://github.com/qwikifiers/qwik-ui/blob/main/packages/kit-headless/src/components/modal/index.ts
|
||||
//Why? because qwik-ui/headless requires qwik v1.7.2 as a peer dependency, which is causing build errors in the monorepo
|
||||
// Reference: https://github.com/qwikifiers/qwik-ui/blob/26c17886e9a84de9d0da09f1180ede5fdceb70f3/packages/kit-headless/package.json#L33
|
||||
|
||||
export { HModalRoot as Root } from './modal-root';
|
||||
export { HModalPanel as Panel } from './modal-panel';
|
||||
export { HModalContent as Content } from './modal-content';
|
||||
export { HModalFooter as Footer } from './modal-footer';
|
||||
export { HModalHeader as Header } from './modal-header';
|
||||
export { HModalTitle as Title } from './modal-title';
|
||||
export { HModalDescription as Description } from './modal-description';
|
||||
export { HModalTrigger as Trigger } from './modal-trigger';
|
||||
export { HModalClose as Close } from './modal-close';
|
||||
16
packages/ui/src/modal/modal-close.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { type PropsOf, Slot, component$, useContext, $ } from '@builder.io/qwik';
|
||||
import { modalContextId } from './modal-context';
|
||||
|
||||
export const HModalClose = component$((props: PropsOf<'button'>) => {
|
||||
const context = useContext(modalContextId);
|
||||
|
||||
const handleClick$ = $(() => {
|
||||
context.showSig.value = false;
|
||||
});
|
||||
|
||||
return (
|
||||
<button onClick$={[handleClick$, props.onClick$]} {...props}>
|
||||
<Slot />
|
||||
</button>
|
||||
);
|
||||
});
|
||||
12
packages/ui/src/modal/modal-content.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { type PropsOf, Slot, component$ } from '@builder.io/qwik';
|
||||
|
||||
/**
|
||||
* @deprecated This component is deprecated and will be removed in future releases.
|
||||
*/
|
||||
export const HModalContent = component$((props: PropsOf<'div'>) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
<Slot />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
13
packages/ui/src/modal/modal-context.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { type QRL, type Signal, createContextId } from '@builder.io/qwik';
|
||||
|
||||
export const modalContextId = createContextId<ModalContext>('qui-modal');
|
||||
|
||||
export type ModalContext = {
|
||||
// core state
|
||||
localId: string;
|
||||
showSig: Signal<boolean>;
|
||||
onShow$?: QRL<() => void>;
|
||||
onClose$?: QRL<() => void>;
|
||||
closeOnBackdropClick?: boolean;
|
||||
alert?: boolean;
|
||||
};
|
||||
16
packages/ui/src/modal/modal-description.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { type PropsOf, Slot, component$, useContext } from '@builder.io/qwik';
|
||||
import { modalContextId } from './modal-context';
|
||||
|
||||
export type ModalDescriptionProps = PropsOf<'p'>;
|
||||
|
||||
export const HModalDescription = component$((props: ModalDescriptionProps) => {
|
||||
const context = useContext(modalContextId);
|
||||
|
||||
const descriptionId = `${context.localId}-description`;
|
||||
|
||||
return (
|
||||
<p id={descriptionId} {...props}>
|
||||
<Slot />
|
||||
</p>
|
||||
);
|
||||
});
|
||||
12
packages/ui/src/modal/modal-footer.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { type PropsOf, Slot, component$ } from '@builder.io/qwik';
|
||||
|
||||
/**
|
||||
* @deprecated This component is deprecated and will be removed in future releases.
|
||||
*/
|
||||
export const HModalFooter = component$((props: PropsOf<'footer'>) => {
|
||||
return (
|
||||
<footer {...props}>
|
||||
<Slot />
|
||||
</footer>
|
||||
);
|
||||
});
|
||||
12
packages/ui/src/modal/modal-header.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { type PropsOf, Slot, component$ } from '@builder.io/qwik';
|
||||
|
||||
/**
|
||||
* @deprecated This component is deprecated and will be removed in future releases.
|
||||
*/
|
||||
export const HModalHeader = component$((props: PropsOf<'header'>) => {
|
||||
return (
|
||||
<header {...props}>
|
||||
<Slot />
|
||||
</header>
|
||||
);
|
||||
});
|
||||
124
packages/ui/src/modal/modal-panel.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
$,
|
||||
type PropsOf,
|
||||
type QRL,
|
||||
type Signal,
|
||||
Slot,
|
||||
component$,
|
||||
useSignal,
|
||||
useStyles$,
|
||||
useTask$,
|
||||
sync$,
|
||||
useContext,
|
||||
} from '@builder.io/qwik';
|
||||
|
||||
import { modalContextId } from './modal-context';
|
||||
|
||||
import styles from './modal.css?inline';
|
||||
import { useModal } from './use-modal';
|
||||
|
||||
export type ModalProps = Omit<PropsOf<'dialog'>, 'open'> & {
|
||||
onShow$?: QRL<() => void>;
|
||||
onClose$?: QRL<() => void>;
|
||||
'bind:show': Signal<boolean>;
|
||||
closeOnBackdropClick?: boolean;
|
||||
alert?: boolean;
|
||||
};
|
||||
|
||||
export const HModalPanel = component$((props: PropsOf<'dialog'>) => {
|
||||
useStyles$(styles);
|
||||
const {
|
||||
activateFocusTrap,
|
||||
closeModal,
|
||||
deactivateFocusTrap,
|
||||
showModal,
|
||||
trapFocus,
|
||||
wasModalBackdropClicked,
|
||||
} = useModal();
|
||||
const context = useContext(modalContextId);
|
||||
|
||||
const panelRef = useSignal<HTMLDialogElement>();
|
||||
|
||||
useTask$(async function toggleModal({ track, cleanup }) {
|
||||
const isOpen = track(() => context.showSig.value);
|
||||
|
||||
if (!panelRef.value) return;
|
||||
|
||||
const focusTrap = await trapFocus(panelRef.value);
|
||||
|
||||
if (isOpen) {
|
||||
// HACK: keep modal scroll position in place with iOS
|
||||
const storedRequestAnimationFrame = window.requestAnimationFrame;
|
||||
window.requestAnimationFrame = () => 42;
|
||||
|
||||
await showModal(panelRef.value);
|
||||
window.requestAnimationFrame = storedRequestAnimationFrame;
|
||||
await context.onShow$?.();
|
||||
activateFocusTrap(focusTrap);
|
||||
} else {
|
||||
await closeModal(panelRef.value);
|
||||
await context.onClose$?.();
|
||||
}
|
||||
|
||||
cleanup(async () => {
|
||||
await deactivateFocusTrap(focusTrap);
|
||||
});
|
||||
});
|
||||
|
||||
const closeOnBackdropClick$ = $(async (e: MouseEvent) => {
|
||||
if (context.alert === true || context.closeOnBackdropClick === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We do not want to close elements that dangle outside of the modal
|
||||
if (!(e.target instanceof HTMLDialogElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await wasModalBackdropClicked(panelRef.value, e)) {
|
||||
context.showSig.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
const handleKeyDownSync$ = sync$((e: KeyboardEvent) => {
|
||||
const keys = [' ', 'Enter'];
|
||||
|
||||
if (e.target instanceof HTMLDialogElement && keys.includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
const handleKeyDown$ = $((e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
context.showSig.value = false;
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<dialog
|
||||
{...props}
|
||||
id={`${context.localId}-root`}
|
||||
aria-labelledby={`${context.localId}-title`}
|
||||
aria-describedby={`${context.localId}-description`}
|
||||
// TODO: deprecate data-state in favor of data-open, data-closing, and data-closed
|
||||
data-state={context.showSig.value ? 'open' : 'closed'}
|
||||
data-open={context.showSig.value && ''}
|
||||
data-closed={!context.showSig.value && ''}
|
||||
role={context.alert === true ? 'alertdialog' : 'dialog'}
|
||||
ref={panelRef}
|
||||
// key={renderKey.value}
|
||||
onKeyDown$={[handleKeyDownSync$, handleKeyDown$, props.onKeyDown$]}
|
||||
onClick$={async (e) => {
|
||||
e.stopPropagation();
|
||||
await closeOnBackdropClick$(e);
|
||||
}}
|
||||
>
|
||||
<Slot />
|
||||
</dialog>
|
||||
);
|
||||
});
|
||||
51
packages/ui/src/modal/modal-root.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
type PropsOf,
|
||||
type QRL,
|
||||
type Signal,
|
||||
Slot,
|
||||
component$,
|
||||
useContextProvider,
|
||||
useId,
|
||||
useSignal,
|
||||
} from '@builder.io/qwik';
|
||||
import { type ModalContext, modalContextId } from './modal-context';
|
||||
|
||||
type ModalRootProps = {
|
||||
onShow$?: QRL<() => void>;
|
||||
onClose$?: QRL<() => void>;
|
||||
'bind:show'?: Signal<boolean>;
|
||||
closeOnBackdropClick?: boolean;
|
||||
alert?: boolean;
|
||||
} & PropsOf<'div'>;
|
||||
|
||||
export const HModalRoot = component$((props: ModalRootProps) => {
|
||||
const localId = useId();
|
||||
|
||||
const {
|
||||
'bind:show': givenShowSig,
|
||||
closeOnBackdropClick,
|
||||
onShow$,
|
||||
onClose$,
|
||||
alert,
|
||||
} = props;
|
||||
|
||||
const defaultShowSig = useSignal<boolean>(false);
|
||||
const showSig = givenShowSig ?? defaultShowSig;
|
||||
|
||||
const context: ModalContext = {
|
||||
localId,
|
||||
showSig,
|
||||
closeOnBackdropClick,
|
||||
onShow$,
|
||||
onClose$,
|
||||
alert,
|
||||
};
|
||||
|
||||
useContextProvider(modalContextId, context);
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
<Slot />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
16
packages/ui/src/modal/modal-title.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { type PropsOf, Slot, component$, useContext } from '@builder.io/qwik';
|
||||
import { modalContextId } from './modal-context';
|
||||
|
||||
export type ModalTitleProps = PropsOf<'h2'>;
|
||||
|
||||
export const HModalTitle = component$((props: ModalTitleProps) => {
|
||||
const context = useContext(modalContextId);
|
||||
|
||||
const titleId = `${context.localId}-title`;
|
||||
|
||||
return (
|
||||
<h2 id={titleId} {...props}>
|
||||
<Slot />
|
||||
</h2>
|
||||
);
|
||||
});
|
||||
23
packages/ui/src/modal/modal-trigger.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { type PropsOf, Slot, component$, useContext, $ } from '@builder.io/qwik';
|
||||
import { modalContextId } from './modal-context';
|
||||
|
||||
export const HModalTrigger = component$((props: PropsOf<'button'>) => {
|
||||
const context = useContext(modalContextId);
|
||||
|
||||
const handleClick$ = $(() => {
|
||||
context.showSig.value = !context.showSig.value;
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={context.showSig.value}
|
||||
data-open={context.showSig.value ? '' : undefined}
|
||||
data-closed={!context.showSig.value ? '' : undefined}
|
||||
onClick$={[handleClick$, props.onClick$]}
|
||||
{...props}
|
||||
>
|
||||
<Slot />
|
||||
</button>
|
||||
);
|
||||
});
|
||||
9
packages/ui/src/modal/modal.css
Normal file
@@ -0,0 +1,9 @@
|
||||
@layer qwik-ui {
|
||||
/* browsers automatically set an interesting max-width and max-height for dialogs
|
||||
https://twitter.com/t3dotgg/status/1774350919133691936
|
||||
*/
|
||||
dialog:modal {
|
||||
max-width: unset;
|
||||
max-height: unset;
|
||||
}
|
||||
}
|
||||
134
packages/ui/src/modal/use-modal.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { $ } from '@builder.io/qwik';
|
||||
import { type FocusTrap, createFocusTrap } from 'focus-trap';
|
||||
import { enableBodyScroll, disableBodyScroll } from 'body-scroll-lock-upgrade';
|
||||
|
||||
export type WidthState = {
|
||||
width: number | null;
|
||||
};
|
||||
|
||||
|
||||
export function useModal() {
|
||||
/**
|
||||
* Listens for animation/transition events in order to
|
||||
* remove Animation-CSS-Classes after animation/transition ended.
|
||||
*/
|
||||
const supportClosingAnimation = $((modal: HTMLDialogElement) => {
|
||||
modal.dataset.closing = '';
|
||||
modal.classList.add('modal-closing');
|
||||
|
||||
const { animationDuration, transitionDuration } = getComputedStyle(modal);
|
||||
|
||||
if (animationDuration !== '0s') {
|
||||
modal.addEventListener(
|
||||
'animationend',
|
||||
(e) => {
|
||||
if (e.target === modal) {
|
||||
delete modal.dataset.closing;
|
||||
modal.classList.remove('modal-closing');
|
||||
enableBodyScroll(modal);
|
||||
modal.close();
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
} else if (transitionDuration !== '0s') {
|
||||
modal.addEventListener(
|
||||
'transitionend',
|
||||
(e) => {
|
||||
if (e.target === modal) {
|
||||
delete modal.dataset.closing;
|
||||
modal.classList.remove('modal-closing');
|
||||
enableBodyScroll(modal);
|
||||
modal.close();
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
} else if (animationDuration === '0s' && transitionDuration === '0s') {
|
||||
delete modal.dataset.closing;
|
||||
modal.classList.remove('modal-closing');
|
||||
enableBodyScroll(modal);
|
||||
modal.close();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Traps the focus of the given Modal
|
||||
* @returns FocusTrap
|
||||
*/
|
||||
const trapFocus = $((modal: HTMLDialogElement): FocusTrap => {
|
||||
return createFocusTrap(modal, { escapeDeactivates: false });
|
||||
});
|
||||
|
||||
const activateFocusTrap = $((focusTrap: FocusTrap | null) => {
|
||||
try {
|
||||
focusTrap?.activate();
|
||||
} catch {
|
||||
// Activating the focus trap throws if no tabbable elements are inside the container.
|
||||
// If this is the case we are fine with not activating the focus trap.
|
||||
// That's why we ignore the thrown error.
|
||||
}
|
||||
});
|
||||
|
||||
const deactivateFocusTrap = $((focusTrap: FocusTrap | null) => {
|
||||
focusTrap?.deactivate();
|
||||
focusTrap = null;
|
||||
});
|
||||
|
||||
/**
|
||||
* Shows the given Modal.
|
||||
* Applies a CSS-Class to animate the modal-showing.
|
||||
* Calls the given callback that is executed after the Modal has been opened.
|
||||
*/
|
||||
const showModal = $(async (modal: HTMLDialogElement) => {
|
||||
disableBodyScroll(modal, { reserveScrollBarGap: true });
|
||||
modal.showModal();
|
||||
});
|
||||
|
||||
/**
|
||||
* Closes the given Modal.
|
||||
* Applies a CSS-Class to animate the Modal-closing.
|
||||
* Calls the given callback that is executed after the Modal has been closed.
|
||||
*/
|
||||
const closeModal = $(async (modal: HTMLDialogElement) => {
|
||||
await supportClosingAnimation(modal);
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines if the backdrop of the Modal has been clicked.
|
||||
*/
|
||||
const wasModalBackdropClicked = $(
|
||||
(modal: HTMLDialogElement | undefined, clickEvent: MouseEvent): boolean => {
|
||||
if (!modal) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rect = modal.getBoundingClientRect();
|
||||
|
||||
const wasBackdropClicked =
|
||||
rect.left > clickEvent.clientX ||
|
||||
rect.right < clickEvent.clientX ||
|
||||
rect.top > clickEvent.clientY ||
|
||||
rect.bottom < clickEvent.clientY;
|
||||
|
||||
/**
|
||||
* If the inside focusable elements are not prevented, such as a button it will also fire a click event.
|
||||
*
|
||||
* Hitting the enter or space keys on a button inside of the dialog for example, will fire a "pointer" event. In reality, it fires our onClick$ handler because we have not prevented the default behavior.
|
||||
*
|
||||
* This is why we check if the pointerId is -1.
|
||||
**/
|
||||
return (clickEvent as PointerEvent).pointerId === -1 ? false : wasBackdropClicked;
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
trapFocus,
|
||||
activateFocusTrap,
|
||||
deactivateFocusTrap,
|
||||
showModal,
|
||||
closeModal,
|
||||
wasModalBackdropClicked,
|
||||
supportClosingAnimation,
|
||||
};
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export const NavBar = component$(() => {
|
||||
return (
|
||||
<nav class={cn("sticky top-0 z-50 px-4 text-sm font-extrabold bg-gray-100/70 dark:bg-gray-900/70 before:backdrop-blur-[15px] before:absolute before:-z-[1] before:top-0 before:left-0 before:w-full before:h-full", hasScrolled.value && "shadow-[0_2px_20px_1px] shadow-gray-300 dark:shadow-gray-700")} >
|
||||
<div class="mx-auto flex max-w-xl items-center border-b-2 dark:border-gray-50/50 border-gray-950/50" >
|
||||
<Link class="outline-none" href="/" >
|
||||
<Link class="outline-none focus:ring-2 py-1 px-3 -ml-3 rounded-lg focus:ring-primary-500 duration-200 transition-all" href="/" >
|
||||
<h1 class="text-lg font-title" >
|
||||
Nestri
|
||||
</h1>
|
||||
@@ -39,7 +39,7 @@ export const NavBar = component$(() => {
|
||||
<ul class="ml-0 -mr-4 flex font-medium m-4 flex-1 gap-1 tracking-[0.035em] items-center justify-end dark:text-primary-50/70 text-primary-950/70">
|
||||
{navLinks.map((linkItem, key) => (
|
||||
<li key={`linkItem-${key}`}>
|
||||
<Link href={linkItem.href} class={cn(buttonVariants.ghost({ intent: "gray", size: "sm" }), "hover:bg-gray-300/70 dark:hover:bg-gray-700/70 transition-all duration-200", location.url.pathname === linkItem.href && "bg-gray-300/70 hover:bg-gray-300/70 dark:bg-gray-700/70 dark:hover:bg-gray-700/70")}>
|
||||
<Link href={linkItem.href} class={cn(buttonVariants.ghost({ intent: "gray", size: "sm" }), "hover:bg-gray-300/70 dark:hover:bg-gray-700/70 focus:ring-2 outline-none focus:ring-primary-500 duration-200 transition-all", location.url.pathname === linkItem.href && "bg-gray-300/70 hover:bg-gray-300/70 dark:bg-gray-700/70 dark:hover:bg-gray-700/70")}>
|
||||
{linkItem.name}
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
BIN
packages/ui/src/portal/assets/play_button_disabled_bg.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
packages/ui/src/portal/assets/play_button_focused_bg.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
2316
packages/ui/src/portal/assets/play_button_idle.json
Normal file
BIN
packages/ui/src/portal/assets/play_button_idle.png
Normal file
|
After Width: | Height: | Size: 1022 KiB |
588
packages/ui/src/portal/assets/play_button_intro.json
Normal file
@@ -0,0 +1,588 @@
|
||||
{"frames": [
|
||||
|
||||
{
|
||||
"filename": "intro_00000.png",
|
||||
"frame": {"x":1,"y":1,"w":3,"h":3},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":3,"h":3},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00001.png",
|
||||
"frame": {"x":911,"y":364,"w":150,"h":150},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":150,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00002.png",
|
||||
"frame": {"x":1063,"y":367,"w":150,"h":150},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":150,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00003.png",
|
||||
"frame": {"x":1215,"y":372,"w":150,"h":150},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":150,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00004.png",
|
||||
"frame": {"x":1367,"y":374,"w":150,"h":150},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":150,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00005.png",
|
||||
"frame": {"x":1519,"y":375,"w":150,"h":150},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":150,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00006.png",
|
||||
"frame": {"x":1671,"y":379,"w":150,"h":150},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":150,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00007.png",
|
||||
"frame": {"x":1823,"y":381,"w":150,"h":150},
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":150,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00008.png",
|
||||
"frame": {"x":759,"y":362,"w":150,"h":149},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":0,"y":0,"w":150,"h":149},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00009.png",
|
||||
"frame": {"x":456,"y":355,"w":148,"h":149},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":2,"y":0,"w":148,"h":149},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00010.png",
|
||||
"frame": {"x":607,"y":360,"w":148,"h":150},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":2,"y":0,"w":148,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00011.png",
|
||||
"frame": {"x":1,"y":349,"w":147,"h":150},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":3,"y":0,"w":147,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00012.png",
|
||||
"frame": {"x":153,"y":351,"w":147,"h":150},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":3,"y":0,"w":147,"h":150},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00013.png",
|
||||
"frame": {"x":305,"y":353,"w":147,"h":149},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":2,"y":1,"w":147,"h":149},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00014.png",
|
||||
"frame": {"x":1867,"y":235,"w":144,"h":148},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":4,"y":2,"w":144,"h":148},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00015.png",
|
||||
"frame": {"x":1719,"y":235,"w":142,"h":146},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":5,"y":3,"w":142,"h":146},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00016.png",
|
||||
"frame": {"x":1574,"y":233,"w":140,"h":143},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":6,"y":4,"w":140,"h":143},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00017.png",
|
||||
"frame": {"x":1432,"y":233,"w":139,"h":140},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":6,"y":6,"w":139,"h":140},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00018.png",
|
||||
"frame": {"x":1292,"y":233,"w":138,"h":137},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":6,"y":7,"w":138,"h":137},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00019.png",
|
||||
"frame": {"x":1154,"y":231,"w":136,"h":134},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":7,"y":9,"w":136,"h":134},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00020.png",
|
||||
"frame": {"x":1018,"y":229,"w":134,"h":133},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":8,"y":9,"w":134,"h":133},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00021.png",
|
||||
"frame": {"x":885,"y":229,"w":131,"h":131},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":9,"y":10,"w":131,"h":131},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00022.png",
|
||||
"frame": {"x":754,"y":229,"w":129,"h":129},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":10,"y":11,"w":129,"h":129},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00023.png",
|
||||
"frame": {"x":624,"y":229,"w":128,"h":127},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":11,"y":12,"w":128,"h":127},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00024.png",
|
||||
"frame": {"x":496,"y":227,"w":126,"h":126},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":12,"y":12,"w":126,"h":126},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00025.png",
|
||||
"frame": {"x":370,"y":227,"w":124,"h":124},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":13,"y":13,"w":124,"h":124},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00026.png",
|
||||
"frame": {"x":246,"y":227,"w":122,"h":122},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":14,"y":14,"w":122,"h":122},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00027.png",
|
||||
"frame": {"x":1,"y":227,"w":121,"h":120},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":14,"y":15,"w":121,"h":120},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00028.png",
|
||||
"frame": {"x":124,"y":227,"w":120,"h":120},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":15,"y":15,"w":120,"h":120},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00029.png",
|
||||
"frame": {"x":1855,"y":115,"w":118,"h":118},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":16,"y":16,"w":118,"h":118},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00030.png",
|
||||
"frame": {"x":1735,"y":115,"w":117,"h":118},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":16,"y":16,"w":117,"h":118},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00031.png",
|
||||
"frame": {"x":1381,"y":115,"w":116,"h":116},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":17,"y":17,"w":116,"h":116},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00032.png",
|
||||
"frame": {"x":1499,"y":115,"w":116,"h":116},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":17,"y":17,"w":116,"h":116},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00033.png",
|
||||
"frame": {"x":1617,"y":115,"w":116,"h":116},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":17,"y":17,"w":116,"h":116},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00034.png",
|
||||
"frame": {"x":685,"y":113,"w":114,"h":114},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":18,"w":114,"h":114},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00035.png",
|
||||
"frame": {"x":801,"y":113,"w":114,"h":114},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":18,"w":114,"h":114},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00036.png",
|
||||
"frame": {"x":917,"y":113,"w":114,"h":114},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":18,"w":114,"h":114},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00037.png",
|
||||
"frame": {"x":1033,"y":113,"w":114,"h":114},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":18,"w":114,"h":114},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00038.png",
|
||||
"frame": {"x":1149,"y":113,"w":114,"h":114},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":18,"w":114,"h":114},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00039.png",
|
||||
"frame": {"x":1265,"y":115,"w":114,"h":114},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":18,"w":114,"h":114},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00040.png",
|
||||
"frame": {"x":1350,"y":1,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00041.png",
|
||||
"frame": {"x":1464,"y":1,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00042.png",
|
||||
"frame": {"x":1578,"y":1,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00043.png",
|
||||
"frame": {"x":1692,"y":1,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00044.png",
|
||||
"frame": {"x":1806,"y":1,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00045.png",
|
||||
"frame": {"x":1920,"y":1,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00046.png",
|
||||
"frame": {"x":1,"y":113,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00047.png",
|
||||
"frame": {"x":115,"y":113,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00048.png",
|
||||
"frame": {"x":229,"y":113,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00049.png",
|
||||
"frame": {"x":343,"y":113,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00050.png",
|
||||
"frame": {"x":457,"y":113,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00051.png",
|
||||
"frame": {"x":571,"y":113,"w":112,"h":112},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":19,"w":112,"h":112},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00052.png",
|
||||
"frame": {"x":6,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00053.png",
|
||||
"frame": {"x":118,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00054.png",
|
||||
"frame": {"x":230,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00055.png",
|
||||
"frame": {"x":342,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00056.png",
|
||||
"frame": {"x":454,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00057.png",
|
||||
"frame": {"x":566,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00058.png",
|
||||
"frame": {"x":678,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00059.png",
|
||||
"frame": {"x":790,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00060.png",
|
||||
"frame": {"x":902,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00061.png",
|
||||
"frame": {"x":1014,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00062.png",
|
||||
"frame": {"x":1126,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "intro_00063.png",
|
||||
"frame": {"x":1238,"y":1,"w":110,"h":110},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":20,"w":110,"h":110},
|
||||
"sourceSize": {"w":150,"h":150},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
}],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "1.0",
|
||||
"image": "play_button_intro.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {"w":2033,"h":532},
|
||||
"scale": "1",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:c182f7ef1f119bbfd7cc0a11dba8aad6:0f1ccfe8fb1cdc864d87b7e8f6fb3197:356e3de40db0da0fa679697918a56687$"
|
||||
}
|
||||
}
|
||||
BIN
packages/ui/src/portal/assets/play_button_intro.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
237
packages/ui/src/portal/assets/play_icon_exit.json
Normal file
@@ -0,0 +1,237 @@
|
||||
{"frames": [
|
||||
|
||||
{
|
||||
"filename": "processing_outro_190822_00000.png",
|
||||
"frame": {"x":82,"y":37,"w":25,"h":13},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":36,"y":16,"w":25,"h":13},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00001.png",
|
||||
"frame": {"x":173,"y":1,"w":27,"h":11},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":16,"w":27,"h":11},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00002.png",
|
||||
"frame": {"x":186,"y":1,"w":30,"h":15},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":50,"y":16,"w":30,"h":15},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00003.png",
|
||||
"frame": {"x":122,"y":35,"w":32,"h":27},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":58,"y":16,"w":32,"h":27},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00004.png",
|
||||
"frame": {"x":1,"y":1,"w":22,"h":37},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":72,"y":23,"w":22,"h":37},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00005.png",
|
||||
"frame": {"x":25,"y":1,"w":17,"h":37},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":77,"y":40,"w":17,"h":37},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00006.png",
|
||||
"frame": {"x":1,"y":40,"w":31,"h":21},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":59,"y":63,"w":31,"h":21},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00007.png",
|
||||
"frame": {"x":34,"y":40,"w":29,"h":19},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":44,"y":65,"w":29,"h":19},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00008.png",
|
||||
"frame": {"x":65,"y":37,"w":15,"h":25},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":41,"y":52,"w":15,"h":25},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00009.png",
|
||||
"frame": {"x":186,"y":18,"w":19,"h":18},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":43,"y":47,"w":19,"h":18},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00010.png",
|
||||
"frame": {"x":186,"y":38,"w":18,"h":18},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":47,"y":46,"w":18,"h":18},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00011.png",
|
||||
"frame": {"x":97,"y":37,"w":23,"h":24},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":45,"y":43,"w":23,"h":24},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00012.png",
|
||||
"frame": {"x":156,"y":35,"w":27,"h":28},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":43,"y":41,"w":27,"h":28},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00013.png",
|
||||
"frame": {"x":142,"y":1,"w":29,"h":32},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":39,"w":29,"h":32},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00014.png",
|
||||
"frame": {"x":110,"y":1,"w":30,"h":32},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":39,"w":30,"h":32},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00015.png",
|
||||
"frame": {"x":44,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00016.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00017.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00018.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00019.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00020.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00021.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00022.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00023.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing_outro_190822_00024.png",
|
||||
"frame": {"x":77,"y":1,"w":31,"h":34},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":42,"y":38,"w":31,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
}],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "1.0",
|
||||
"image": "play_icon_exit.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {"w":217,"h":63},
|
||||
"scale": "1",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:c3c530fe6905479b4ca8719a9d079c2e:313834a760df7f23cb3a698e4cd74589:0d545ddd0574d55083449defb59d19dc$"
|
||||
}
|
||||
}
|
||||
BIN
packages/ui/src/portal/assets/play_icon_exit.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
237
packages/ui/src/portal/assets/play_icon_intro.json
Normal file
@@ -0,0 +1,237 @@
|
||||
{"frames": [
|
||||
|
||||
{
|
||||
"filename": "processing-intro_190822_00000.png",
|
||||
"frame": {"x":1,"y":1,"w":35,"h":38},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":40,"y":36,"w":35,"h":38},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00001.png",
|
||||
"frame": {"x":38,"y":1,"w":34,"h":38},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":40,"y":36,"w":34,"h":38},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00002.png",
|
||||
"frame": {"x":74,"y":1,"w":29,"h":32},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":43,"y":39,"w":29,"h":32},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00003.png",
|
||||
"frame": {"x":1,"y":41,"w":22,"h":24},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":46,"y":43,"w":22,"h":24},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00004.png",
|
||||
"frame": {"x":142,"y":43,"w":17,"h":18},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":48,"y":46,"w":17,"h":18},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00005.png",
|
||||
"frame": {"x":161,"y":40,"w":14,"h":14},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":49,"y":45,"w":14,"h":14},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00006.png",
|
||||
"frame": {"x":183,"y":1,"w":10,"h":10},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":54,"y":42,"w":10,"h":10},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00007.png",
|
||||
"frame": {"x":183,"y":37,"w":9,"h":9},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":62,"y":38,"w":9,"h":9},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00008.png",
|
||||
"frame": {"x":183,"y":25,"w":10,"h":10},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":73,"y":38,"w":10,"h":10},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00009.png",
|
||||
"frame": {"x":180,"y":13,"w":10,"h":12},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":82,"y":46,"w":10,"h":12},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00010.png",
|
||||
"frame": {"x":168,"y":1,"w":10,"h":13},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":84,"y":59,"w":10,"h":13},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00011.png",
|
||||
"frame": {"x":177,"y":48,"w":13,"h":13},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":76,"y":72,"w":13,"h":13},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00012.png",
|
||||
"frame": {"x":139,"y":13,"w":17,"h":12},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":63,"y":80,"w":17,"h":12},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00013.png",
|
||||
"frame": {"x":158,"y":13,"w":20,"h":10},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":49,"y":84,"w":20,"h":10},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00014.png",
|
||||
"frame": {"x":160,"y":25,"w":21,"h":13},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":36,"y":81,"w":21,"h":13},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00015.png",
|
||||
"frame": {"x":114,"y":27,"w":21,"h":18},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":26,"y":73,"w":21,"h":18},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00016.png",
|
||||
"frame": {"x":94,"y":35,"w":18,"h":22},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":20,"y":63,"w":18,"h":22},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00017.png",
|
||||
"frame": {"x":123,"y":1,"w":14,"h":24},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":53,"w":14,"h":24},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00018.png",
|
||||
"frame": {"x":139,"y":1,"w":10,"h":27},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":42,"w":10,"h":27},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00019.png",
|
||||
"frame": {"x":114,"y":50,"w":13,"h":26},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":34,"w":13,"h":26},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00020.png",
|
||||
"frame": {"x":75,"y":35,"w":17,"h":25},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":19,"y":27,"w":17,"h":25},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00021.png",
|
||||
"frame": {"x":27,"y":41,"w":21,"h":22},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":21,"y":22,"w":21,"h":22},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00022.png",
|
||||
"frame": {"x":50,"y":41,"w":23,"h":19},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":25,"y":19,"w":23,"h":19},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00023.png",
|
||||
"frame": {"x":105,"y":1,"w":24,"h":16},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":30,"y":17,"w":24,"h":16},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-intro_190822_00024.png",
|
||||
"frame": {"x":134,"y":27,"w":24,"h":14},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":35,"y":16,"w":24,"h":14},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
}],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "1.0",
|
||||
"image": "play_icon_intro.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {"w":194,"h":64},
|
||||
"scale": "1",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:a423de98fed1e06cc62611996868a2db:909cd8dbde93c0d2fca4402e7546ffd8:5ea08d4d7caaa5043281f5095baffab1$"
|
||||
}
|
||||
}
|
||||
BIN
packages/ui/src/portal/assets/play_icon_intro.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
237
packages/ui/src/portal/assets/play_icon_loop.json
Normal file
@@ -0,0 +1,237 @@
|
||||
{"frames": [
|
||||
|
||||
{
|
||||
"filename": "processing-loop_190822_00000.png",
|
||||
"frame": {"x":37,"y":97,"w":23,"h":11},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":40,"y":16,"w":23,"h":11},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00001.png",
|
||||
"frame": {"x":37,"y":110,"w":23,"h":10},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":46,"y":16,"w":23,"h":10},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00002.png",
|
||||
"frame": {"x":30,"y":257,"w":24,"h":12},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":51,"y":16,"w":24,"h":12},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00003.png",
|
||||
"frame": {"x":34,"y":225,"w":25,"h":15},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":56,"y":16,"w":25,"h":15},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00004.png",
|
||||
"frame": {"x":1,"y":252,"w":27,"h":20},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":61,"y":17,"w":27,"h":20},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00005.png",
|
||||
"frame": {"x":35,"y":180,"w":24,"h":25},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":68,"y":19,"w":24,"h":25},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00006.png",
|
||||
"frame": {"x":39,"y":39,"w":21,"h":30},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":74,"y":24,"w":21,"h":30},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00007.png",
|
||||
"frame": {"x":1,"y":120,"w":16,"h":34},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":80,"y":30,"w":16,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00008.png",
|
||||
"frame": {"x":1,"y":58,"w":13,"h":36},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":83,"y":38,"w":13,"h":36},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00009.png",
|
||||
"frame": {"x":41,"y":1,"w":19,"h":36},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":77,"y":47,"w":19,"h":36},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00010.png",
|
||||
"frame": {"x":1,"y":151,"w":27,"h":33},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":68,"y":57,"w":27,"h":33},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00011.png",
|
||||
"frame": {"x":1,"y":73,"w":34,"h":26},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":57,"y":68,"w":34,"h":26},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00012.png",
|
||||
"frame": {"x":1,"y":1,"w":38,"h":18},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":46,"y":76,"w":38,"h":18},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00013.png",
|
||||
"frame": {"x":1,"y":21,"w":38,"h":13},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":36,"y":81,"w":38,"h":13},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00014.png",
|
||||
"frame": {"x":1,"y":36,"w":36,"h":20},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":27,"y":74,"w":36,"h":20},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00015.png",
|
||||
"frame": {"x":1,"y":197,"w":31,"h":27},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":21,"y":66,"w":31,"h":27},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00016.png",
|
||||
"frame": {"x":1,"y":226,"w":24,"h":31},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":57,"w":24,"h":31},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00017.png",
|
||||
"frame": {"x":1,"y":101,"w":17,"h":34},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":47,"w":17,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00018.png",
|
||||
"frame": {"x":1,"y":138,"w":11,"h":34},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":39,"w":11,"h":34},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00019.png",
|
||||
"frame": {"x":1,"y":180,"w":15,"h":32},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":31,"w":15,"h":32},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00020.png",
|
||||
"frame": {"x":37,"y":149,"w":20,"h":29},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":18,"y":25,"w":20,"h":29},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00021.png",
|
||||
"frame": {"x":37,"y":122,"w":22,"h":25},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":21,"y":21,"w":22,"h":25},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00022.png",
|
||||
"frame": {"x":39,"y":71,"w":24,"h":21},
|
||||
"rotated": true,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":25,"y":18,"w":24,"h":21},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00023.png",
|
||||
"frame": {"x":34,"y":206,"w":25,"h":17},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":30,"y":16,"w":25,"h":17},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
},
|
||||
{
|
||||
"filename": "processing-loop_190822_00024.png",
|
||||
"frame": {"x":34,"y":242,"w":25,"h":13},
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": {"x":36,"y":16,"w":25,"h":13},
|
||||
"sourceSize": {"w":110,"h":110},
|
||||
"pivot": {"x":0.5,"y":0.5}
|
||||
}],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "1.0",
|
||||
"image": "play_icon_loop.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {"w":61,"h":273},
|
||||
"scale": "1",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:4d92719faa27cbc50926429a66fc808c:25af8a045fca3b76dba8d8d26ed4ccf7:9b86d3b10829102d2ccf1bd5942144f2$"
|
||||
}
|
||||
}
|
||||
BIN
packages/ui/src/portal/assets/play_icon_loop.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
packages/ui/src/portal/assets/portal_background_placeholder.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
71
packages/ui/src/portal/index.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { $, component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
|
||||
import portal, { PortalButton, PortalIcon } from "./play";
|
||||
|
||||
//TODO: Fix the portal animation does not restart when a new element renders it. What i mean is, if you click one games, then quit, then click on another, the animation won't show.
|
||||
//FIXME: I dunno why the animation is not showing on the second game.
|
||||
|
||||
// interface PortalProps {
|
||||
// onComplete$: QRL<() => void>;
|
||||
// }
|
||||
|
||||
export default component$(() => {
|
||||
const buttonRef = useSignal<HTMLCanvasElement | undefined>();
|
||||
const iconRef = useSignal<HTMLCanvasElement | undefined>();
|
||||
const imagesLoaded = useSignal(false);
|
||||
|
||||
const imageUrls = [
|
||||
portal.assets.button_assets["intro"].image,
|
||||
portal.assets.button_assets["idle"].image,
|
||||
portal.assets.icon_assets["exit"].image,
|
||||
portal.assets.icon_assets["intro"].image,
|
||||
portal.assets.icon_assets["loop"].image
|
||||
];
|
||||
|
||||
const loadImages = $(() => {
|
||||
return Promise.all(imageUrls.map(url => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = url;
|
||||
});
|
||||
}));
|
||||
})
|
||||
|
||||
// eslint-disable-next-line qwik/no-use-visible-task
|
||||
useVisibleTask$(async ({ track }) => {
|
||||
track(() => imagesLoaded.value);
|
||||
|
||||
if (buttonRef.value && iconRef.value) {
|
||||
const [introImg, idleImg, exitImg, , loopImg] = await loadImages();
|
||||
|
||||
const button = new PortalButton(buttonRef.value);
|
||||
const icon = new PortalIcon(iconRef.value)
|
||||
// if (!isMounted) return;
|
||||
|
||||
await button.render("intro", false, introImg as HTMLImageElement);
|
||||
await icon.render("exit", false, exitImg as HTMLImageElement, false);
|
||||
await button.render("idle", true, idleImg as HTMLImageElement, 3);
|
||||
|
||||
// Intro and loop animation
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
if (iconRef.value) {
|
||||
await icon.render("loop", false, loopImg as HTMLImageElement, true);
|
||||
await icon.render("loop", false, loopImg as HTMLImageElement, true);
|
||||
await icon.render("exit", false, exitImg as HTMLImageElement, true);
|
||||
}
|
||||
})(),
|
||||
button.render("idle", true, idleImg as HTMLImageElement, 2),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<button class="relative rounded-full size-[100px] outline-none focus:ring-2 focus:ring-primary-500 hover:ring-2 hover:ring-primary-500 transition-all duration-200">
|
||||
<canvas class="absolute w-full h-full left-0 top-0 bottom-0 right-0" height={100} width={100} ref={buttonRef} />
|
||||
<canvas class="relative w-full h-full z-[5] left-0 top-0 bottom-0 right-0" height={100} width={100} ref={iconRef} />
|
||||
</button>
|
||||
)
|
||||
})
|
||||
309
packages/ui/src/portal/play.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
import PlayButtonIntro from "./assets/play_button_intro.json"
|
||||
import PlayButtonIdle from "./assets/play_button_idle.json"
|
||||
import PlayIconIntro from "./assets/play_icon_intro.json"
|
||||
import PlayIconLoop from "./assets/play_icon_loop.json"
|
||||
import PlayIconExit from "./assets/play_icon_exit.json"
|
||||
|
||||
const button_assets = {
|
||||
intro: {
|
||||
image: "/portal/play_button_intro.png",
|
||||
json: PlayButtonIntro
|
||||
},
|
||||
idle: {
|
||||
image: "/portal/play_button_idle.png",
|
||||
json: PlayButtonIdle
|
||||
}
|
||||
}
|
||||
|
||||
const icon_assets = {
|
||||
intro: {
|
||||
image: "/portal/play_icon_intro.png",
|
||||
json: PlayIconIntro
|
||||
},
|
||||
loop: {
|
||||
image: "/portal/play_icon_loop.png",
|
||||
json: PlayIconLoop
|
||||
},
|
||||
exit: {
|
||||
image: "/portal/play_icon_exit.png",
|
||||
json: PlayIconExit
|
||||
}
|
||||
}
|
||||
|
||||
export class PortalButton {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private currentFrame: number;
|
||||
private index: number;
|
||||
private buttonQueue: (() => void)[];
|
||||
private isButtonRunning: boolean;
|
||||
private animationSpeed: number;
|
||||
|
||||
constructor(canvas: HTMLCanvasElement) {
|
||||
this.canvas = canvas;
|
||||
this.currentFrame = 0;
|
||||
this.index = 0;
|
||||
this.buttonQueue = [];
|
||||
this.isButtonRunning = false;
|
||||
this.animationSpeed = 50;
|
||||
}
|
||||
|
||||
render(type: "intro" | "idle", loop: boolean, image: HTMLImageElement, index?: number) {
|
||||
if (index) this.index = index
|
||||
return new Promise<void>((resolve) => {
|
||||
const buttonTask = () => {
|
||||
// Get the canvas element
|
||||
const ctx = this.canvas.getContext('2d');
|
||||
|
||||
// Load the JSON data
|
||||
const animationData = button_assets[type].json;
|
||||
|
||||
// Play the animation
|
||||
const frames = animationData.frames;
|
||||
const totalFrames = frames.length;
|
||||
|
||||
if (this.index) this.currentFrame = this.index;
|
||||
|
||||
const targetDim = 100 //target dimensions of the output image (height, width)
|
||||
|
||||
// Start the animation
|
||||
const updateFrame = () => {
|
||||
|
||||
// Check if we have reached the last frame
|
||||
if (!loop && this.currentFrame === totalFrames - 1) {
|
||||
// Animation has reached the last frame, stop playing
|
||||
this.isButtonRunning = false;
|
||||
|
||||
// Resolve the Promise to indicate completion
|
||||
resolve();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clear the canvas
|
||||
ctx?.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Get the current frame details
|
||||
const singleFrame = frames[this.currentFrame];
|
||||
const { frame, sourceSize: ss, rotated, spriteSourceSize: sss, trimmed } = singleFrame;
|
||||
|
||||
this.canvas.width = targetDim;
|
||||
this.canvas.height = targetDim;
|
||||
this.canvas.style.borderRadius = `${ss.h / 2}px`
|
||||
|
||||
const newSize = {
|
||||
w: frame.w,
|
||||
h: frame.h
|
||||
};
|
||||
|
||||
const newPosition = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
|
||||
if (rotated) {
|
||||
ctx?.save()
|
||||
ctx?.translate(this.canvas.width / 2, this.canvas.height / 2)
|
||||
ctx?.rotate(-Math.PI / 2);
|
||||
ctx?.translate(-this.canvas.height / 2, -this.canvas.width / 2);
|
||||
|
||||
newSize.w = frame.h;
|
||||
newSize.h = frame.w;
|
||||
}
|
||||
|
||||
if (trimmed) {
|
||||
newPosition.x = sss.x;
|
||||
newPosition.y = sss.y;
|
||||
|
||||
if (rotated) {
|
||||
newPosition.x = this.canvas.height - sss.h - sss.y;
|
||||
newPosition.y = sss.x;
|
||||
}
|
||||
}
|
||||
|
||||
const scaleFactor = Math.min(targetDim / newSize.w, targetDim / newSize.h);
|
||||
const scaledWidth = newSize.w * scaleFactor;
|
||||
const scaledHeight = newSize.w * scaleFactor;
|
||||
|
||||
// Calculate the center position to draw the resized image
|
||||
const x = (targetDim - scaledWidth) / 2;
|
||||
const y = (targetDim - scaledHeight) / 2;
|
||||
|
||||
ctx?.drawImage(
|
||||
image,
|
||||
frame.x,
|
||||
frame.y,
|
||||
newSize.w,
|
||||
newSize.h,
|
||||
x,
|
||||
y,
|
||||
scaledWidth,
|
||||
scaledHeight
|
||||
)
|
||||
|
||||
|
||||
if (rotated) {
|
||||
ctx?.restore()
|
||||
}
|
||||
// Increment the frame index
|
||||
this.currentFrame = (this.currentFrame + 1) % totalFrames
|
||||
|
||||
// Schedule the next frame update
|
||||
setTimeout(updateFrame, this.animationSpeed);
|
||||
};
|
||||
|
||||
return updateFrame()
|
||||
}
|
||||
// Check if the button function is already running
|
||||
if (this.isButtonRunning) {
|
||||
// If running, add the button task to the queue
|
||||
this.buttonQueue.push(buttonTask);
|
||||
|
||||
} else {
|
||||
// If not running, set the flag and execute the button task immediately
|
||||
this.isButtonRunning = true;
|
||||
buttonTask();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class PortalIcon {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private currentFrame: number;
|
||||
private index: number;
|
||||
private iconQueue: (() => void)[];
|
||||
private isIconRunning: boolean;
|
||||
private animationSpeed: number;
|
||||
|
||||
constructor(canvas: HTMLCanvasElement) {
|
||||
this.canvas = canvas;
|
||||
this.currentFrame = 0;
|
||||
this.index = 0;
|
||||
this.iconQueue = [];
|
||||
this.isIconRunning = false;
|
||||
this.animationSpeed = 50;
|
||||
}
|
||||
|
||||
render(type: "loop" | "intro" | "exit", loop: boolean, image: HTMLImageElement, play: boolean) {
|
||||
return new Promise<void>((resolve) => {
|
||||
const iconTask = () => {
|
||||
// Get the canvas element
|
||||
const ctx = this.canvas.getContext('2d');
|
||||
|
||||
// Load the JSON data
|
||||
const animationData = icon_assets[type].json;
|
||||
|
||||
// Load the image
|
||||
// const image = new Image();
|
||||
image.src = icon_assets[type].image; // Path to the sprite sheet image
|
||||
|
||||
// Play the animation
|
||||
const frames = animationData.frames;
|
||||
const totalFrames = frames.length;
|
||||
|
||||
if (!play) {
|
||||
this.currentFrame = totalFrames - 3
|
||||
} else { this.currentFrame = 0 }
|
||||
|
||||
// Start the animation
|
||||
const updateFrame = () => {
|
||||
|
||||
// Check if we have reached the last frame
|
||||
if (!loop && this.currentFrame === totalFrames - 1) {
|
||||
// Animation has reached the last frame, stop playing
|
||||
this.isIconRunning = false;
|
||||
|
||||
// Resolve the Promise to indicate completion
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the canvas
|
||||
ctx?.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Get the current frame details
|
||||
const singleFrame = frames[this.currentFrame];
|
||||
const { frame, sourceSize: ss, rotated, spriteSourceSize: sss, trimmed } = singleFrame;
|
||||
|
||||
this.canvas.width = ss.w;
|
||||
this.canvas.height = ss.h
|
||||
this.canvas.style.borderRadius = `${ss.h / 2}px`
|
||||
|
||||
const newSize = {
|
||||
w: frame.w,
|
||||
h: frame.h
|
||||
};
|
||||
|
||||
const newPosition = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
|
||||
if (rotated) {
|
||||
ctx?.save()
|
||||
ctx?.translate(this.canvas.width / 2, this.canvas.height / 2)
|
||||
ctx?.rotate(-Math.PI / 2);
|
||||
ctx?.translate(-this.canvas.height / 2, -this.canvas.width / 2);
|
||||
|
||||
newSize.w = frame.h;
|
||||
newSize.h = frame.w;
|
||||
}
|
||||
|
||||
if (trimmed) {
|
||||
newPosition.x = sss.x;
|
||||
newPosition.y = sss.y;
|
||||
|
||||
if (rotated) {
|
||||
newPosition.x = this.canvas.height - sss.h - sss.y;
|
||||
newPosition.y = sss.x;
|
||||
}
|
||||
}
|
||||
|
||||
ctx?.drawImage(
|
||||
image,
|
||||
frame.x,
|
||||
frame.y,
|
||||
newSize.w,
|
||||
newSize.h,
|
||||
newPosition.x,
|
||||
newPosition.y,
|
||||
newSize.w,
|
||||
newSize.h
|
||||
)
|
||||
|
||||
|
||||
if (rotated) {
|
||||
ctx?.restore()
|
||||
}
|
||||
// Increment the frame index
|
||||
this.currentFrame = (this.currentFrame + 1) % totalFrames
|
||||
|
||||
|
||||
// Schedule the next frame update
|
||||
if (!play) {
|
||||
this.isIconRunning = false;
|
||||
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(updateFrame, this.animationSpeed)
|
||||
};
|
||||
|
||||
return updateFrame();
|
||||
}
|
||||
// Check if the icon function is already running
|
||||
if (this.isIconRunning) {
|
||||
// If running, add the button icon to the queue
|
||||
this.iconQueue.push(iconTask);
|
||||
} else {
|
||||
// If not running, set the flag and execute the button task immediately
|
||||
this.isIconRunning = true;
|
||||
iconTask();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const portal = { assets: { button_assets, icon_assets } }
|
||||
export default portal;
|
||||
@@ -118,6 +118,14 @@ export default {
|
||||
"100%": {
|
||||
transform: "translateX(-50%)"
|
||||
}
|
||||
},
|
||||
"zoom-out": {
|
||||
"0%": {
|
||||
transform: "scale(1.2)"
|
||||
},
|
||||
"100%": {
|
||||
transform: "scale(1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
@@ -125,7 +133,8 @@ export default {
|
||||
"fade-up": "fade-up 0.5s",
|
||||
"fade-down": "fade-down 0.5s",
|
||||
"shake": "shake 0.075s 8",
|
||||
"multicolor": "multicolor 5s linear 0s infinite"
|
||||
"multicolor": "multicolor 5s linear 0s infinite",
|
||||
"zoom-out": "zoom-out 5s ease-out"
|
||||
},
|
||||
},
|
||||
plugins: []
|
||||
|
||||