fix: Move more directories

This commit is contained in:
Wanjohi
2025-09-06 16:50:44 +03:00
parent 1c1c73910b
commit 9818165a90
248 changed files with 9 additions and 9566 deletions

View File

@@ -1,42 +0,0 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:qwik/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"],
ecmaVersion: 2021,
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"prefer-spread": "off",
"no-case-declarations": "off",
"no-console": "off",
"@typescript-eslint/no-unused-vars": ["error"],
"@typescript-eslint/consistent-type-imports": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
},
};

0
packages/ui/.gitkeep Normal file
View File

View File

@@ -1,520 +0,0 @@
@tailwind components;
@tailwind base;
@tailwind utilities;
@font-face {
font-family: 'Basement Grotesque';
font-style: normal;
font-display: swap;
font-weight: 800;
src: url("/fonts/BasementGrotesque-Black.woff2") format('woff2'), url("/fonts/BasementGrotesque-Black.woff") format('woff');
}
@layer base {
html {
width: 100%;
height: 100%;
}
html,
html * {
scrollbar-color: theme("colors.gray.700") theme("colors.gray.300");
scrollbar-width: thin;
scroll-behavior: smooth;
}
*::selection {
background-color: theme("colors.primary.100");
color: theme("colors.primary.500");
}
*::-moz-selection {
background-color: theme("colors.primary.100");
color: theme("colors.primary.500");
}
html.dark *::selection {
background-color: theme("colors.primary.500");
color: theme("colors.primary.100");
}
html.dark *::-moz-selection {
background-color: theme("colors.primary.500");
color: theme("colors.primary.100");
}
html.dark,
html.dark * {
scrollbar-color: theme("colors.gray.300") theme("colors.gray.700");
scrollbar-width: thin;
}
@media (prefers-color-scheme: dark) {
*::selection {
background-color: theme("colors.primary.500");
color: theme("colors.primary.100");
}
*::-moz-selection {
background-color: theme("colors.primary.500");
color: theme("colors.primary.100");
}
html,
html * {
scrollbar-color: theme("colors.gray.300") theme("colors.gray.700");
scrollbar-width: thin;
}
}
input:-webkit-autofill,
input:-webkit-autofill:focus {
transition: background-color 0s 600000s, color 0s 600000s !important;
}
}
.marquee-container {
width: 100%;
padding: 1rem;
scale: var(--scale);
}
.marquee-container[data-spill=true]::after {
--padding-x: 1rem;
--padding-y: 1rem;
content: "";
position: fixed;
top: 50%;
left: 50%;
background: hsl(10 80% 0% / 0.25);
width: calc(var(--scale) * 10000vw);
height: calc(var(--scale) * 10000vh);
pointer-events: none;
translate: -50% -50%;
mask:
linear-gradient(white, white) 50% 50% / 100% 100% no-repeat,
linear-gradient(white, white) 50% 50% / calc(100cqi + (var(--padding-x) * 2)) calc(100cqh + (var(--padding-y) * 2)) no-repeat;
mask-composite: exclude;
}
.marquee-container:not([data-spill=true]) {
overflow: hidden;
}
[data-direction=horizontal] {
height: 100%;
}
[data-direction=vertical] {
width: 100%;
}
.marquee-container ul {
display: flex;
padding: 0;
margin: 0;
list-style-type: none;
}
[data-reverse=true] * {
animation-direction: reverse !important;
}
[data-translate=track][data-direction=horizontal] ul {
--destination-x: -100%;
animation: track-translate calc(var(--speed) * 1s) infinite linear;
}
[data-translate=track][data-direction=vertical] ul {
--destination-y: -100%;
animation: track-translate calc(var(--speed) * 1s) infinite linear;
}
[data-translate=track][data-direction=horizontal][data-pad=true] ul {
--destination-x: calc((100% / -3) * 2);
translate: calc(100% / -3) 0;
}
[data-translate=track][data-direction=vertical][data-pad=true] ul {
--destination-y: calc((100% / -3) * 2);
translate: 0 calc(100% / -3);
}
[data-pad-diff=true] .pad {
background: hsl(0 0% 10%);
color: hsl(0 0% 98%);
}
@keyframes track-translate {
to {
translate: var(--destination-x, 0) var(--destination-y, 0);
}
}
[data-direction=horizontal] ul {
height: max-content;
width: fit-content;
align-items: center;
}
[data-direction=vertical] ul {
width: 100%;
height: fit-content;
justify-items: center;
flex-direction: column;
}
[data-play-state=running] :is(ul, li) {
animation-play-state: running !important;
}
[data-play-state=paused] :is(ul, li) {
animation-play-state: paused !important;
}
/* The animation stuff */
@media(prefers-reduced-motion: no-preference) {
[data-translate=items] ul {
gap: 0;
}
[data-translate=items][data-direction=horizontal].marquee-container {
padding-inline: 0;
}
[data-translate=items][data-direction=vertical].marquee-container {
padding-block: 0;
}
[data-translate=items][data-spill=true][data-direction=horizontal].marquee-container::after {
--padding-x: 0rem;
}
[data-translate=items][data-direction=vertical][data-spill=true].marquee-container::after {
--padding-y: 0rem;
}
[data-pause-on-hover=true]:hover li {
animation-play-state: paused !important;
}
[data-translate=items] li {
--duration: calc(var(--speed) * 1s);
--delay: calc((var(--duration) / var(--count)) * (var(--index, 0) - (var(--count) * 0.5)));
animation: slide var(--duration) calc(var(--delay) - (var(--count) * 0.5s)) infinite linear paused;
translate: var(--origin-x) var(--origin-y);
}
[data-translate=items][data-direction=horizontal] li {
--origin-x: calc(((var(--count) - var(--index)) + var(--inset, 0)) * 100%);
--origin-y: 0;
--destination-x: calc(calc((var(--index) + 1 + var(--outset, 0)) * -100%));
--destination-y: 0;
}
[data-translate=items][data-direction=vertical] li {
--origin-x: 0;
--origin-y: calc(((var(--count) - var(--index)) + var(--inset, 0)) * 100%);
--destination-x: 0;
--destination-y: calc(calc((var(--index) + 1 + var(--outset, 0)) * -100%));
}
@keyframes slide {
100% {
translate: var(--destination-x) var(--destination-y);
}
}
}
.marquee-animation {
-webkit-animation: loop-animate 60s linear infinite;
animation: loop-animate 60s linear infinite;
}
@keyframes loop-animate {
0% {
-webkit-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0);
}
100% {
-webkit-transform: translateX(-50%);
-ms-transform: translateX(-50%);
transform: translateX(-50%);
}
}
.digit_timing {
transition: translate 1s linear(0, 0.0009 8.51%, -0.0047 19.22%, 0.0016 22.39%, 0.023 27.81%,
0.0237 30.08%, 0.0144 31.81%, -0.0051 33.48%, -0.1116 39.25%, -0.1181 40.59%,
-0.1058 41.79%, -0.0455, 0.0701 45.34%, 0.9702 55.19%, 1.0696 56.97%,
1.0987 57.88%, 1.1146 58.82%, 1.1181 59.83%, 1.1092 60.95%, 1.0057 66.48%,
0.986 68.14%, 0.9765 69.84%, 0.9769 72.16%, 0.9984 77.61%, 1.0047 80.79%,
0.9991 91.48%, 1);
translate: 0 calc((var(--v) + 1) * (var(--line-height) * -1));
}
.modal-sheet {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
touch-action: none;
will-change: transform;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
animation-name: slideFromRight;
}
.modal::backdrop,
.modal-sheet::backdrop {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
touch-action: none;
will-change: transform;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
animation-name: fadeIn;
}
.modal-sheet[data-closing] {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
touch-action: none;
will-change: transform;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
animation-name: slideToRight;
}
.tooltip[data-closing],
.tooltip[data-closed] {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
touch-action: none;
will-change: transform;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
animation-name: fadeOut;
}
.tooltip[data-opening],
.tooltip[data-opened] {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
touch-action: none;
will-change: transform;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
animation-name: fadeIn;
}
.modal[data-closing]::backdrop,
.modal-sheet[data-closing]::backdrop {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
touch-action: none;
will-change: transform;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
animation-name: fadeOut;
}
.modal {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
touch-action: none;
will-change: transform;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
animation-name: modalIn;
}
.modal[data-closing] {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
touch-action: none;
will-change: transform;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
animation-name: modalOut;
}
@keyframes slideFromRight {
from {
transform: translate3d(var(--initial-transform, 100%), 0, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes slideToRight {
to {
transform: translate3d(var(--initial-transform, 100%), 0, 0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
to {
opacity: 0;
}
}
@keyframes modalIn {
from {
opacity: 0;
scale: 0.9;
}
to {
opacity: 1;
scale: 1;
}
}
@keyframes modalOut {
to {
opacity: 0;
scale: 0.9;
}
}
[data-component="spinner"] {
--spinner-size: 20px;
--spinner-color: #000;
@media (prefers-color-scheme: dark) {
--spinner-color: #FFF;
}
height: var(--spinner-size, 20px);
width: var(--spinner-size, 20px);
margin-left: calc(var(--spinner-size, 20px)*-1px);
/* display: none; */
&>div {
position: relative;
top: 50%;
left: 50%;
height: var(--spinner-size, 20px);
width: var(--spinner-size, 20px);
}
&>div>div {
animation: spin 1.2s linear infinite;
background: var(--spinner-color);
border-radius: 9999px;
height: 8%;
left: -10%;
position: absolute;
top: -3.9%;
width: 24%;
}
&>div>div:first-child {
animation-delay: -1.2s;
transform: rotate(.0001deg) translate(146%);
}
&>div>div:nth-child(2) {
animation-delay: -1.1s;
transform: rotate(30deg) translate(146%);
}
&>div>div:nth-child(3) {
animation-delay: -1s;
transform: rotate(60deg) translate(146%);
}
&>div>div:nth-child(4) {
animation-delay: -.9s;
transform: rotate(90deg) translate(146%);
}
&>div>div:nth-child(5) {
animation-delay: -.8s;
transform: rotate(120deg) translate(146%);
}
&>div>div:nth-child(6) {
animation-delay: -.7s;
transform: rotate(150deg) translate(146%);
}
&>div>div:nth-child(7) {
animation-delay: -.6s;
transform: rotate(180deg) translate(146%);
}
&>div>div:nth-child(8) {
animation-delay: -.5s;
transform: rotate(210deg) translate(146%);
}
&>div>div:nth-child(9) {
animation-delay: -.4s;
transform: rotate(240deg) translate(146%);
}
&>div>div:nth-child(10) {
animation-delay: -.3s;
transform: rotate(270deg) translate(146%);
}
&>div>div:nth-child(11) {
animation-delay: -.2s;
transform: rotate(300deg) translate(146%);
}
&>div>div:nth-child(12) {
animation-delay: -.1s;
transform: rotate(330deg) translate(146%);
}
}
@keyframes spin {
0% {
opacity: 1;
}
100% {
opacity: .15;
}
}
@keyframes bgRotate {
to {
content: var(--tw-content);
transform: rotate(1turn)
}
}
@keyframes playing {
0%,
to {
height: 3px
}
50% {
height: 12px
}
}
.shadow-browser {
box-shadow: 0 30.0333px 63.0111px rgba(45, 48, 57, .09), 0 12.5472px 26.3245px rgba(45, 48, 57, .065), 0 6.70834px 14.0744px rgba(45, 48, 57, .054), 0 3.76064px 7.88997px rgba(45, 48, 57, .045), 0 1.99725px 4.1903px rgba(45, 48, 57, .036), 0 .831099px 1.74368px rgba(45, 48, 57, .025);
border-radius: 7.07px;
}
.bg-radial-gradient {
filter: blur(32px);
background-image: linear-gradient(90deg, rgb(239, 118, 70), rgb(251, 91, 88), rgb(255, 61, 116), rgb(249, 33, 149), rgb(227, 34, 188), rgb(181, 94, 230), rgb(118, 128, 252), rgb(0, 150, 255), rgb(0, 183, 255), rgb(0, 208, 242), rgb(0, 227, 184), rgb(70, 239, 111));;
}

View File

@@ -1,62 +0,0 @@
{
"name": "@nestri/ui",
"version": "0.0.0",
"private": true,
"sideEffects": false,
"files": [
"tailwind.config.js",
"postcss.config.js",
"globals.css"
],
"exports": {
".": "./src/index.ts",
"./react": "./src/react/index.ts",
"./globals.css": "./globals.css",
"./postcss.config": "./postcss.config.js",
"./tailwind.config": "./tailwind.config.js",
"./image": "./src/image/index.ts",
"./design": "./src/design/index.ts"
},
"scripts": {
"lint": "eslint . --max-warnings 0"
},
"devDependencies": {
"@builder.io/qwik": "^1.8.0",
"@builder.io/qwik-city": "^1.8.0",
"@builder.io/qwik-react": "0.5.0",
"@fluejs/noscroll": "^1.0.0",
"@fontsource-variable/bricolage-grotesque": "^5.0.1",
"@fontsource/geist-mono": "^5.1.0",
"@fontsource/geist-sans": "^5.1.0",
"@fontsource-variable/mona-sans": "^5.0.1",
"@modular-forms/qwik": "^0.29.0",
"@nestri/core": "*",
"@qwik-ui/headless": "^0.6.4",
"@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",
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
"autoprefixer": "^10.4.20",
"body-scroll-lock-upgrade": "^1.1.0",
"clsx": "^2.1.1",
"eslint": "^8.57.0",
"eslint-plugin-qwik": "latest",
"focus-trap": "^7.5.4",
"framer-motion": "^11.3.24",
"nprogress": "^0.2.0",
"postcss": "^8.4.41",
"prettier": "latest",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-wrap-balancer": "^1.1.1",
"tailwind-merge": "^2.4.0",
"tailwind-variants": "^0.2.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.3.3",
"valibot": "^0.42.1"
}
}

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@@ -1,98 +0,0 @@
import { component$ } from "@builder.io/qwik";
const DEFAULT_COLORS = ['#6A5ACD', '#E63525','#20B2AA', '#E87D58'];
const getModulo = (value: number, divisor: number, useEvenCheck?: number) => {
const remainder = value % divisor;
if (useEvenCheck && Math.floor(value / Math.pow(10, useEvenCheck) % 10) % 2 === 0) {
return -remainder;
}
return remainder;
};
const generateColors = (name: string, colors = DEFAULT_COLORS) => {
const hashCode = name.split('').reduce((acc, char) => {
acc = ((acc << 5) - acc) + char.charCodeAt(0);
return acc & acc;
}, 0);
const hash = Math.abs(hashCode);
const numColors = colors.length;
return Array.from({ length: 3 }, (_, index) => ({
color: colors[(hash + index) % numColors],
translateX: getModulo(hash * (index + 1), 4, 1),
translateY: getModulo(hash * (index + 1), 4, 2),
scale: 1.2 + getModulo(hash * (index + 1), 2) / 10,
rotate: getModulo(hash * (index + 1), 360, 1)
}));
};
type Props = {
name: string;
size?: number;
class?:string;
colors?: string[]
}
export default component$(({ class:className, name, size = 80, colors = DEFAULT_COLORS }: Props) => {
const colorData = generateColors(name, colors);
return (
<svg
viewBox={`0 0 ${size} ${size}`}
fill="none"
role="img"
class={className}
aria-describedby={name}
width={size}
height={size}
>
<title id={name}>{`Fallback avatar for ${name}`}</title>
<mask
id="mask__marble"
maskUnits="userSpaceOnUse"
x={0}
y={0}
width={size}
height={size}
>
<rect width={size} height={size} rx={size * 2} fill="#FFFFFF" />
</mask>
<g mask="url(#mask__marble)">
<rect width={size} height={size} fill={colorData[0].color} />
<path
filter="url(#prefix__filter0_f)"
d="M32.414 59.35L50.376 70.5H72.5v-71H33.728L26.5 13.381l19.057 27.08L32.414 59.35z"
fill={colorData[1].color}
transform={`
translate(${colorData[1].translateX} ${colorData[1].translateY})
rotate(${colorData[1].rotate} ${size / 2} ${size / 2})
scale(${colorData[1].scale})
`}
/>
<path
filter="url(#prefix__filter0_f)"
style={{ mixBlendMode: "overlay" }}
d="M22.216 24L0 46.75l14.108 38.129L78 86l-3.081-59.276-22.378 4.005 12.972 20.186-23.35 27.395L22.215 24z"
fill={colorData[2].color}
transform={`
translate(${colorData[2].translateX} ${colorData[2].translateY})
rotate(${colorData[2].rotate} ${size / 2} ${size / 2})
scale(${colorData[2].scale})
`}
/>
</g>
<defs>
<filter
id="prefix__filter0_f"
filterUnits="userSpaceOnUse"
color-interpolation-filters="s-rGB"
>
<feFlood flood-opacity={0} result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation={7} result="effect1_foregroundBlur" />
</filter>
</defs>
</svg>
)
})

View File

@@ -1,46 +0,0 @@
import { component$ } from "@builder.io/qwik";
import { cn } from "./design";
type Props = {
bgColor?: string;
textColor?: string;
title?: string;
class?: string;
}
export default component$(({ bgColor = "hsla(0,0%,89%,1)", textColor = "#000", title = "Design Engineering at Vercel", class: className }: Props) => {
return (
<div style={{ "--book-width": 196, "--book-default-width": 196, "--book-color": bgColor, "--book-text-color": textColor, "--book-depth": "29cqw", "--hover-rotate": "-20deg", "--hover-scale": 1.066, "--hover-translate-x": "-8px" }} class={cn("[perspective:900px] inline-block w-fit rounded-[6px_4px_4px_6px] group", className)}>
<div class="aspect-[49/60] w-fit rotate-0 relative [transform-style:preserve-3d] min-w-[calc(var(--book-width)*1px)] [transition:transform_.25s_ease-out] [container-type:inline-size]
group-hover:[transform:rotateY(var(--hover-rotate))_scale(var(--hover-scale))_translateX(var(--hover-translate-x))] [.flip_&]:[transform:rotateY(var(--hover-rotate))_scale(var(--hover-scale))_translateX(var(--hover-translate-x))]">
<div class="bg-[--book-color] absolute min-w-[calc(var(--book-width)*1px)] w-[calc(var(--book-width)*1px)] h-full overflow-hidden rounded-[6px_4px_4px_6px] [box-shadow:0_1px_1px_0_rgba(0,0,0,.02),0_4px_8px_-4px_rgba(0,0,0,.1),0_16px_24px_-8px_rgba(0,0,0,.03)] [transform:translateZ(0)]
after:absolute after:inset-0 after:border after:border-black/[.08] after:w-full after:h-full after:rounded-[inherit] after:[box-shadow:inset_0_1px_2px_0_hsla(0,0%,100%,.3)] after:pointer-events-none">
<div
style={{
boxShadow: "0 1.8px 3.6px rgba(0,0,0,.05),0 10.8px 21.6px rgba(0,0,0,.08),inset 0 -.9px 0 rgba(0,0,0,.1),inset 0 1.8px 1.8px hsla(0,0%,100%,.1),inset 3.6px 0 3.6px rgba(0,0,0,.1)",
background: "linear-gradient(180deg,hsla(0,0%,100%,.1) 0,hsla(0,0%,100%,0) 50%,hsla(0,0%,100%,0) 100%),var(--book-color)"
}}
class="size-full flex">
<div
style={{ background: "linear-gradient(90deg,hsla(0,0%,100%,0),hsla(0,0%,100%,0) 12%,hsla(0,0%,100%,.25) 29.25%,hsla(0,0%,100%,0) 50.5%,hsla(0,0%,100%,0) 75.25%,hsla(0,0%,100%,.25) 91%,hsla(0,0%,100%,0)),linear-gradient(90deg,rgba(0,0,0,.03),rgba(0,0,0,.1) 12%,transparent 30%,rgba(0,0,0,.02) 50%,rgba(0,0,0,.2) 73.5%,rgba(0,0,0,.5) 75.25%,rgba(0,0,0,.15) 85.25%,transparent)" }}
class="mix-blend-overlay opacity-90 min-w-[8.2%] h-full w-[8.2%]" />
<div class="flex mt-[5%] flex-col gap-[calc((16px_/_var(--book-default-width))_*_var(--book-width))] p-[6.1%] [container-type:inline-size] w-full">
<span
style={{ textShadow: "0 .025em .5px color-mix(in srgb,var(--book-color) 80%,#fff 20%),-.02em -.02em .5px color-mix(in srgb,var(--book-color) 80%,#000 20%)" }}
class="leading-[1.7rem] text-left font-semibold text-[1.7rem] tracking-[-.02em] text-balance text-[--book-text-color]">{title}</span>
</div>
</div>
<div class="bg-[url(/images/book-texture.avif)] bg-cover absolute inset-0 mix-blend-hard-light rounded-[6px_4px_4px_6px] bg-no-repeat opacity-50 pointer-events-none [filter:brightness(1.1)]" />
</div>
<div
class="h-[calc(100%-2*3px)] w-[calc(var(--book-depth)-2px)] top-[3px] rounded-[6px_4px_4px_6px] overflow-hidden absolute [transform:translateX(calc(var(--book-width)*1px-var(--book-depth)/2-3px))_rotateY(90deg)_translateX(calc(var(--book-depth)_/_2))]"
style={{ background: "repeating-linear-gradient(90deg,#fff,#efefef 1px,#fff 3px,#9a9a9a 0)" }} />
<div
style={{
boxShadow: "0 1.8px 3.6px rgba(0,0,0,.05),0 10.8px 21.6px rgba(0,0,0,.08),inset 0 -.9px 0 rgba(0,0,0,.1),inset 0 1.8px 1.8px hsla(0,0%,100%,.1),inset 3.6px 0 3.6px rgba(0,0,0,.1)",
background: "linear-gradient(180deg,hsla(0,0%,100%,.1) 0,hsla(0,0%,100%,0) 50%,hsla(0,0%,100%,0) 100%),var(--book-color)"
}}
class="bg-[--book-color] absolute left-0 w-[calc(var(--book-width)*1px)] h-full rounded-[6px_4px_4px_6px] [transform:translateZ(calc(-1*var(--book-depth)))]" />
</div>
</div>
)
})

View File

@@ -1,124 +0,0 @@
import { $, component$, type PropsOf, useVisibleTask$ } from "@builder.io/qwik";
import { Modal, Portal } from "@nestri/ui";
import { cn } from "@nestri/ui/design"
import { BasicImageLoader } from "./image";
interface Props extends PropsOf<"div"> {
game: {
name: string;
id: number;
},
size: "xs" | "small" | "large";
titleWidth: number;
titleHeight: number;
}
//TODO: Show gradient animation while image loads
export const Card = component$(({ titleWidth, titleHeight, game, size, ...props }: 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.crossOrigin = "anonymous"
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}));
});
// eslint-disable-next-line qwik/no-use-visible-task
useVisibleTask$(async () => {
await loadModalImages();
});
return (
<Modal.Root
class="w-full"
{...(props as any)}>
{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 }}>
<BasicImageLoader class="rounded-full size-full" width={24} height={24} src={`https://nexus.nestri.workers.dev/image/avatar/avatar-${index + 1}.png`} alt={`avatar-${index + 1}`} />
</div>
))}
</div>
<span class="text-sm max-w-[70%]">
<span class="font-semibold font-title">JD the Smith</span>&nbsp;
{"and"}&nbsp;
{"15 others"}&nbsp;
{"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">
<BasicImageLoader
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}
src={imageUrl}
alt={game.name}
/>
</section>
</Modal.Trigger>) : size == "small" ? (
<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">
<BasicImageLoader
class="rounded-2xl ring-2 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={230}
height={345}
src={imageUrl}
alt={game.name}
/>
</section>
</Modal.Trigger>
) : (
<Modal.Trigger class="w-full h-full">
<BasicImageLoader width={251} height={314} src={imageUrl} alt={game.name} />
</Modal.Trigger>
)}
<Modal.Panel
class="lg:w-full w-[calc(100%-1rem)] 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 rounded-xl mx-auto inset-0 h-full relative flex overflow-hidden",
"before:absolute before:inset-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>
</Modal.Panel>
</Modal.Root >
)
});

View File

@@ -1,4 +0,0 @@
export const CONSTANTS = {
githubLink: "https://github.com/nestrilabs/nestri#start",
enterpriseContact: "mailto:enterprise@nestri.io"
}

View File

@@ -1,178 +0,0 @@
// The solid and outlined variants are based on CSS Pro 3D Buttons code (https://csspro.com/css-3d-buttons/)
import { tv } from "tailwind-variants";
export type ButtonVariantProps = {
variant?: keyof typeof buttonVariants | undefined;
intent?: keyof typeof solid.variants.intent;
size?: keyof typeof baseButton.variants.size;
};
export type ButtonVariantIconProps = {
type?: keyof typeof buttonIcon.variants.type;
size?: keyof typeof buttonIcon.variants.size;
};
const baseButton = tv({
base: "group font-title flex justify-center select-none gap-1.5 items-center rounded-lg outline-2 outline-offset-2 focus-visible:outline outline-primary-6000 disabled:text-gray-400 disabled:border disabled:border-gray-300 dark:disabled:bg-gray-500/10 disabled:bg-gray-200 disabled:shadow-none disabled:hover:brightness-100 dark:disabled:text-gray-700 dark:disabled:shadow-none disabled:cursor-not-allowed dark:disabled:border dark:disabled:border-gray-700", // dark:disabled:[background-image:none]
variants: {
size: {
xs: "text-sm h-7 px-3",
sm: "text-sm h-8 px-3.5",
md: "text-base h-9 px-4",
lg: "text-base h-10 px-5",
xl: "text-lg h-12 px-6",
},
iconOnlyButtonSize: {
xs: "size-7",
sm: "size-8",
md: "size-9",
lg: "size-10",
xl: "size-12",
},
defaultVariants: {
intent: "primary",
size: "md",
},
},
});
const link = tv({
// extend: baseButton,
// base: "group transition-shadows relative ml-1.5 font-medium shadow-[inset_0_-0.2em_0_0_theme(colors.primary.200)] dark:shadow-[inset_0_-0.2em_0_0_theme(colors.primary.600)] duration-300 ease-out hover:shadow-[inset_0_-1em_0_0_theme(colors.primary.200)] dark:hover:shadow-[inset_0_-1em_0_0_theme(colors.primary.600)] text-primary-950 font-medium dark:text-primary-50", //"relative text-primary-950 font-medium dark:text-primary-50 hover:after:w-[calc(100%+2px)] focus:after:w-[calc(100%+2px)] px-1 after:-bottom-1 transition-all duration-[.2s] ease-[cubic-bezier(.165,.84,.44,1)] after:w-0 after:h-0.5 after:bg-primary-950 dark:after:bg-primary-50 after:absolute after:left-0 after:transition-[width] after:duration-[.2s] focus:shadow-none outline-none"
base: "group relative ml-1.5 font-medium border-b-2 border-primary-950 dark:border-primary-50 hover:border-primary-950/60 dark:hover:border-primary-50/60 hover:text-primary-950/70 dark:hover:text-primary-50/70 text-primary-950 font-medium dark:text-primary-50", //"relative text-primary-950 font-medium dark:text-primary-50 hover:after:w-[calc(100%+2px)] focus:after:w-[calc(100%+2px)] px-1 after:-bottom-1 transition-all duration-[.2s] ease-[cubic-bezier(.165,.84,.44,1)] after:w-0 after:h-0.5 after:bg-primary-950 dark:after:bg-primary-50 after:absolute after:left-0 after:transition-[width] after:duration-[.2s] focus:shadow-none outline-none"
variants: {
size: {
xs: "text-sm h-7 px-3",
sm: "text-sm h-8 px-3.5",
md: "text-base h-9 px-4",
lg: "text-base h-10 px-5",
xl: "text-lg h-12 px-6",
},
iconOnlyButtonSize: {
xs: "size-7",
sm: "size-8",
md: "size-9",
lg: "size-10",
xl: "size-12",
},
defaultVariants: {
intent: "primary",
size: "md",
},
},
});
const solid = tv({
extend: baseButton,
base: "bg-gradient-to-b [box-shadow:rgba(255,255,255,0.25)_0px_1px_0px_0px_inset,var(--btn-border-color)_0px_0px_0px_1px] text-white hover:brightness-[1.1] transition-[filter] duration-150 ease-in-out active:brightness-95 dark:border-t dark:shadow-white/10 disabled:from-gray-200 disabled:to-gray-200 dark:disabled:text-gray-400 dark:disabled:from-gray-800 dark:disabled:to-gray-800",
variants: {
intent: {
primary:
"from-primary-500 to-primary-600 [--btn-border-color:theme(colors.primary.600)] dark:border-primary-500/75",
secondary:
"from-secondary-500 to-secondary-600 [--btn-border-color:theme(colors.secondary.700)] dark:border-secondary-400/75",
accent: "from-accent-500 to-accent-600 [--btn-border-color:theme(colors.accent.700)] dark:border-accent-400/75",
danger: "from-danger-500 to-danger-600 [--btn-border-color:theme(colors.danger.700)] dark:border-danger-400/75",
info: "from-info-500 to-info-600 [--btn-border-color:theme(colors.info.700)] dark:border-info-400/75",
success:
"from-success-500 to-success-600 [--btn-border-color:theme(colors.success.700)] dark:border-success-400/75",
warning:
"from-warning-400 to-warning-500 text-warning-950 [--btn-border-color:theme(colors.warning.600)] dark:border-warning-300",
gray: "from-gray-500 to-gray-600 [--btn-border-color:theme(colors.gray.700)] dark:border-gray-500",
neutral:
"bg-gray-900 [background-image:radial-gradient(76%_151%_at_52%_-52%,rgba(255,255,255,0.5)_0%,transparent_100%)] [box-shadow:rgba(255,255,255,0.3)_0px_1px_0px_0px_inset,theme(colors.gray.950)_0px_0px_0px_1px] hover:brightness-125 dark:bg-white dark:text-gray-950 dark:border-gray-300",
},
},
});
const outlined = tv({
extend: baseButton,
base: "[--outline-radial-opacity:0.6] dark:[background-image:none] [--inner-border-color:1] dark:[--inner-border-color:0] dark:[--outline-radial-opacity:0.2] [background-image:radial-gradient(76%_151%_at_52%_-52%,rgba(255,255,255,var(--outline-radial-opacity))_0%,transparent_100%)] [box-shadow:rgba(255,255,255,var(--inner-border-color))_0px_1px_0px_0px_inset,var(--btn-border-color)_0px_0px_0px_1px,0px_1px_2px_rgba(0,0,0,0.1)] hover:brightness-[0.98] active:brightness-100 transtion-[filter] ease-in-out duration-150",
variants: {
intent: {
primary:
"[--btn-border-color:theme(colors.primary.200)] dark:[--btn-border-color:theme(colors.primary.500/0.3)] text-primary-800 bg-primary-50 dark:text-primary-300 dark:bg-primary-500/5 dark:hover:bg-primary-500/10 dark:active:bg-primary-500/5",
secondary:
"[--btn-border-color:theme(colors.secondary.200)] dark:[--btn-border-color:theme(colors.secondary.500/0.3)] text-secondary-800 bg-secondary-50 dark:text-secondary-300 dark:bg-secondary-500/5 dark:hover:bg-secondary-500/10 dark:active:bg-secondary-500/5",
accent: "[--btn-border-color:theme(colors.accent.200)] dark:[--btn-border-color:theme(colors.accent.500/0.3)] text-accent-800 bg-accent-50 dark:text-accent-300 dark:bg-accent-500/5 dark:hover:bg-accent-500/10 dark:active:bg-accent-500/5",
danger: "[--btn-border-color:theme(colors.danger.200)] dark:[--btn-border-color:theme(colors.danger.500/0.3)] text-danger-800 bg-danger-50 dark:text-danger-300 dark:bg-danger-500/5 dark:hover:bg-danger-500/10 dark:active:bg-danger-500/5",
info: "[--btn-border-color:theme(colors.info.200)] dark:[--btn-border-color:theme(colors.info.500/0.3)] text-info-800 bg-info-50 dark:text-info-300 dark:bg-info-500/5 dark:hover:bg-info-500/10 dark:active:bg-info-500/5",
success:
"[--btn-border-color:theme(colors.success.200)] dark:[--btn-border-color:theme(colors.success.500/0.3)] text-success-800 bg-success-100 dark:text-success-300 dark:bg-success-500/5 dark:hover:bg-success-500/10 dark:active:bg-success-500/5",
warning:
"[--btn-border-color:theme(colors.warning.200)] dark:[--btn-border-color:theme(colors.warning.500/0.3)] text-warning-800 bg-warning-50 dark:text-warning-300 dark:bg-warning-500/5 dark:hover:bg-warning-500/10 dark:active:bg-warning-500/5",
gray: "[--btn-border-color:theme(colors.gray.200)] dark:[--btn-border-color:theme(colors.gray.500/0.3)] text-gray-800 bg-gray-50 dark:text-gray-300 dark:bg-gray-500/5 dark:hover:bg-gray-500/10 dark:active:bg-gray-500/5",
neutral:
"[--btn-border-color:theme(colors.gray.300)] dark:[--btn-border-color:theme(colors.gray.700)] text-gray-800 bg-gray-100 dark:text-white dark:bg-gray-500/5 dark:hover:bg-gray-500/10 dark:active:bg-gray-500/5",
},
},
});
const soft = tv({
extend: baseButton,
variants: {
intent: {
primary:
"text-primary-700 bg-primary-100 hover:bg-primary-200/75 active:bg-primary-100 dark:text-primary-300 dark:bg-primary-500/10 dark:hover:bg-primary-500/15 dark:active:bg-primary-500/10",
secondary:
"text-secondary-700 bg-secondary-100 hover:bg-secondary-200/75 active:bg-secondary-100 dark:text-secondary-300 dark:bg-secondary-500/10 dark:hover:bg-secondary-500/15 dark:active:bg-secondary-500/10",
accent: "text-accent-700 bg-accent-100 hover:bg-accent-200/75 active:bg-accent-100 dark:text-accent-300 dark:bg-accent-500/10 dark:hover:bg-accent-500/15 dark:active:bg-accent-500/10",
danger: "text-danger-700 bg-danger-100 hover:bg-danger-200/75 active:bg-danger-100 dark:text-danger-300 dark:bg-danger-500/10 dark:hover:bg-danger-500/15 dark:active:bg-danger-500/10",
info: "text-info-700 bg-info-100 hover:bg-info-200/75 active:bg-info-100 dark:text-info-300 dark:bg-info-500/10 dark:hover:bg-info-500/15 dark:active:bg-info-500/10",
success:
"text-success-700 bg-success-100 hover:bg-success-200/75 active:bg-success-100 dark:text-success-300 dark:bg-success-500/10 dark:hover:bg-success-500/15 dark:active:bg-success-500/10",
warning:
"text-warning-700 bg-warning-100 hover:bg-warning-200/75 active:bg-warning-100 dark:text-warning-300 dark:bg-warning-500/10 dark:hover:bg-warning-500/15 dark:active:bg-warning-500/10",
gray: "text-gray-800 bg-gray-100 hover:bg-gray-200/75 active:bg-gray-100 dark:text-gray-300 dark:bg-gray-500/10 dark:hover:bg-gray-500/15 dark:active:bg-gray-500/10",
neutral:
"text-gray-950 bg-gray-100 hover:bg-gray-950 hover:text-white active:text-white active:bg-gray-900 dark:text-gray-300 dark:bg-gray-500/10 dark:hover:bg-white dark:hover:text-gray-950 dark:active:bg-gray-200 dark:active:text-gray-950",
},
},
});
const ghost = tv({
extend: baseButton,
variants: {
intent: {
primary:
"hover:bg-gray-100 active:bg-primary-200/75 dark:hover:bg-gray-800 dark:active:bg-primary-500/15",
secondary:
"text-secondary-600 hover:bg-secondary-100 active:bg-secondary-200/75 dark:text-secondary-400 dark:hover:bg-secondary-500/10 dark:active:bg-secondary-500/15",
accent: "text-accent-600 hover:bg-accent-100 active:bg-accent-200/75 dark:text-accent-400 dark:hover:bg-accent-500/10 dark:active:bg-accent-500/15",
danger: "text-danger-600 hover:bg-danger-100 active:bg-danger-200/75 dark:text-danger-400 dark:hover:bg-danger-500/10 dark:active:bg-danger-500/15",
info: "text-info-600 hover:bg-info-100 active:bg-info-200/75 dark:text-info-400 dark:hover:bg-info-500/10 dark:active:bg-info-500/15",
success:
"text-success-600 hover:bg-success-100 active:bg-success-200/75 dark:text-success-400 dark:hover:bg-success-500/10 dark:active:bg-success-500/15",
warning:
"text-warning-600 hover:bg-warning-100 active:bg-warning-200/75 dark:text-warning-400 dark:hover:bg-warning-500/10 dark:active:bg-warning-500/15",
gray: "text-gray-800 hover:bg-gray-100 active:bg-gray-200/75 dark:text-gray-300 dark:hover:bg-gray-500/10 dark:active:bg-gray-500/15",
neutral:
"text-gray-950 hover:bg-gray-950 hover:text-white active:text-white active:bg-gray-900 dark:text-white dark:hover:bg-white dark:hover:text-gray-950 dark:active:bg-gray-200 dark:active:text-gray-950",
},
},
});
export const buttonIcon = tv({
variants: {
type: {
leading: "-ml-1",
trailing: "-mr-1",
only: "m-auto",
},
size: {
xs: "size-3.5",
sm: "size-4",
md: "size-[1.125rem]",
lg: "size-5",
xl: "size-6",
},
},
});
export const buttonVariants = {
solid,
outlined,
soft,
ghost,
link
};

View File

@@ -1,215 +0,0 @@
import { tv } from "tailwind-variants";
export const form = tv({
slots: {
label: "text-nowrap text-[--title-text-color]",
input: "[--btn-radius:lg] w-full px-[--input-px] bg-transparent peer transition-[outline] placeholder-[--placeholder-text-color] text-[--title-text-color] rounded-[--btn-radius] disabled:opacity-50",
message: "mt-2 text-[--caption-text-color]",
icon: "absolute inset-y-0 my-auto text-[--placeholder-text-color] pointer-events-none",
field: "relative group *:has-[:disabled]:opacity-50 *:has-[:disabled]:pointer-events-none data-[invalid]:[--caption-text-color:theme(colors.danger.600)] dark:data-[invalid]:[--caption-text-color:theme(colors.danger.400)] data-[valid]:[--caption-text-color:theme(colors.success.600)] dark:data-[valid]:[--caption-text-color:theme(colors.success.400)]",
textarea: "py-[calc(var(--input-px)/1.5)] h-auto",
},
variants: {
variant: {
outlined: {
input: "outline-2 focus:outline-primary-600 -outline-offset-1 focus:outline border data-[invalid]:border-danger-600 focus:data-[invalid]:outline-danger-600 dark:data-[invalid]:border-danger-500 dark:focus:data-[invalid]:outline-danger-500 data-[valid]:border-success-600 focus:data-[valid]:outline-success-600 dark:data-[valid]:border-success-500 dark:focus:data-[valid]:outline-success-500",
},
soft: {
input: "outline-none bg-[--ui-soft-bg] focus:brightness-95 dark:focus:brightness-105 data-[invalid]:[--ui-soft-bg:theme(colors.danger.100)] dark:data-[invalid]:[--ui-soft-bg:theme(colors.danger.800/0.25)] data-[valid]:[--ui-soft-bg:theme(colors.success.100)] dark:data-[valid]:[--ui-soft-bg:theme(colors.success.800/0.25)]",
},
mixed: {
input: "placeholder-gray-950/50 dark:placeholder-gray-50/50 shadow-sm hover:shadow-lg dark:hover:shadow-sm shadow-gray-950/50 dark:shadow-gray-50 outline-2 focus:outline-primary-600 focus:outline -outline-offset-1 border border-primary-950 dark:border-primary-50 bg-gray-100 dark:bg-gray-800 data-[invalid]:border-2 data-[invalid]:border-danger-600 focus:data-[invalid]:outline-danger-600 dark:data-[invalid]:border-danger-500 dark:focus:data-[invalid]:outline-danger-500 data-[valid]:border-success-600 focus:data-[valid]:outline-success-600 dark:data-[valid]:border-success-500 dark:focus:data-[valid]:outline-success-500",
},
plain: {
input: "rounded-none px-0 outline-none bg-transparent invalid:text-danger-600 dark:invalid:text-danger-400",
},
bottomOutlined: {
input: "rounded-none transition-[border] px-0 focus:outline-none border-b focus:border-b-2 focus:border-primary-600 data-[invalid]:border-danger-400 dark:data-[invalid]:border-danger-600 data-[valid]:border-success-400 dark:data-[valid]:border-success-600",
},
},
size: {
xs: {
message: "text-xs",
},
sm: {
label: "text-sm",
message: "text-sm",
input: "text-sm h-8 [--input-px:theme(spacing[2.5])]",
field: "[--input-px:theme(spacing[2.5])]",
},
md: {
label: "text-base",
message: "text-base",
input: "text-sm h-9 [--input-px:theme(spacing[3])]",
field: "[--input-px:theme(spacing[3])]",
},
lg: {
label: "text-lg",
input: "text-base h-10 [--input-px:theme(spacing[4])]",
field: "[--input-px:theme(spacing[4])]",
},
xl: {
label: "text-xl",
input: "text-base h-12 [--input-px:theme(spacing[5])]",
field: "[--input-px:theme(spacing[5])]",
},
},
fancy: {
true: {
input: "shadow-inner shadow-gray-950/5 dark:shadow-gray-950/35",
}
},
floating: {
true: {
label: "absolute block inset-y-0 text-base left-[--input-px] h-fit text-nowrap my-auto text-[--caption-text-color] pointer-events-none transition duration-150 scale-[.8] origin-top-left peer-placeholder-shown:scale-100 peer-focus:scale-[.8] peer-placeholder-shown:translate-y-0",
},
},
asTextarea: {
true: {
label: "top-[calc(var(--input-px)/1.5)] mt-0",
},
},
},
compoundVariants: [
{
floating: true,
variant: ["bottomOutlined", "plain"],
class: {
input: "px-0",
label: "left-0",
},
},
{
floating: true,
variant: ["outlined", "soft", "bottomOutlined", "mixed"],
size: "xl",
class: {
field: "[--input-px:theme(spacing[2.5])]",
input: "pt-3.5 h-14",
label: "-translate-y-2.5 peer-focus:-translate-y-2.5",
},
},
{
floating: true,
variant: ["soft", "bottomOutlined"],
size: "lg",
class: {
input: "pt-4 h-12",
label: "-translate-y-2 peer-focus:-translate-y-2",
},
},
{
floating: true,
variant: ["outlined", "mixed"],
size: "lg",
class: {
input: "h-12",
label: "-translate-y-[21.5px] peer-focus:-translate-y-[21.5px]",
},
},
{
floating: true,
variant: ["outlined", "bottomOutlined", "mixed"],
size: "md",
class: {
input: "h-9",
label: "-translate-y-[15.5px] peer-focus:-translate-y-[15.5px]",
},
},
{
floating: true,
variant: ["outlined", "bottomOutlined", "mixed"],
size: "sm",
class: {
input: "h-8",
label: "text-base -translate-y-[13.5px] peer-focus:-translate-y-[13.5px] peer-placeholder-shown:text-sm peer-focus:text-base",
},
},
{
floating: true,
size: ["sm", "md", "lg"],
variant: ["outlined", "mixed"],
class: {
label: "peer-placeholder-shown:before:scale-x-0 peer-focus:before:scale-x-100 before:scale-x-100 before:absolute peer-placeholder-shown:before:transition-none before:transition-none peer-focus:before:transition peer-focus:before:delay-[.01s] before:duration-500 before:-z-[1] peer-placeholder-shown:before:-top-[9px] before:top-[48%] peer-focus:before:top-[44%] before:-inset-x-1 before:h-0.5 before:my-auto group-has-[:focus]:before:h-[3px] before:bg-white dark:before:bg-[--ui-bg]",
},
},
{
floating: true,
variant: ["outlined", "mixed"],
class: {
label: "translate-x-px",
},
},
{
floating: true,
asTextarea: true,
size: ["lg", "xl"],
variant: "bottomOutlined",
class: {
input: "pt-0",
},
},
{
variant: "plain",
class: {
input: "py-0",
},
},
{
floating: true,
asTextarea: true,
size: ["xl"],
class: {
input: "pt-7",
label: "-translate-y-1 peer-focus:-translate-y-1",
},
},
{
floating: true,
asTextarea: true,
size: ["lg"],
class: {
input: "pt-6",
label: "-translate-y-1 peer-focus:-translate-y-1 before:hidden",
},
},
{
floating: true,
asTextarea: true,
size: ["md"],
variant: ["outlined", "bottomOutlined", "mixed"],
class: {
label: "-translate-y-[17.5px] peer-focus:-translate-y-[17.5px]",
},
},
{
floating: true,
asTextarea: true,
size: ["sm"],
variant: ["outlined", "bottomOutlined", "mixed"],
class: {
label: "-translate-y-4 peer-focus:-translate-y-4",
},
},
],
});
export type FormProps = {
variant?: keyof typeof form.variants.variant;
size?: keyof typeof form.variants.size;
floating?: boolean;
asTextarea?: boolean;
};
export type InputProps = Omit<FormProps, "asTextarea"> & {
size?: Exclude<FormProps["size"], "xs">;
fancy?: boolean;
};
export type LabelProps = FormProps & {
size?: Exclude<FormProps["size"], "xs">;
};
export type MessageProps = {
size?: Exclude<FormProps["size"], "lg" | "xl">;
};

View File

@@ -1,4 +0,0 @@
export * from "./button-variants"
export * from "./typography"
export * from "./utils"
export * from "./form"

View File

@@ -1,401 +0,0 @@
import { tv } from "tailwind-variants";
export const base = tv({
variants: {
weight: {
black: "font-black",
bold: "font-bold",
semibold: "font-semibold",
medium: "font-medium",
normal: "font-normal",
},
align: {
left: "text-left",
center: "text-center",
right: "text-right",
},
},
defaultVariants: {
size: "xl",
weight: "normal",
},
});
export const caption = tv(
{
extend: base,
base: "text-gray-500",
variants: {
size: {
xs: "text-xs",
sm: "text-sm",
base: "text-base",
},
neutral: {
true: "text-gray-950 dark:text-white",
},
},
defaultVariants: {
size: "sm",
weight: "normal",
},
},
{
responsiveVariants: ["sm", "md", "lg", "xl", "2xl"],
},
);
export const text = tv(
{
extend: base,
base: "text-gray-700",
variants: {
size: {
sm: "text-sm",
base: "text-base",
lg: "text-lg",
xl: "text-xl",
},
neutral: {
true: "text-gray-950 dark:text-white",
},
},
defaultVariants: {
size: "base",
weight: "normal",
},
},
{
responsiveVariants: ["sm", "md", "lg", "xl", "2xl"],
},
);
export const list = tv(
{
extend: text,
base: "list-outside pl-4",
variants: {
type: {
disc: "list-disc",
decimal: "list-decimal",
none: "list-none",
},
inside: {
true: "list-outside pl-0",
},
},
defaultVariants: {
size: "base",
type: "disc",
weight: "normal",
inside: false,
},
},
{
responsiveVariants: ["sm", "md", "lg", "xl", "2xl"],
},
);
export const link = tv(
{
extend: base,
base: "transition",
variants: {
size: {
xs: "text-xs",
sm: "text-sm",
base: "text-base",
lg: "text-lg",
xl: "text-xl",
},
intent: {
primary:
"text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-500",
secondary:
"text-secondary-600 hover:text-secondary-700 dark:text-secondary-400 dark:hover:text-secondary-500",
accent: "text-accent-600 hover:text-accent-700 dark:text-accent-400 dark:hover:text-accent-500",
info: "text-info-600 hover:text-info-700 dark:text-info-400 dark:hover:text-info-500",
danger: "text-danger-600 hover:text-danger-700 dark:text-danger-400 dark:hover:text-danger-500",
success:
"text-success-600 hover:text-success-700 dark:text-success-400 dark:hover:text-success-500",
warning:
"text-warning-700 hover:text-warning-600 dark:text-warning-400 dark:hover:text-warning-500",
gray: "text-gray-700",
neutral:
"text-gray-950 hover:text-gray-800 dark:text-white dark:hover:text-gray-200",
},
variant: {
plain: "",
underlined: "underline",
ghost: "hover:underline",
animated:
"relative before:absolute before:inset-x-0 before:bottom-0 before:h-px before:scale-x-0 before:origin-right hover:before:origin-left hover:before:scale-x-100 before:transition before:duration-200",
},
visited: {
true: "visited:text-accent-600 dark:visited:text-accent-400",
},
},
compoundVariants: [
{
variant: ["plain", "ghost", "underlined"],
intent: "gray",
class: "hover:text-gray-950 dark:hover:text-white",
},
{
variant: "animated",
intent: "primary",
class: "before:bg-primary-600/50 dark:before:bg-primary-400/50",
},
{
variant: "animated",
intent: "info",
class: "before:bg-info-600/50 dark:before:bg-info-400/50",
},
{
variant: "animated",
intent: "neutral",
class: "before:bg-gray-950/50 dark:before:bg-white/50",
},
{
variant: "animated",
intent: "gray",
class: "before:bg-gray-600/50 dark:before:bg-gray-400/50",
},
{
variant: "animated",
intent: "secondary",
class: "before:bg-secondary-600/50 dark:before:bg-secondary-400/50",
},
{
variant: "animated",
intent: "accent",
class: "before:bg-accent-600/50 dark:before:bg-accent-400/50",
},
{
variant: "animated",
intent: "danger",
class: "before:bg-danger-600/50 dark:before:bg-danger-400/50",
},
{
variant: "animated",
intent: "success",
class: "before:bg-success-600/50 dark:before:bg-success-400/50",
},
{
variant: "animated",
intent: "warning",
class: "before:bg-warning-600/50 dark:before:bg-warning-400/50",
},
{
variant: "underlined",
intent: "primary",
class: "decoration-primary-600/50 dark:decoration-primary-400/50",
},
{
variant: "underlined",
intent: "info",
class: "decoration-info-600/50 dark:decoration-info-400/50",
},
{
variant: "underlined",
intent: "gray",
class: "decoration-gray-600/50 dark:decoration-gray-400/50",
},
{
variant: "underlined",
intent: "neutral",
class: "decoration-gray-950/25 dark:decoration-white/25",
},
{
variant: "underlined",
intent: "secondary",
class: "decoration-secondary-600/50 dark:decoration-secondary-400/50",
},
{
variant: "underlined",
intent: "accent",
class: "decoration-accent-600/50 dark:decoration-accent-400/50",
},
{
variant: "underlined",
intent: "danger",
class: "decoration-danger-600/50 dark:decoration-danger-400/50",
},
{
variant: "underlined",
intent: "success",
class: "decoration-success-600/50 dark:decoration-success-400/50",
},
{
variant: "underlined",
intent: "warning",
class: "decoration-warning-600/50 dark:decoration-warning-400/50",
},
],
defaultVariants: {
intent: "primary",
variant: "ghost",
size: "base",
},
},
{
responsiveVariants: ["sm", "md", "lg", "xl", "2xl"],
},
);
export const display = tv(
{
extend: base,
base: "block text-gray-950 dark:text-gray-50",
variants: {
size: {
"4xl": "text-4xl",
"5xl": "text-5xl",
"6xl": "text-6xl",
"7xl": "text-7xl",
"8xl": "text-8xl",
"9xl": "text-9xl",
},
},
defaultVariants: {
size: "6xl",
weight: "bold",
},
},
{
responsiveVariants: ["sm", "md", "lg", "xl", "2xl"],
},
);
export const title = tv(
{
extend: base,
base: "block text-gray-950",
variants: {
size: {
base: "text-base",
lg: "text-lg",
xl: "text-xl",
"2xl": "text-2xl",
"3xl": "text-3xl",
},
},
defaultVariants: {
size: "xl",
weight: "semibold",
},
},
{
responsiveVariants: ["sm", "md", "lg", "xl", "2xl"],
},
);
export const codeTheme = tv({
base: "text-sm inline-block border rounded-md py-px px-1",
variants: {
intent: {
primary:
"bg-primary-50 text-primary-600 dark:text-primary-300 border-primary-200 dark:border-primary-500/20 dark:bg-primary-500/5",
secondary:
"bg-secondary-50 text-secondary-600 dark:text-secondary-300 border-secondary-200 dark:border-secondary-500/20 dark:bg-secondary-500/5",
accent: "bg-accent-50 text-accent-600 dark:text-accent-300 border-accent-200 dark:border-accent-500/20 dark:bg-accent-500/5",
gray: "bg-gray-50 text-gray-700 dark:border-gray-500/20 dark:bg-gray-500/5 dark:border-gray-500/20",
neutral: "bg-gray-50 text-gray-950 dark:text-white dark:bg-gray-500/5 dark:border-gray-500/20",
},
},
defaultVariants: {
intent: "gray",
},
});
export const kbdTheme = tv({
base: "inline-flex items-center justify-center text-gray-800 dark:text-white h-5 text-[11px] min-w-5 px-1.5 rounded font-sans bg-gray-100 dark:bg-white/10 ring-1 border-b border-t border-t-white border-b-gray-200 dark:border-t-transparent dark:border-b-gray-950 ring-gray-300 dark:ring-white/15",
});
export type CodeThemeProps = {
intent?: keyof typeof codeTheme.variants.intent;
};
export type Weight = keyof typeof base.variants.weight;
export type Align = keyof typeof base.variants.align;
type BaseTextProps = {
weight?: Weight;
align?: Align;
};
export type CaptionProps = BaseTextProps & {
size?: keyof typeof caption.variants.size;
neutral?: boolean;
};
export type TextProps = BaseTextProps & {
size?: keyof typeof text.variants.size;
neutral?: boolean;
};
export type ListProps = BaseTextProps & {
size?: keyof typeof text.variants.size;
type?: keyof typeof list.variants.type;
inside?: boolean;
neutral?: boolean;
};
export type LinkProps = BaseTextProps & {
size?: keyof typeof text.variants.size | keyof typeof link.variants.size;
variant?: keyof typeof link.variants.variant;
intent?: keyof typeof link.variants.intent;
visited?: boolean;
};
export type TitleProps = BaseTextProps & {
size?: keyof typeof title.variants.size;
};
export type TitleSizeProp =
| TitleProps["size"]
| {
initial?: TitleProps["size"];
sm?: TitleProps["size"];
md?: TitleProps["size"];
lg?: TitleProps["size"];
xl?: TitleProps["size"];
xxl?: TitleProps["size"];
};
export type TextSizeProp =
| TextProps["size"]
| {
initial?: TextProps["size"];
sm?: TextProps["size"];
md?: TextProps["size"];
lg?: TextProps["size"];
xl?: TextProps["size"];
xxl?: TextProps["size"];
};
export type DisplayProps = BaseTextProps & {
size?: keyof typeof display.variants.size;
};
export type TextWeightProp =
| Weight
| {
initial?: Weight;
sm?: Weight;
md?: Weight;
lg?: Weight;
xl?: Weight;
xxl?: Weight;
};
export type TextAlignProp =
| Align
| {
initial?: Align;
sm?: Align;
md?: Align;
lg?: Align;
xl?: Align;
xxl?: Align;
};

View File

@@ -1,6 +0,0 @@
import { twMerge } from "tailwind-merge"
import { type ClassValue, clsx } from "clsx"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -1,18 +0,0 @@
import { component$, Slot } from "@builder.io/qwik";
//font-sans or font-body
import "@fontsource/geist-sans/400.css"
import "@fontsource/geist-sans/500.css"
import "@fontsource/geist-sans/600.css"
import "@fontsource/geist-sans/700.css"
// font mono
import "@fontsource/geist-mono/400.css"
import "@fontsource/geist-mono/700.css"
//font-mona
import "@fontsource-variable/mona-sans"
//font-bricolage
import '@fontsource-variable/bricolage-grotesque';
export const Fonts = component$(() => {
return <Slot />;
});

View File

@@ -1,83 +0,0 @@
import Book from "./book";
import { CONSTANTS } from "./constants";
import { Link } from "@builder.io/qwik-city";
import { MotionComponent, transition } from "@nestri/ui/react";
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
export const FooterBanner = component$(() => {
const docsLinkRef = useSignal<HTMLElement | undefined>()
const bookRef = useSignal<HTMLElement | undefined>()
// eslint-disable-next-line qwik/no-use-visible-task
useVisibleTask$(() => {
docsLinkRef.value?.addEventListener("mouseenter", () => {
bookRef.value?.classList.add('flip')
})
docsLinkRef.value?.addEventListener("mouseleave", () => {
bookRef.value?.classList.remove('flip')
})
return () => {
docsLinkRef.value?.removeEventListener("mouseenter", () => {
bookRef.value?.classList.add('flip')
})
docsLinkRef.value?.removeEventListener("mouseleave", () => {
bookRef.value?.classList.remove('flip')
})
}
})
return (
<MotionComponent
initial={{ opacity: 0, y: 100 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={transition}
client:load
class="flex items-center justify-center w-screen px-4 py-10"
as="div"
>
<section class="w-full flex flex-col items-center justify-center">
<div class="w-full max-w-2xl mx-auto">
<div class="z-[2] h-max md:flex-row flex-col relative overflow-hidden flex justify-between md:items-center gap-6 px-6 pt-6 bg-white dark:bg-black ring-2 ring-gray-300 dark:ring-gray-700 rounded-xl">
<div class="w-full h-max md:pb-6">
<div class="gap-3 w-full flex flex-col">
<div class="flex w-full flex-col text-neutral-900/70 dark:text-neutral-100/70 gap-1" >
<p class="text-lg font-medium text-balance tracking-tight leading-tight">
<b class="text-black dark:text-white font-semibold text-2xl text-balance tracking-[-.96px] leading-tight font-title">Ready to start playing?</b>
<br />
Dive into the documentation or unlock premium features with <u class="font-bold [text-decoration:none]" >Nestri Pro</u>
</p>
</div>
<div class="flex md:flex-row flex-col w-full gap-2 h-max md:items-center">
<Link href="/pricing" class="h-max w-max relative overflow-hidden rounded-lg flex justify-center text-gray-500 dark:text-gray-100/70 font-title font-bold items-center group py-2 px-4">
<span class="invisible"> Get Nestri Pro</span>
<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]" />
<div class="select-none absolute justify-center items-center min-w-max inset-auto flex z-[2] rounded-md h-[83%] w-[96%] bg-white dark:bg-black group-hover:bg-transparent transition-all duration-200">
<span class="text-sm group-hover:text-white w-full transition-all duration-200">
<div class="flex justify-around items-center w-full h-max">
Get Nestri Pro
</div>
</span>
</div>
</Link>
<a href={CONSTANTS.githubLink} ref={v => docsLinkRef.value = v} class="w-max focus:ring-primary-500 hover:ring-primary-500 ring-gray-500 rounded-lg outline-none dark:text-gray-100/70 ring-2 text-sm h-max py-2 px-4 flex items-center transition-all duration-200 focus:bg-primary-100 focus:dark:bg-primary-900 focus:text-primary-500 text-gray-500 font-title font-bold justify-between">
<div class="size-5 relative mr-2">
<svg xmlns="http://www.w3.org/2000/svg" class="w-full h-full" height={20} width={20} viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M14.25 4.48v3.057c0 .111 0 .27.021.406a.94.94 0 0 0 .444.683a.96.96 0 0 0 .783.072c.13-.04.272-.108.378-.159L17 8.005l1.124.534c.106.05.248.119.378.16a.96.96 0 0 0 .783-.073a.94.94 0 0 0 .444-.683c.022-.136.021-.295.021-.406V3.031q.17-.008.332-.013C21.154 2.98 22 3.86 22 4.933v11.21c0 1.112-.906 2.01-2.015 2.08c-.97.06-2.108.179-2.985.41c-1.082.286-2.373.904-3.372 1.436q-.422.224-.878.323V5.174a3.6 3.6 0 0 0 .924-.371q.277-.162.576-.323m5.478 8.338a.75.75 0 0 1-.546.91l-4 1a.75.75 0 1 1-.364-1.456l4-1a.75.75 0 0 1 .91.546M11.25 5.214a3.4 3.4 0 0 1-.968-.339C9.296 4.354 8.05 3.765 7 3.487c-.887-.233-2.041-.352-3.018-.412C2.886 3.008 2 3.9 2 4.998v11.146c0 1.11.906 2.01 2.015 2.079c.97.06 2.108.179 2.985.41c1.081.286 2.373.904 3.372 1.436q.422.224.878.324zM4.273 8.818a.75.75 0 0 1 .91-.546l4 1a.75.75 0 1 1-.365 1.456l-4-1a.75.75 0 0 1-.545-.91m.91 3.454a.75.75 0 1 0-.365 1.456l4 1a.75.75 0 0 0 .364-1.456z" clip-rule="evenodd" /><path fill="currentColor" d="M18.25 3.151c-.62.073-1.23.18-1.75.336a8 8 0 0 0-.75.27v3.182l.75-.356l.008-.005a1.1 1.1 0 0 1 .492-.13q.072 0 .138.01c.175.029.315.1.354.12l.009.005l.75.356V3.15" /></svg>
</div>
Read the Docs
</a>
</div>
</div>
</div>
<a href={CONSTANTS.githubLink} ref={v => bookRef.value = v} class="h-full max-h-[160px] pt-4 md:w-[65%] w-full flex items-start justify-center overflow-hidden outline-none">
<Book
textColor="#FFF"
bgColor="#FF4F01"
title="Getting started with Nestri" class="shadow-lg shadow-gray-900 dark:shadow-gray-300" />
</a>
<div class="animate-multicolor absolute blur-[2px] -right-full left-0 -bottom-[2px] h-4 [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]" />
</div>
</div>
</section>
</MotionComponent>
);
});

View File

@@ -1,64 +0,0 @@
import { component$ } from "@builder.io/qwik"
import { cn } from "@nestri/ui/design"
import { ImageLoader } from "@nestri/ui/image"
type Game = {
appid: string
name: string
release_date: number
compatibility: string
teams: number
}
type Props = {
game: Game;
class?: string;
}
export const GameCard = component$(({ game, class: className }: Props) => {
return (
<div key={`game-${game.appid}`} class={cn("bg-gray-200/70 min-w-[250px] backdrop-blur-sm ring-gray-300 select-none max-w-[270px] group dark:ring-gray-700 ring dark:bg-gray-800/70 group rounded-3xl dark:text-primary-50/70 text-primary-950/70 duration-300 transition-colors flex flex-col", className)}>
<header class="flex gap-4 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>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" class={cn("absolute right-0 bottom-0 top-0", game.compatibility == "perfect" ? "text-green-600 dark:text-green-400" : "text-amber-600 dark:text-amber-300")} viewBox="0 0 24 24">
<path fill="currentColor" d="M9.592 3.2a5.727 5.727 0 0 1-.495.399c-.298.2-.633.338-.985.408c-.153.03-.313.043-.632.068c-.801.064-1.202.096-1.536.214a2.713 2.713 0 0 0-1.655 1.655c-.118.334-.15.735-.214 1.536a5.707 5.707 0 0 1-.068.632c-.07.352-.208.687-.408.985c-.087.13-.191.252-.399.495c-.521.612-.782.918-.935 1.238c-.353.74-.353 1.6 0 2.34c.153.32.414.626.935 1.238c.208.243.312.365.399.495c.2.298.338.633.408.985c.03.153.043.313.068.632c.064.801.096 1.202.214 1.536a2.713 2.713 0 0 0 1.655 1.655c.334.118.735.15 1.536.214c.319.025.479.038.632.068c.352.07.687.209.985.408c.13.087.252.191.495.399c.612.521.918.782 1.238.935c.74.353 1.6.353 2.34 0c.32-.153.626-.414 1.238-.935c.243-.208.365-.312.495-.399c.298-.2.633-.338.985-.408c.153-.03.313-.043.632-.068c.801-.064 1.202-.096 1.536-.214a2.713 2.713 0 0 0 1.655-1.655c.118-.334.15-.735.214-1.536c.025-.319.038-.479.068-.632c.07-.352.209-.687.408-.985c.087-.13.191-.252.399-.495c.521-.612.782-.918.935-1.238c.353-.74.353-1.6 0-2.34c-.153-.32-.414-.626-.935-1.238a5.574 5.574 0 0 1-.399-.495a2.713 2.713 0 0 1-.408-.985a5.72 5.72 0 0 1-.068-.632c-.064-.801-.096-1.202-.214-1.536a2.713 2.713 0 0 0-1.655-1.655c-.334-.118-.735-.15-1.536-.214a5.707 5.707 0 0 1-.632-.068a2.713 2.713 0 0 1-.985-.408a5.73 5.73 0 0 1-.495-.399c-.612-.521-.918-.782-1.238-.935a2.713 2.713 0 0 0-2.34 0c-.32.153-.626.414-1.238.935" opacity=".5" />
<path fill="currentColor" d="M16.374 9.863a.814.814 0 0 0-1.151-1.151l-4.85 4.85l-1.595-1.595a.814.814 0 0 0-1.151 1.151l2.17 2.17a.814.814 0 0 0 1.15 0z" />
</svg>
</div>
<time>{new Date(game.release_date).getUTCFullYear()}</time>
</header>
<div class="flex-1 flex items-center justify-center">
<div class="max-h-64 h-full p-4 2xl:max-h-80 relative">
<ImageLoader height={224} width={150} src={game.appid} alt={game.name} class="block h-full mx-auto rounded-2xl shadow-2xl shadow-primary-900 dark:shadow-primary-800 relative" />
</div>
</div>
<footer class="flex justify-between p-4">
<span class="text-left max-w-[70%]" >
{"Downloaded in "}&nbsp;
{`${game.teams}`}&nbsp;
{"Nestri Teams"}
</span>
<div>
<div class="flex relative p-3 text-primary-500">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12.832 21.801c3.126-.626 7.168-2.875 7.168-8.69c0-5.291-3.873-8.815-6.658-10.434c-.619-.36-1.342.113-1.342.828v1.828c0 1.442-.606 4.074-2.29 5.169c-.86.559-1.79-.278-1.894-1.298l-.086-.838c-.1-.974-1.092-1.565-1.87-.971C4.461 8.46 3 10.33 3 13.11C3 20.221 8.289 22 10.933 22q.232 0 .484-.015c.446-.056 0 .099 1.415-.185" opacity=".5" /><path fill="currentColor" d="M8 18.444c0 2.62 2.111 3.43 3.417 3.542c.446-.056 0 .099 1.415-.185C13.871 21.434 15 20.492 15 18.444c0-1.297-.819-2.098-1.46-2.473c-.196-.115-.424.03-.441.256c-.056.718-.746 1.29-1.215.744c-.415-.482-.59-1.187-.59-1.638v-.59c0-.354-.357-.59-.663-.408C9.495 15.008 8 16.395 8 18.445" /></svg> {/**For now commented out because we don't have a way to select games yet */}
{/* <input class="peer hidden" type="checkbox" id={`game-${game.appid}`} value={game.appid} checked={false} />
<label for={`game-${game.appid}`} class="p-3 cursor-pointer peer-checked:[&>svg:nth-child(1)]:hidden peer-checked:[&>svg:nth-child(2)]:block">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="block" >
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5">
<path d="M6.286 19C3.919 19 2 17.104 2 14.765c0-2.34 1.919-4.236 4.286-4.236c.284 0 .562.028.83.08m7.265-2.582a5.765 5.765 0 0 1 1.905-.321c.654 0 1.283.109 1.87.309m-11.04 2.594a5.577 5.577 0 0 1-.354-1.962C6.762 5.528 9.32 3 12.476 3c2.94 0 5.361 2.194 5.68 5.015m-11.04 2.594a4.29 4.29 0 0 1 1.55.634m9.49-3.228C20.392 8.78 22 10.881 22 13.353c0 2.707-1.927 4.97-4.5 5.52" opacity=".5" />
<path stroke-linejoin="round" d="M12 22v-6m0 6l2-2m-2 2l-2-2" />
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="hidden" >
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5">
<path d="M6.286 19C3.919 19 2 17.104 2 14.765c0-2.34 1.919-4.236 4.286-4.236c.284 0 .562.028.83.08m7.265-2.582a5.765 5.765 0 0 1 1.905-.321c.654 0 1.283.109 1.87.309m-11.04 2.594a5.577 5.577 0 0 1-.354-1.962C6.762 5.528 9.32 3 12.476 3c2.94 0 5.361 2.194 5.68 5.015m-11.04 2.594a4.29 4.29 0 0 1 1.55.634m9.49-3.228C20.392 8.78 22 10.881 22 13.353c0 2.707-1.927 4.97-4.5 5.52" opacity=".5" />
<path stroke-linejoin="round" d="m10 19.8l1.143 1.2L14 18" />
</g>
</svg>
</label> */}
</div>
</div>
</footer>
</div>
)
})

View File

@@ -1,89 +0,0 @@
import { Modal } from "@qwik-ui/headless";
import { MotionComponent } from "../react";
import { component$, type QRL } from "@builder.io/qwik";
// const FadeScale = {
// initial: {
// opacity: 0,
// scale: 0.85,
// },
// animate: {
// opacity: 1,
// scale: 1,
// },
// exit: {
// opacity: 0,
// scale: 0.85,
// },
// transition: {
// type: "spring",
// duration: 0.35,
// bounce: 0.1
// }
// };
type StoreSelectProps = {
onSteamPress$: QRL<() => void>
}
export const StoreSelect = component$(({ onSteamPress$ }: StoreSelectProps) => {
return (
// <MotionComponent class="w-full h-full" key="store-select" {...FadeScale} >
<>
<div class="absolute right-6 top-6 z-10">
<Modal.Close class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full hover:bg-gray-300 transition-all duration-300 ease-out dark:hover:bg-gray-800 focus:scale-90 active:scale-75">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" class="h-5 w-5 stroke-gray-600 will-change-transform"><path d="M18 6L6 18M6 6L18 18" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
</Modal.Close>
</div>
<div >
<div class="flex flex-col space-y-1.5 text-center sm:text-left">
<h2 class="tracking-tight flex items-center justify-between px-6 py-6 text-center font-title text-xl font-semibold">
<div class="flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full hover:bg-gray-300 transition-all duration-300 ease-out dark:hover:bg-gray-800 focus:scale-90 active:scale-75">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 48 48" class="size-5 text-gray-600"><g fill="none"><path stroke="currentColor" stroke-linejoin="round" stroke-width="4" d="M24 44a19.94 19.94 0 0 0 14.142-5.858A19.94 19.94 0 0 0 44 24a19.94 19.94 0 0 0-5.858-14.142A19.94 19.94 0 0 0 24 4A19.94 19.94 0 0 0 9.858 9.858A19.94 19.94 0 0 0 4 24a19.94 19.94 0 0 0 5.858 14.142A19.94 19.94 0 0 0 24 44Z" /><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M24 28.625v-4a6 6 0 1 0-6-6" /><path fill="currentColor" fill-rule="evenodd" d="M24 37.625a2.5 2.5 0 1 0 0-5a2.5 2.5 0 0 0 0 5" clip-rule="evenodd" /></g></svg>
</div>
<span class="w-full pr-8 text-center font-title font-semibold">Connect Game Store</span>
</h2>
</div>
<div class="flex flex-col gap-3 px-6 pb-4">
<button onClick$={onSteamPress$} class="flex h-[64px] w-full cursor-pointer flex-row items-center justify-between gap-2 rounded-2xl bg-gray-100/70 px-5 transition-all duration-200 ease-out hover:bg-gray-300 focus:bg-gray-300 dark:bg-[#171717] dark:hover:bg-gray-800">
<span class="select-none text-center font-title text-lg font-semibold sm:font-medium">Steam</span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 16 16" class="size-8 dark:text-white"><g fill="currentColor"><path d="M.329 10.333A8.01 8.01 0 0 0 7.99 16C12.414 16 16 12.418 16 8s-3.586-8-8.009-8A8.006 8.006 0 0 0 0 7.468l.003.006l4.304 1.769A2.2 2.2 0 0 1 5.62 8.88l1.96-2.844l-.001-.04a3.046 3.046 0 0 1 3.042-3.043a3.046 3.046 0 0 1 3.042 3.043a3.047 3.047 0 0 1-3.111 3.044l-2.804 2a2.223 2.223 0 0 1-3.075 2.11a2.22 2.22 0 0 1-1.312-1.568L.33 10.333Z" /><path d="M4.868 12.683a1.715 1.715 0 0 0 1.318-3.165a1.7 1.7 0 0 0-1.263-.02l1.023.424a1.261 1.261 0 1 1-.97 2.33l-.99-.41a1.7 1.7 0 0 0 .882.84Zm3.726-6.687a2.03 2.03 0 0 0 2.027 2.029a2.03 2.03 0 0 0 2.027-2.029a2.03 2.03 0 0 0-2.027-2.027a2.03 2.03 0 0 0-2.027 2.027m2.03-1.527a1.524 1.524 0 1 1-.002 3.048a1.524 1.524 0 0 1 .002-3.048" /></g></svg>
</button>
<div class="flex h-[64px] w-full cursor-pointer flex-row items-center justify-between gap-2 rounded-2xl bg-gray-100/70 px-5 transition-all duration-200 ease-out hover:bg-gray-300 focus:bg-gray-300 dark:bg-[#171717] dark:hover:bg-gray-800">
<span class="select-none text-center font-title text-lg font-semibold sm:font-medium">Epic Games</span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" class="size-8 text-black dark:text-white" viewBox="0 0 24 24" ><path fill="currentColor" d="M3.537 0C2.165 0 1.66.506 1.66 1.879V18.44a4 4 0 0 0 .02.433c.031.3.037.59.316.92c.027.033.311.245.311.245c.153.075.258.13.43.2l8.335 3.491c.433.199.614.276.928.27h.002c.314.006.495-.071.928-.27l8.335-3.492c.172-.07.277-.124.43-.2c0 0 .284-.211.311-.243c.28-.33.285-.621.316-.92a4 4 0 0 0 .02-.434V1.879c0-1.373-.506-1.88-1.878-1.88zm13.366 3.11h.68c1.138 0 1.688.553 1.688 1.696v1.88h-1.374v-1.8c0-.369-.17-.54-.523-.54h-.235c-.367 0-.537.17-.537.539v5.81c0 .369.17.54.537.54h.262c.353 0 .523-.171.523-.54V8.619h1.373v2.143c0 1.144-.562 1.71-1.7 1.71h-.694c-1.138 0-1.7-.566-1.7-1.71V4.82c0-1.144.562-1.709 1.7-1.709zm-12.186.08h3.114v1.274H6.117v2.603h1.648v1.275H6.117v2.774h1.74v1.275h-3.14zm3.816 0h2.198c1.138 0 1.7.564 1.7 1.708v2.445c0 1.144-.562 1.71-1.7 1.71h-.799v3.338h-1.4zm4.53 0h1.4v9.201h-1.4zm-3.13 1.235v3.392h.575c.354 0 .523-.171.523-.54V4.965c0-.368-.17-.54-.523-.54zm-3.74 10.147a1.7 1.7 0 0 1 .591.108a1.8 1.8 0 0 1 .49.299l-.452.546a1.3 1.3 0 0 0-.308-.195a.9.9 0 0 0-.363-.068a.7.7 0 0 0-.28.06a.7.7 0 0 0-.224.163a.8.8 0 0 0-.151.243a.8.8 0 0 0-.056.299v.008a.9.9 0 0 0 .056.31a.7.7 0 0 0 .157.245a.7.7 0 0 0 .238.16a.8.8 0 0 0 .303.058a.8.8 0 0 0 .445-.116v-.339h-.548v-.565H7.37v1.255a2 2 0 0 1-.524.307a1.8 1.8 0 0 1-.683.123a1.6 1.6 0 0 1-.602-.107a1.5 1.5 0 0 1-.478-.3a1.4 1.4 0 0 1-.318-.455a1.4 1.4 0 0 1-.115-.58v-.008a1.4 1.4 0 0 1 .113-.57a1.5 1.5 0 0 1 .312-.46a1.4 1.4 0 0 1 .474-.309a1.6 1.6 0 0 1 .598-.111h.045zm11.963.008a2 2 0 0 1 .612.094a1.6 1.6 0 0 1 .507.277l-.386.546a1.6 1.6 0 0 0-.39-.205a1.2 1.2 0 0 0-.388-.07a.35.35 0 0 0-.208.052a.15.15 0 0 0-.07.127v.008a.16.16 0 0 0 .022.084a.2.2 0 0 0 .076.066a1 1 0 0 0 .147.06q.093.03.236.061a3 3 0 0 1 .43.122a1.3 1.3 0 0 1 .328.17a.7.7 0 0 1 .207.24a.74.74 0 0 1 .071.337v.008a.9.9 0 0 1-.081.382a.8.8 0 0 1-.229.285a1 1 0 0 1-.353.18a1.6 1.6 0 0 1-.46.061a2.2 2.2 0 0 1-.71-.116a1.7 1.7 0 0 1-.593-.346l.43-.514q.416.335.9.335a.46.46 0 0 0 .236-.05a.16.16 0 0 0 .082-.142v-.008a.15.15 0 0 0-.02-.077a.2.2 0 0 0-.073-.066a1 1 0 0 0-.143-.062a3 3 0 0 0-.233-.062a5 5 0 0 1-.413-.113a1.3 1.3 0 0 1-.331-.16a.7.7 0 0 1-.222-.243a.73.73 0 0 1-.082-.36v-.008a.9.9 0 0 1 .074-.359a.8.8 0 0 1 .214-.283a1 1 0 0 1 .34-.185a1.4 1.4 0 0 1 .448-.066zm-9.358.025h.742l1.183 2.81h-.825l-.203-.499H8.623l-.198.498h-.81zm2.197.02h.814l.663 1.08l.663-1.08h.814v2.79h-.766v-1.602l-.711 1.091h-.016l-.707-1.083v1.593h-.754zm3.469 0h2.235v.658h-1.473v.422h1.334v.61h-1.334v.442h1.493v.658h-2.255zm-5.3.897l-.315.793h.624zm-1.145 5.19h8.014l-4.09 1.348z" /></svg>
</div>
<div class="flex h-[64px] w-full cursor-pointer flex-row items-center justify-between gap-2 rounded-2xl bg-gray-100/70 px-5 transition-all duration-200 ease-out hover:bg-gray-300 focus:bg-gray-300 dark:bg-[#171717] dark:hover:bg-gray-800">
<span class="select-none text-center font-title text-lg font-semibold sm:font-medium">GOG.com</span>
<svg preserveAspectRatio="xMidYMax meet" viewBox="0 0 34 31" width="24" height="24" class="size-8 text-black dark:text-white" >
<path fill="currentColor" d="M31,31H3a3,3,0,0,1-3-3V3A3,3,0,0,1,3,0H31a3,3,0,0,1,3,3V28A3,3,0,0,1,31,31ZM4,24.5A1.5,1.5,0,0,0,5.5,26H11V24H6.5a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5H11V18H5.5A1.5,1.5,0,0,0,4,19.5Zm8-18A1.5,1.5,0,0,0,10.5,5h-5A1.5,1.5,0,0,0,4,6.5v5A1.5,1.5,0,0,0,5.5,13H9V11H6.5a.5.5,0,0,1-.5-.5v-3A.5.5,0,0,1,6.5,7h3a.5.5,0,0,1,.5.5v6a.5.5,0,0,1-.5.5H4v2h6.5A1.5,1.5,0,0,0,12,14.5Zm0,13v5A1.5,1.5,0,0,0,13.5,26h5A1.5,1.5,0,0,0,20,24.5v-5A1.5,1.5,0,0,0,18.5,18h-5A1.5,1.5,0,0,0,12,19.5Zm9-13A1.5,1.5,0,0,0,19.5,5h-5A1.5,1.5,0,0,0,13,6.5v5A1.5,1.5,0,0,0,14.5,13h5A1.5,1.5,0,0,0,21,11.5Zm9,0A1.5,1.5,0,0,0,28.5,5h-5A1.5,1.5,0,0,0,22,6.5v5A1.5,1.5,0,0,0,23.5,13H27V11H24.5a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5h3a.5.5,0,0,1,.5.5v6a.5.5,0,0,1-.5.5H22v2h6.5A1.5,1.5,0,0,0,30,14.5ZM30,18H22.5A1.5,1.5,0,0,0,21,19.5V26h2V20.5a.5.5,0,0,1,.5-.5h1v6h2V20H28v6h2ZM18.5,11h-3a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5h3a.5.5,0,0,1,.5.5v3A.5.5,0,0,1,18.5,11Zm-4,9h3a.5.5,0,0,1,.5.5v3a.5.5,0,0,1-.5.5h-3a.5.5,0,0,1-.5-.5v-3A.5.5,0,0,1,14.5,20Z" />
</svg>
</div>
<div class="flex h-[64px] w-full cursor-pointer flex-row items-center justify-between gap-2 rounded-2xl bg-gray-100/70 px-5 transition-all duration-200 ease-out hover:bg-gray-300 focus:bg-gray-300 dark:bg-[#171717] dark:hover:bg-gray-800">
<span class="select-none text-center font-title text-lg font-semibold sm:font-medium">Amazon Games</span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" class="size-8 text-black dark:text-white" viewBox="0 0 291.29 134.46" fill="currentColor">
<path fill-rule="evenodd" d="M50.38,59.78c1.09-3.68,1-8.31,1-13.08V12.56c0-1.64.4-6.32-.25-7.29s-3.15-.75-4.9-.75c-5,0-7.22-.69-7.67,4.08l-.19.13c-3.92-3-7.65-5.85-14.84-5.72l-2.39.19a22.76,22.76,0,0,0-4.08,1A19.69,19.69,0,0,0,6.31,15a36.62,36.62,0,0,0-2.08,7.36,23.87,23.87,0,0,0-.38,7.54c.38,2.36.15,4.42.63,6.48,1.74,7.39,5.21,13.15,11.57,15.9a20.21,20.21,0,0,0,11.13,1.39A21,21,0,0,0,34.35,51l2.7-2h.06A22.54,22.54,0,0,1,37,55.75c-1.12,6.39-3,8.54-9.37,9.68a18,18,0,0,1-5.41.13l-5.28-.69L9.2,63a1.69,1.69,0,0,0-1.26,1.07,40.2,40.2,0,0,0,.25,7.8c.89,1.48,3.75,2.07,5.54,2.64,6,1.91,15.69,2.83,22.19.57C43.36,72.52,48.07,67.51,50.38,59.78ZM37.17,22.87V40.41a15.23,15.23,0,0,1-4.33,2.14c-10.59,3.32-14.59-4.12-14.59-13.89a25.33,25.33,0,0,1,1.13-8.87c.93-2.4,2.37-4.5,4.72-5.47.84-.34,1.85-.26,2.76-.63a21.18,21.18,0,0,1,7.8,1.2L37,16C37.57,17,37.17,21.31,37.17,22.87ZM79.74,56.32a25.65,25.65,0,0,0,8.36-3.21l3.33-2.45c.86,1.11.52,2.8,1.63,3.65s9.68,1.16,10.5,0,.44-3.67.44-5.41V26.46c0-4.37.33-9.26-.69-12.7C100.92,5.67,94.08,2.89,83.51,3l-5.66.37a62,62,0,0,0-9.56,2.08c-1.36.47-3.44.82-4,2.07s-.45,7.84.31,8.36c1.12.77,6.5-1,8-1.32,4.34-.94,14.24-1.9,16.66,1.2C91,18,90.71,22.37,90.67,26.39c-1,.24-2.72-.42-3.77-.63l-4.78-.5a18,18,0,0,0-5.28.19c-8.2,1.41-14,4.53-15.9,12.13C58,49.27,68.13,58.77,79.74,56.32ZM77.35,34.63c1.19-.7,2.67-.51,4.15-1.07,3.35,0,6.18.51,9,.63.51,1.12.14,6.83.12,8.55-2.39,3.17-12,6.33-15.27,1.82C73,41.23,74.57,36.26,77.35,34.63Zm38.53,16c0,1.75-.21,3.48.88,4.15.62.37,2.09.19,3,.19,2.09,0,9.28.44,10.06-.57,1-1.25.44-7.82.44-10.12V16.84a19.35,19.35,0,0,1,6.1-2.27c3.38-.79,7.86-.8,9.55,1.45,1.49,2,1.26,5.56,1.26,9.05v19c0,2.58-.58,9.79.88,10.69.9.54,5,.19,6.41.19s5.54.34,6.42-.32c1.18-.89.69-7.28.69-9.56q0-14.13.06-28.29c.48-.79,2.45-1.11,3.4-1.44,4.14-1.46,10.62-2.42,12.63,1.63,1,2.1.69,5.92.69,9V44.81c0,2.24-.5,8.33.44,9.56.55.71,1.83.57,3.08.57,1.88,0,9.33.33,10.19-.32,1.24-.94.75-4.74.75-6.85V28.22c0-8.24.64-15.75-3-20.44-6.52-8.5-23.71-3.95-30,1.45h-.25C157.15,5.18,153,2.9,146.44,3l-2.64.19a30.21,30.21,0,0,0-5.28,1.19,40.58,40.58,0,0,0-6.35,3l-3.08,1.89c-1.12-1.35-.44-3.54-2-4.46-.61-.37-8.67-.47-9.8-.19a2,2,0,0,0-1.07.69c-.66,1-.32,7.59-.32,9.49Zm96.32,2.13c6.17,3.87,17.31,4.71,26.09,2.52,2.21-.55,6.52-1.33,7.29-3.14a48.27,48.27,0,0,0,.12-7.55,1.83,1.83,0,0,0-.81-.94c-.79-.34-2,.24-2.77.44l-6.48,1.19a23.66,23.66,0,0,1-7.16.26,39.37,39.37,0,0,1-5-.7c-4.92-1.49-8.19-5.16-8.24-11.44,1.17-.53,5-.12,6.6-.12h16c2.3,0,6,.47,7.41-.57,1.89-1.41,1.75-10.85,1.14-13.89-2.07-10.3-8.28-16-20.75-15.78l-1.51.06-4.53.63c-4.86,1.22-9.05,3.46-11.75,6.85a25.69,25.69,0,0,0-3.71,6C201.68,22.42,201,33,203.08,40,204.76,45.59,207.71,49.93,212.2,52.73Zm3.7-32.56c1.13-3.25,3-5.62,6.29-6.66L225,13c7.46-.07,9.52,3.79,9.43,11.26-1,.46-4.25.12-5.66.12H215.21C214.8,23.33,215.58,21.1,215.9,20.17Zm77.65,13.2c-3-5.2-9.52-7.23-15.34-9.62-2.76-1.13-7.28-2.08-7.93-5.28-1.37-6.84,12.69-4.86,16.85-3.83,1.16.28,3.85,1.33,4.59.37s.38-3.29.38-4.77c0-1.23.16-2.8-.32-3.59-.72-1.21-2.61-1.55-4.08-2A36.6,36.6,0,0,0,276,3l-3.59.25A29.08,29.08,0,0,0,265.88,5a14.84,14.84,0,0,0-8,7.79c-2.23,5.52-.14,12.84,3.21,15.53,4,3.23,9.43,5.07,14.58,7.17,2.6,1.06,5.55,1.67,6.1,4.78,1.49,8.45-14.51,5.39-19.3,4.15-1-.27-4.16-1.34-5-.88-1.14.65-.69,3.85-.69,5.59,0,1-.15,2.42.25,3.08,1.2,2,7.83,3.26,10.75,3.84,11.6,2.3,21.92-1.62,25.65-8.93C295.3,43.59,295.64,37,293.55,33.37ZM252.81,83l-2.2.13a37.54,37.54,0,0,0-6.35.69,43.91,43.91,0,0,0-13.52,4.72c-1,.61-5,2.58-4.27,4.4.57,1.46,6.36.25,8.23.12,3.7-.25,5.51-.57,9-.56h6.41a35.9,35.9,0,0,1,5.73.37,8.52,8.52,0,0,1,3.45,1.64c1.46,1.25,1.19,5.49.69,7.48a139.33,139.33,0,0,1-5.78,18.86c-.41,1-3.64,7.3-.06,6.54,1.62-.35,4.9-4,5.91-5.22,5-6.39,8.15-13.75,10.5-23,.54-2.15,1.78-10.6.56-12.57C269.11,83.34,258.52,82.89,252.81,83ZM245,101l-5.72,2.51-9.49,3.58c-8.44,3.27-17.84,5.41-27.23,7.74l-11,2.07-12.95,1.7-4.15.31c-1.66.35-3.61.15-5.47.44a83.4,83.4,0,0,1-12.38.51l-9.37.06-6.73-.25-4.33-.25c-1-.2-2.18-.06-3.27-.26l-13.14-1.44c-3.89-.73-8.07-1-11.76-2l-3.08-.51L93.5,112.65c-8.16-2.55-16.27-4.54-23.89-7.48-8.46-3.27-17.29-6.84-24.77-11.26l-7.41-4.27c-1.35-.81-2.44-2-4.59-2-1.6.79-2.09,1.83-1,3.71a12.73,12.73,0,0,0,2.89,2.83l3.4,3.14c4.9,3.9,9.82,7.91,15.15,11.38,4.6,3,9.5,5.55,14.33,8.36l7.23,3.46c4.13,1.82,8.42,3.7,12.76,5.4l11.13,3.71c6,2,12.53,3,19,4.59l13.64,2,4.4.32,7.42.56h2.7a30.39,30.39,0,0,0,7.92.07l2.83-.07,3.46-.06,11.82-.94c5.3-1.18,10.88-1,15.9-2.52l11.57-2.82a195.36,195.36,0,0,0,20.31-7.11,144.13,144.13,0,0,0,23.63-12.57c2.56-1.72,6.18-3,6.86-6.6C250.75,101.43,247.63,100.27,245,101Z" transform="translate(-3.69 -3)" />
</svg>
</div>
</div>
<div class="flex px-6 pb-4" >
<button class="group flex h-[48px] w-full select-none items-center justify-center gap-2 rounded-full text-base font-semibold dark:text-gray-400/70 text-gray-600/70 transition-all duration-200 ease-out hover:text-gray-800 dark:hover:text-gray-200 focus:scale-95 active:scale-95 sm:font-medium">
<svg xmlns="http://www.w3.org/2000/svg" class="size-5 shrink-0 transition-all duration-200 ease-out" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m9 15l-2.968 2.968A2.362 2.362 0 0 1 2 16.298V15l1.357-6.784A4 4 0 0 1 7.279 5h9.442a4 4 0 0 1 3.922 3.216L22 15v1.297a2.362 2.362 0 0 1-4.032 1.67L15 15z" /><path d="m9 5l1 2h4l1-2" /></g></svg>
I don't own any games
</button>
</div>
</div>
</>
// </MotionComponent>
)
})
export const SteamLoad = component$(() => {
return (
<MotionComponent key="steam-load">
<div class="h-[200px] w-[240px] bg-red-500">
</div>
</MotionComponent>
)
})

View File

@@ -1,9 +0,0 @@
// import { SteamLoad, StoreSelect } from "./default";
import { $, component$, useSignal } from "@builder.io/qwik";
export default component$(() => {
// const storeSelect = useSignal(true)
return (
<></>
)
})

View File

@@ -1,135 +0,0 @@
import { cn } from "../design";
import Avatar from "../avatar";
import type Nestri from "@nestri/sdk"
import { Tooltip } from '@qwik-ui/headless';
import { useNavigate } from "@builder.io/qwik-city";
import { $, component$, useOnDocument, useSignal, type QRL } from "@builder.io/qwik";
type Props = {
getActiveUsers$: QRL<() => Promise<Nestri.Users.UserListResponse.Data[] | undefined>>
getSession$: QRL<(profileID: string) => Promise<Nestri.Users.UserSessionResponse | undefined>>
}
const skeletonCrew = new Array(3).fill(0)
export const HomeFriendsSection = component$(({ getActiveUsers$, getSession$ }: Props) => {
const activeUsers = useSignal<Nestri.Users.UserListResponse.Data[] | undefined>()
const nav = useNavigate()
useOnDocument("load", $(async () => {
const sessionUserData = sessionStorage.getItem("active_user_data")
if (!sessionUserData) {
const users = await getActiveUsers$()
sessionStorage.setItem("active_user_data", JSON.stringify(users))
activeUsers.value = users
} else {
activeUsers.value = JSON.parse(sessionUserData)
}
}))
const onWatch = $(async (profileID: string) => {
const session = await getSession$(profileID)
await nav(`/play/${session?.data.id}`)
})
return (
<div class="gap-2 w-full flex-col flex">
<hr class="border-none h-[1.5px] dark:bg-gray-700 bg-gray-300 w-full" />
<div class="flex flex-col justify-center py-2 px-3 items-start w-full ">
<div class="text-gray-600/70 dark:text-gray-400/70 leading-none flex justify-between items-center w-full py-1">
<span class="text-xl text-gray-700 dark:text-gray-300 leading-none font-bold font-title flex gap-2 items-center pb-2">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 size-5" viewBox="0 0 20 20"><path fill="currentColor" d="M2.049 9.112a8.001 8.001 0 1 1 9.718 8.692a1.5 1.5 0 0 0-.206-1.865l-.01-.01q.244-.355.47-.837a9.3 9.3 0 0 0 .56-1.592H9.744q.17-.478.229-1h2.82A15 15 0 0 0 13 10c0-.883-.073-1.725-.206-2.5H7.206l-.05.315a4.5 4.5 0 0 0-.971-.263l.008-.052H3.46q-.112.291-.198.595c-.462.265-.873.61-1.213 1.017m9.973-4.204C11.407 3.59 10.657 3 10 3s-1.407.59-2.022 1.908A9.3 9.3 0 0 0 7.42 6.5h5.162a9.3 9.3 0 0 0-.56-1.592M6.389 6.5c.176-.743.407-1.422.683-2.015c.186-.399.401-.773.642-1.103A7.02 7.02 0 0 0 3.936 6.5zm9.675 7H13.61a10.5 10.5 0 0 1-.683 2.015a6.6 6.6 0 0 1-.642 1.103a7.02 7.02 0 0 0 3.778-3.118m-2.257-1h2.733c.297-.776.46-1.62.46-2.5s-.163-1.724-.46-2.5h-2.733c.126.788.193 1.63.193 2.5s-.067 1.712-.193 2.5m2.257-6a7.02 7.02 0 0 0-3.778-3.118c.241.33.456.704.642 1.103c.276.593.507 1.272.683 2.015zm-7.76 7.596a3.5 3.5 0 1 0-.707.707l2.55 2.55a.5.5 0 0 0 .707-.707zM8 12a2.5 2.5 0 1 1-5 0a2.5 2.5 0 0 1 5 0" /></svg>
Find people to play with
</span>
</div>
<ul class="list-none ml-4 relative w-[calc(100%-1rem)]">
{activeUsers.value ? (
activeUsers.value.slice(0, 3).map((user, key) => (
<div key={`user-${key}`} >
<div class="gap-3.5 hover:bg-gray-200 dark:hover:bg-gray-800 text-left outline-none group rounded-lg px-3 [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] flex items-center w-full">
<div class="relative [&>svg]:size-[80px] [&>img]:size-[80px] min-w-[80px]">
{user.avatarUrl ?
(<img height={52} width={52} draggable={false} class="[transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] ring-2 ring-gray-200 dark:ring-gray-800 select-none rounded-full aspect-square w-[80px]" src={user.avatarUrl} alt={user.username} />) :
(<Avatar name={`${user.username}#${user.discriminator}`} />)}
</div>
<div class={cn("w-full h-[100px] overflow-hidden pr-2 border-b-2 border-gray-400/70 dark:border-gray-700/70 flex gap-2 items-center", key == 2 && "border-none")}>
<div class="flex-col">
<span class="font-medium tracking-tighter text-gray-700 dark:text-gray-300 max-w-full text-lg truncate leading-none flex [&>svg]:size-5 [&>svg]:dark:text-[#12ECFA] ">
{`${user.username}`}&nbsp;<p class="hidden group-hover:block text-gray-600/70 dark:text-gray-400/70 transition-all duration-200 ease-in">{` #${user.discriminator}`}</p>
{/* &nbsp;{user.status && (user.status == "active" && (<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M10.277 16.515c.005-.11.186-.154.24-.058c.254.45.686 1.111 1.176 1.412s1.276.386 1.792.408c.11.005.153.186.057.24c-.45.254-1.11.686-1.411 1.176s-.386 1.276-.408 1.792c-.005.11-.187.153-.24.057c-.254-.45-.686-1.11-1.177-1.411c-.49-.301-1.276-.386-1.791-.408c-.11-.005-.154-.187-.058-.24c.45-.254 1.111-.686 1.412-1.177c.3-.49.386-1.276.408-1.791"/><path fill="currentColor" d="M18.492 15.515c-.009-.11-.2-.156-.258-.062c-.172.283-.42.623-.697.793s-.692.236-1.022.262c-.11.008-.156.2-.062.257c.282.172.623.42.793.697s.236.693.262 1.023c.008.11.2.155.257.061c.172-.282.42-.623.697-.792s.693-.237 1.023-.262c.11-.009.155-.2.061-.258c-.282-.172-.623-.42-.792-.697s-.237-.692-.262-1.022" opacity=".5"/><path fill="currentColor" d="m14.703 4.002l-.242-.306c-.937-1.183-1.405-1.775-1.95-1.688c-.544.088-.805.796-1.326 2.213l-.135.366c-.148.403-.222.604-.364.752s-.336.225-.724.38l-.353.141l-.247.1c-1.2.48-1.804.753-1.882 1.283c-.082.565.49 1.049 1.634 2.016l.296.25c.326.275.488.413.581.6c.094.187.107.403.133.835l.024.393c.094 1.52.14 2.28.635 2.542c.494.262 1.108-.147 2.336-.966l.318-.212c.349-.233.523-.35.723-.381s.401.024.806.136l.367.102c1.423.394 2.134.591 2.521.188c.388-.403.195-1.14-.19-2.613l-.1-.381c-.109-.419-.164-.628-.134-.835s.142-.389.366-.752l.203-.33c.785-1.276 1.178-1.914.924-2.426c-.255-.51-.988-.557-2.454-.648l-.38-.024c-.416-.026-.624-.039-.805-.135s-.314-.264-.58-.6"/><path fill="currentColor" d="M8.835 13.326C6.698 14.37 4.919 16.024 4.248 18c-.752-4.707.292-7.747 1.965-9.637c.144.295.332.539.5.73c.35.396.852.82 1.362 1.251l.367.31l.17.145c.005.064.01.14.015.237l.03.485c.04.655.08 1.294.178 1.805" opacity=".5"/></svg>))} */}
</span>
<div class="flex items-center gap-2 w-full cursor-pointer px-1 rounded-md">
<div class={cn("font-normal capitalize w-full text-gray-600/70 dark:text-gray-400/70 truncate flex gap-1 items-center", (user.status && user.status == "active") && "dark:text-[#50e3c2]")}>
<div class={cn("size-3 rounded-full block", user.status ? (user.status == "active" ? "bg-[#50e3c2]" : user.status == "idle" ? "bg-[#ff990a]" : "hidden") : "hidden")} />
{user.status ? (user.status == "active" ? "Playing Steam" : user.status) : "Offline"}
</div>
</div>
</div>
<div class="ml-auto relative flex gap-2 justify-center h-full items-center">
{user.status && (user.status == "active" &&
(
<Tooltip.Root gutter={12} flip={false} placement="top" >
<Tooltip.Trigger onClick$={async () => { await onWatch(user.id) }} type="button" class="bg-gray-200 group-hover:bg-gray-300 dark:group-hover:bg-gray-700 transition-all duration-200 ease-in text-gray-600 dark:text-gray-400 dark:bg-gray-800 [&>svg]:size-5 p-2 rounded-full" >
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M4.75 5.5c.427 0 .791.163 1.075.45c-.145-.778-.37-1.415-.64-1.894C4.72 3.236 4.216 3 3.75 3s-.97.237-1.434 1.056C1.835 4.906 1.5 6.25 1.5 8s.335 3.094.816 3.944c.463.82.967 1.056 1.434 1.056s.97-.237 1.434-1.056c.272-.48.496-1.116.64-1.895a1.47 1.47 0 0 1-1.074.451C3.674 10.5 3 9.47 3 8s.674-2.5 1.75-2.5M7.5 8c0 3.822-1.445 6.5-3.75 6.5S0 11.822 0 8s1.445-6.5 3.75-6.5S7.5 4.178 7.5 8m6.825 2.05c-.145.778-.37 1.415-.64 1.894c-.464.82-.968 1.056-1.435 1.056s-.97-.237-1.434-1.056C10.335 11.094 10 9.75 10 8s.335-3.094.816-3.944C11.279 3.236 11.783 3 12.25 3s.97.237 1.434 1.056c.272.48.496 1.116.64 1.895A1.47 1.47 0 0 0 13.25 5.5c-1.076 0-1.75 1.03-1.75 2.5s.674 2.5 1.75 2.5a1.47 1.47 0 0 0 1.075-.45M16 8c0 3.822-1.445 6.5-3.75 6.5S8.5 11.822 8.5 8s1.445-6.5 3.75-6.5S16 4.178 16 8" clip-rule="evenodd" /></svg>
</Tooltip.Trigger>
<Tooltip.Panel class="tooltip capitalize text-sm relative rounded-md dark:bg-gray-700 bg-gray-300 py-2 px-3 text-gray-700 dark:text-gray-300" aria-label="More options">
Watch stream
<div class="-bottom-2 left-1/2 -translate-x-1/2 absolute dark:text-gray-700 text-gray-300" >
<svg width="15" height="10" viewBox="0 0 30 10" preserveAspectRatio="none" fill="currentColor"><polygon points="0,0 30,0 15,10"></polygon></svg>
</div>
</Tooltip.Panel>
</Tooltip.Root>
))}
<Tooltip.Root gutter={12} flip={false} placement="top" >
<Tooltip.Trigger disabled type="button" class="bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed group-hover:bg-gray-300 dark:group-hover:bg-gray-700 transition-all duration-200 ease-in text-gray-600 dark:text-gray-400 dark:bg-gray-800 [&>svg]:size-5 p-2 rounded-full" >
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0-8 0m8 12h6m-3-3v6M6 21v-2a4 4 0 0 1 4-4h4" /></svg>
</Tooltip.Trigger>
<Tooltip.Panel class="tooltip capitalize text-sm relative rounded-md dark:bg-gray-700 bg-gray-300 py-2 px-3 text-gray-700 dark:text-gray-300" aria-label="More options">
Invite
<div class="-bottom-2 left-1/2 -translate-x-1/2 absolute dark:text-gray-700 text-gray-300" >
<svg width="15" height="10" viewBox="0 0 30 10" preserveAspectRatio="none" fill="currentColor"><polygon points="0,0 30,0 15,10"></polygon></svg>
</div>
</Tooltip.Panel>
</Tooltip.Root>
<Tooltip.Root gutter={12} flip={false} placement="top" >
<Tooltip.Trigger disabled type="button" class="bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed group-hover:bg-gray-300 dark:group-hover:bg-gray-700 transition-all duration-200 ease-in text-gray-600 dark:text-gray-400 dark:bg-gray-800 [&>svg]:size-5 p-2 rounded-full" >
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2s-2 .9-2 2s.9 2 2 2m0 2c-1.1 0-2 .9-2 2s.9 2 2 2s2-.9 2-2s-.9-2-2-2m0 6c-1.1 0-2 .9-2 2s.9 2 2 2s2-.9 2-2s-.9-2-2-2" /></svg>
</Tooltip.Trigger>
<Tooltip.Panel class="tooltip capitalize text-sm relative rounded-md dark:bg-gray-700 bg-gray-300 py-2 px-3 text-gray-700 dark:text-gray-300" aria-label="More options">
more
<div class="-bottom-2 left-1/2 -translate-x-1/2 absolute dark:text-gray-700 text-gray-300" >
<svg width="15" height="10" viewBox="0 0 30 10" preserveAspectRatio="none" fill="currentColor"><polygon points="0,0 30,0 15,10"></polygon></svg>
</div>
</Tooltip.Panel>
</Tooltip.Root>
</div>
</div>
</div>
</div>
))
) :
(
skeletonCrew.map((_, key) => (
<div key={`skeleton-friend-${key}`} >
<div class="gap-3.5 text-left animate-pulse outline-none group rounded-lg px-3 [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] flex items-center w-full">
<div class="relative w-max">
<div class="size-20 rounded-full bg-gray-200 dark:bg-gray-800" />
</div>
<div class={cn("w-full h-[100px] overflow-hidden pr-2 border-b-2 border-gray-400/70 dark:border-gray-700/70 flex gap-2 items-center", key == 2 && "border-none")}>
<div class="flex-col w-[80%] gap-2 flex">
<span class="font-medium tracking-tighter bg-gray-200 dark:bg-gray-800 rounded-md h-6 w-2/3 max-w-full text-lg font-title truncate leading-none block" />
<div class="flex items-center gap-2 w-full h-6 bg-gray-200 dark:bg-gray-800 rounded-md" />
</div>
<div class="bg-gray-200 dark:bg-gray-800 h-7 w-16 ml-auto rounded-md" />
</div>
</div>
</div>
))
)
}
<div class="[border:1px_dashed_theme(colors.gray.300)] dark:[border:1px_dashed_theme(colors.gray.800)] [mask-image:linear-gradient(rgb(0,0,0)_0%,_rgb(0,0,0)_calc(100%-120px),_transparent_100%)] bottom-0 top-0 -left-[0.4625rem] absolute" />
</ul>
</div>
</div>
)
})

View File

@@ -1,102 +0,0 @@
import type Nestri from "@nestri/sdk";
import { useNavigate } from "@builder.io/qwik-city";
import { $, component$, useOnDocument, useSignal, type QRL } from "@builder.io/qwik";
type Props = {
getUserSubscription$: QRL<() => Promise<"Free" | "Pro" | undefined>>
createSession$: QRL<() => Promise<Nestri.Tasks.TaskSessionResponse.Data | undefined>>
}
const skeletonGames = new Array(6).fill(0)
export const HomeGamesSection = component$(({ getUserSubscription$ }: Props) => { //createSession$
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const nav = useNavigate()
const creatingSession = useSignal(false)
const userSubscription = useSignal<"Free" | "Pro" | undefined>()
useOnDocument("load", $(async () => {
const userSub = sessionStorage.getItem("subscription_data")
if (userSub) {
userSubscription.value = JSON.parse(userSub)
} else {
const subscription = await getUserSubscription$()
sessionStorage.setItem("subscription_data", JSON.stringify(subscription))
userSubscription.value = subscription
}
}))
const onClick = $(async () => {
console.log("clicked")
// creatingSession.value = true
// const sessionID = await createSession$()
// if (sessionID) {
// creatingSession.value = false
// await nav(`/play/${sessionID.id}`)
// }
});
return (
<div class="gap-2 w-full flex-col flex">
<hr class="border-none h-[1.5px] dark:bg-gray-700 bg-gray-300 w-full" />
<div class="text-gray-600/70 dark:text-gray-400/70 text-sm leading-none flex justify-start py-2 px-3 items-end">
<span class="text-xl text-gray-700 dark:text-gray-300 leading-none font-bold font-title flex gap-2 ">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 size-5" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 22c-.818 0-1.6-.33-3.163-.99C4.946 19.366 3 18.543 3 17.16V7m9 15c.818 0 1.6-.33 3.163-.99C19.054 19.366 21 18.543 21 17.16V7m-9 15V11.355M8.326 9.691L5.405 8.278C3.802 7.502 3 7.114 3 6.5s.802-1.002 2.405-1.778l2.92-1.413C10.13 2.436 11.03 2 12 2s1.871.436 3.674 1.309l2.921 1.413C20.198 5.498 21 5.886 21 6.5s-.802 1.002-2.405 1.778l-2.92 1.413C13.87 10.564 12.97 11 12 11s-1.871-.436-3.674-1.309M6 12l2 1m9-9L7 9" color="currentColor" /></svg>
Your Games
</span>
{/* {userSubscription.value ? (
<button disabled={userSubscription.value === "Free"} class="disabled:opacity-50 disabled:cursor-not-allowed ml-auto flex gap-1 items-center cursor-pointer [&:not(:disabled)]:hover:text-gray-800 dark:[&:not(:disabled)]:hover:text-gray-200 transition-all duration-200 outline-none">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 size-5" viewBox="0 0 256 256"><path fill="currentColor" d="M248 128a87.34 87.34 0 0 1-17.6 52.81a8 8 0 1 1-12.8-9.62A71.34 71.34 0 0 0 232 128a72 72 0 0 0-144 0a8 8 0 0 1-16 0a88 88 0 0 1 3.29-23.88C74.2 104 73.1 104 72 104a48 48 0 0 0 0 96h24a8 8 0 0 1 0 16H72a64 64 0 1 1 9.29-127.32A88 88 0 0 1 248 128m-69.66 42.34L160 188.69V128a8 8 0 0 0-16 0v60.69l-18.34-18.35a8 8 0 0 0-11.32 11.32l32 32a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32" /></svg>
<span>Install a game</span>
</button>
) : (
<div class="ml-auto h-4 w-28 rounded-md bg-gray-200 dark:gray-800 animate-pulse" />
)} */}
</div>
<ul class="relative py-3 w-full list-none after:pointer-events-none after:select-none after:w-full after:h-[120px] after:fixed after:z-10 after:backdrop-blur-[1px] after:bg-gradient-to-b after:from-transparent after:to-gray-200 dark:after:to-gray-800 after:[-webkit-mask-image:linear-gradient(to_top,theme(colors.gray.200)_25%,transparent)] dark:after:[-webkit-mask-image:linear-gradient(to_top,theme(colors.gray.800)_25%,transparent)] after:left-0 after:-bottom-[1px]">
{userSubscription.value ? (
<div class="flex flex-col items-center justify-center gap-6 px-6 py-20 w-full" >
<div class="relative flex items-center justify-center overflow-hidden rounded-[22px] p-[2px] before:absolute before:left-[-50%] before:top-[-50%] before:z-[-2] before:h-[200%] before:w-[200%] before:animate-[bgRotate_1.15s_linear_infinite] before:bg-[conic-gradient(from_0deg,transparent_0%,#ff4f01_10%,#ff4f01_25%,transparent_35%)] before:content-[''] after:absolute after:inset-[2px] after:z-[-1] after:content-['']" >
<div class="flex items-center justify-center rounded-[20px] bg-gray-200 dark:bg-gray-800 p-1">
<div class="flex items-center justify-center rounded-2xl bg-[#F5F5F5] p-1 dark:bg-[#171717]">
<div class="flex h-[64px] w-[64px] items-center justify-center rounded-xl bg-gray-100 dark:bg-gray-900">
<svg xmlns="http://www.w3.org/2000/svg" width="32" class="h-8 w-8 shrink-0 dark:text-gray-700 text-gray-300" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11.968 2C6.767 2 2.4 6.045 2.048 11.181l5.329 2.216c.45-.322.995-.45 1.573-.45h.128l2.344-3.5v-.031a3.74 3.74 0 0 1 3.756-3.756c2.087 0 3.788 1.67 3.788 3.756a3.74 3.74 0 0 1-3.756 3.756h-.096l-3.403 2.44v.128a2.863 2.863 0 0 1-2.857 2.857c-1.349 0-2.536-.995-2.761-2.247l-3.724-1.637C3.557 18.886 7.44 22 11.968 22c5.49-.032 9.984-4.494 9.984-10.016S17.457 2 11.968 2" /><path fill="currentColor" d="m8.276 17.152l-1.22-.481c.225.45.578.867 1.092 1.027c1.027.45 2.311-.032 2.76-1.123a2.07 2.07 0 0 0 0-1.638a2.26 2.26 0 0 0-1.123-1.187c-.514-.225-1.027-.193-1.54-.033l1.251.546c.77.353 1.188 1.252.867 2.023c-.353.802-1.252 1.155-2.087.866m9.502-7.736c0-1.349-1.124-2.536-2.536-2.536c-1.349 0-2.536 1.123-2.536 2.536c0 1.412 1.188 2.536 2.536 2.536s2.536-1.156 2.536-2.536m-4.366 0c0-1.027.867-1.862 1.862-1.862c1.027 0 1.862.867 1.862 1.862c0 1.027-.867 1.862-1.862 1.862c-1.027.032-1.862-.835-1.862-1.862" /></svg>
</div>
</div>
</div>
</div>
<div class="flex flex-col items-center justify-center gap-1">
<span class="select-none text-center text-gray-700 dark:text-gray-300 font-title text-xl font-semibold sm:font-medium">Waiting for your first game install</span>
<p class="text-center text-base font-medium text-gray-600 dark:text-gray-400 sm:font-regular">Once you have installed a game on your machine, it should appear here</p>
</div>
<button
onClick$={onClick}
// disabled={userSubscription.value === "Free"}
disabled
class="flex gap-2 h-[48px] disabled:cursor-not-allowed disabled:opacity-50 max-w-[360px] w-full select-none items-center justify-center rounded-full bg-primary-500 text-base font-semibold text-white transition-all duration-200 ease-out [&:not(:disabled)]:hover:ring-2 [&:not(:disabled)]:hover:ring-gray-600 dark:[&:not(:disabled)]:hover:ring-gray-400 [&:not(:disabled)]:focus:scale-95 [&:not(:disabled)]:active:scale-95 sm:font-medium">
{creatingSession.value &&
<div style={{ "--spinner-color": "#FFF" }} data-component="spinner">
<div>
{new Array(12).fill(0).map((i, k) => (
<div key={k} />
))}
</div>
</div>
}
<span> {creatingSession.value ? "Launching Steam" : "Launch Steam"}</span>
</button>
</div>
) : (
<div class="grid sm:grid-cols-3 grid-cols-2 gap-2 gap-y-3 w-full animate-pulse" >
{skeletonGames.map((_, key) => (
<div key={`skeleton-game-${key}`} class="w-full gap-2 flex flex-col" >
<div class="bg-gray-200 dark:bg-gray-800 w-full aspect-square rounded-2xl" />
</div>
))}
</div>
)}
</ul >
</div >
)
})

View File

@@ -1,131 +0,0 @@
/* eslint-disable qwik/jsx-img */
import { cn } from "../design";
import { MotionComponent } from "../react";
import { $, component$, useOnDocument, useSignal, type QRL } from "@builder.io/qwik";
import { Link } from "@builder.io/qwik-city";
type Props = {
getUserSubscription$: QRL<() => Promise<"Free" | "Pro" | undefined>>
}
export const HomeMachineSection = component$(({ getUserSubscription$ }: Props) => {
const isHovered = useSignal(false)
const userSubscription = useSignal<"Free" | "Pro" | undefined>()
useOnDocument("load", $(async () => {
const userSub = sessionStorage.getItem("subscription_data")
if (userSub) {
userSubscription.value = JSON.parse(userSub)
} else {
const subscription = await getUserSubscription$()
sessionStorage.setItem("subscription_data", JSON.stringify(subscription))
userSubscription.value = subscription
}
}))
return (
<div class="flex flex-col gap-6 w-full py-4">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
{userSubscription.value ? (
<>
{userSubscription.value == "Pro" ? (
<div class="w-full animate-fade-in opacity-0 transition-all duration-200 ease-in" >
<Link href="/machine" class="w-full border-gray-400/70 dark:border-gray-700/70 hover:ring-2 hover:ring-[#8f8f8f] dark:hover:ring-[#707070] outline-none group transition-all duration-200 border-[2px] h-14 rounded-xl px-4 gap-2 flex items-center justify-between overflow-hidden bg-white dark:bg-black hover:bg-gray-300/70 dark:hover:bg-gray-700/70 disabled:opacity-50">
<div class="py-2 w-2/3 flex truncate group">
<div class="flex items-center justify-between group gap-2">
<div class="flex items-center w-auto gap-2 overflow-hidden">
<div class="flex items-center gap-2">
<div class="w-auto select-none text-nowrap font-medium transition-colors duration-200 ease-out group-hover:text-black text-black/80 dark:group-hover:text-white dark:text-white/80">Steam Machine</div>
</div>
</div>
{/* <div class="select-none flex gap-1 items-center overflow-hidden rounded-md px-2 py-1 text-xs font-medium transition-colors duration-200 ease-out" > */}
<div class={cn("select-none overflow-hidden flex gap-1 uppercase items-center rounded-md px-2 py-1 text-xs font-medium transition-colors duration-200 ease-out", userSubscription.value === "Pro" ? "bg-[#0CCE6B]/30 text-[#0CCE6B] group-hover:bg-[#0CCE6B]/40" : " bg-[#EE0048]/30 text-[#EE0048] hover:bg-[#EE0048]/40")}>
<div class={cn("size-2 rounded-full", userSubscription.value == "Pro" ? "bg-[#0CCE6B]" : "bg-[#EE0048]")} />
<span>{userSubscription.value == "Pro" ? "Online" : "Error"}</span>
</div>
</div>
</div>
<div
style={{
"--cutout-avatar-percentage-visible": 0.2,
"--head-margin-percentage": 0.1,
"--size": "3rem"
}}
class="relative h-full flex w-[20%] justify-end">
<img draggable={false} alt="game" width={256} height={256} src="/images/steam.png" class="h-12 shadow-lg shadow-gray-900 ring-gray-400/70 ring-1 bg-black w-12 translate-y-4 rotate-[14deg] rounded-lg object-cover transition-transform sm:h-16 sm:w-16 group-hover:scale-110" />
</div>
</Link>
</div>
) : (
<div class="w-full animate-fade-in opacity-0 transition-all duration-200 ease-in relative">
<MotionComponent
client:load
as="span"
initial={{ x: 80, y: 0, rotate: 0 }}
animate={{
x: isHovered.value ? 5 : 80,
y: isHovered.value ? -20 : 0,
rotate: isHovered.value ? 720 : 0
}}
transition={{
type: "spring",
stiffness: 200,
damping: 15,
}}
class="absolute text-[#99CCFF] flex items-center justify-center z-[1]">
<svg height="16" class="size-10" stroke-linejoin="round" viewBox="0 0 16 16" width="16" color="currentColor">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.49999 0H6.49999L6.22628 1.45975C6.1916 1.64472 6.05544 1.79299 5.87755 1.85441C5.6298 1.93996 5.38883 2.04007 5.15568 2.15371C4.98644 2.2362 4.78522 2.22767 4.62984 2.12136L3.40379 1.28249L1.28247 3.40381L2.12135 4.62986C2.22766 4.78524 2.23619 4.98646 2.1537 5.15569C2.04005 5.38885 1.93995 5.62981 1.8544 5.87756C1.79297 6.05545 1.6447 6.19162 1.45973 6.2263L0 6.5V9.5L1.45973 9.7737C1.6447 9.80838 1.79297 9.94455 1.8544 10.1224C1.93995 10.3702 2.04006 10.6112 2.1537 10.8443C2.23619 11.0136 2.22767 11.2148 2.12136 11.3702L1.28249 12.5962L3.40381 14.7175L4.62985 13.8786C4.78523 13.7723 4.98645 13.7638 5.15569 13.8463C5.38884 13.9599 5.6298 14.06 5.87755 14.1456C6.05544 14.207 6.1916 14.3553 6.22628 14.5403L6.49999 16H9.49999L9.77369 14.5403C9.80837 14.3553 9.94454 14.207 10.1224 14.1456C10.3702 14.06 10.6111 13.9599 10.8443 13.8463C11.0135 13.7638 11.2147 13.7723 11.3701 13.8786L12.5962 14.7175L14.7175 12.5962L13.8786 11.3701C13.7723 11.2148 13.7638 11.0135 13.8463 10.8443C13.9599 10.6112 14.06 10.3702 14.1456 10.1224C14.207 9.94455 14.3553 9.80839 14.5402 9.7737L16 9.5V6.5L14.5402 6.2263C14.3553 6.19161 14.207 6.05545 14.1456 5.87756C14.06 5.62981 13.9599 5.38885 13.8463 5.1557C13.7638 4.98647 13.7723 4.78525 13.8786 4.62987L14.7175 3.40381L12.5962 1.28249L11.3701 2.12137C11.2148 2.22768 11.0135 2.2362 10.8443 2.15371C10.6111 2.04007 10.3702 1.93996 10.1224 1.85441C9.94454 1.79299 9.80837 1.64472 9.77369 1.45974L9.49999 0ZM8 11C9.65685 11 11 9.65685 11 8C11 6.34315 9.65685 5 8 5C6.34315 5 5 6.34315 5 8C5 9.65685 6.34315 11 8 11Z" fill="currentColor"></path>
</svg>
</MotionComponent>
<MotionComponent
as="span"
client:load
initial={{ x: 80, y: 0, rotate: 0 }}
animate={{
x: isHovered.value ? 100 : 20,
y: isHovered.value ? 35 : 0,
rotate: isHovered.value ? 720 : 0
}}
transition={{
type: "spring",
stiffness: 200,
damping: 15,
}}
class="absolute text-[#99CCFF] flex items-center justify-center z-[1]">
<svg height="16" class="size-10" stroke-linejoin="round" viewBox="0 0 16 16" width="16" color="currentColor">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.49999 0H6.49999L6.22628 1.45975C6.1916 1.64472 6.05544 1.79299 5.87755 1.85441C5.6298 1.93996 5.38883 2.04007 5.15568 2.15371C4.98644 2.2362 4.78522 2.22767 4.62984 2.12136L3.40379 1.28249L1.28247 3.40381L2.12135 4.62986C2.22766 4.78524 2.23619 4.98646 2.1537 5.15569C2.04005 5.38885 1.93995 5.62981 1.8544 5.87756C1.79297 6.05545 1.6447 6.19162 1.45973 6.2263L0 6.5V9.5L1.45973 9.7737C1.6447 9.80838 1.79297 9.94455 1.8544 10.1224C1.93995 10.3702 2.04006 10.6112 2.1537 10.8443C2.23619 11.0136 2.22767 11.2148 2.12136 11.3702L1.28249 12.5962L3.40381 14.7175L4.62985 13.8786C4.78523 13.7723 4.98645 13.7638 5.15569 13.8463C5.38884 13.9599 5.6298 14.06 5.87755 14.1456C6.05544 14.207 6.1916 14.3553 6.22628 14.5403L6.49999 16H9.49999L9.77369 14.5403C9.80837 14.3553 9.94454 14.207 10.1224 14.1456C10.3702 14.06 10.6111 13.9599 10.8443 13.8463C11.0135 13.7638 11.2147 13.7723 11.3701 13.8786L12.5962 14.7175L14.7175 12.5962L13.8786 11.3701C13.7723 11.2148 13.7638 11.0135 13.8463 10.8443C13.9599 10.6112 14.06 10.3702 14.1456 10.1224C14.207 9.94455 14.3553 9.80839 14.5402 9.7737L16 9.5V6.5L14.5402 6.2263C14.3553 6.19161 14.207 6.05545 14.1456 5.87756C14.06 5.62981 13.9599 5.38885 13.8463 5.1557C13.7638 4.98647 13.7723 4.78525 13.8786 4.62987L14.7175 3.40381L12.5962 1.28249L11.3701 2.12137C11.2148 2.22768 11.0135 2.2362 10.8443 2.15371C10.6111 2.04007 10.3702 1.93996 10.1224 1.85441C9.94454 1.79299 9.80837 1.64472 9.77369 1.45974L9.49999 0ZM8 11C9.65685 11 11 9.65685 11 8C11 6.34315 9.65685 5 8 5C6.34315 5 5 6.34315 5 8C5 9.65685 6.34315 11 8 11Z" fill="currentColor"></path>
</svg>
</MotionComponent>
<div class="hover:border-[#99CCFF]/70 border-gray-300 dark:border-gray-700 bg-white dark:bg-black z-[5] relative w-full transition-all duration-300 border-[2px] h-14 rounded-xl px-4 gap-2 flex items-center justify-between overflow-hidden outline-none disabled:opacity-50">
<span class="p-2 pl-0 text-gray-800/70 dark:text-gray-200/70 leading-none shrink truncate flex text-start items-center gap-2">
<a
onMouseEnter$={() => isHovered.value = true}
onMouseLeave$={() => isHovered.value = false}
href="https://polar.sh/nestri" class="dark:text-white text-black border-b border-[#99CCFF] py-0.5">Upgrade to Pro</a>&nbsp;to get a machine
</span>
</div>
</div>
)}
<button class="w-full animate-fade-in opacity-0 transition-all duration-200 ease-in">
<div class="border-gray-400/70 w-full dark:border-gray-700/70 transition-all border-dashed duration-200 border-[2px] h-14 rounded-xl px-4 gap-2 flex items-center justify-between overflow-hidden outline-none disabled:opacity-50">
<span class="p-2 pl-0 text-gray-600/70 dark:text-gray-400/70 leading-none shrink truncate flex text-start items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 size-5" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M11.505 2h-1.501c-3.281 0-4.921 0-6.084.814a4.5 4.5 0 0 0-1.106 1.105C2 5.08 2 6.72 2 10s0 4.919.814 6.081a4.5 4.5 0 0 0 1.106 1.105C5.083 18 6.723 18 10.004 18h4.002c3.28 0 4.921 0 6.084-.814a4.5 4.5 0 0 0 1.105-1.105c.63-.897.772-2.08.805-4.081m-8-6h4m0 0h4m-4 0V2m0 4v4m-7 5h2m-1 3v4m-4 0h8" color="currentColor" /></svg>
Add your machine
<div class="select-none text-[#ff990a] bg-[#ff990a]/30 h-max uppercase overflow-hidden rounded-md px-2 py-1 text-xs transition-colors duration-200 ease-out font-semibold font-title">
<span>Soon</span>
</div>
</span>
</div>
</button>
</>
) :
new Array(2).fill(0).map((_, key) => (
<div class="w-full animate-pulse" key={`skeleton-machine-${key}`}>
<div class="rounded-xl bg-gray-200 dark:bg-gray-800 h-14 w-full" />
</div>
))
}
</div>
</div >
)
})

View File

@@ -1,393 +0,0 @@
import { cn } from "../design";
import Avatar from "../avatar"
import type Nestri from "@nestri/sdk"
import { MotionComponent } from "../react";
import { Dropdown, Modal } from '@qwik-ui/headless';
import { useLocation, useNavigate } from "@builder.io/qwik-city";
import { disablePageScroll, enablePageScroll } from '@fluejs/noscroll';
import { $, component$, type QRL, useOnDocument, useSignal } from "@builder.io/qwik";
type Props = {
getUserProfile$: QRL<() => Promise<Nestri.Users.UserRetrieveResponse.Data | undefined>>
}
export const HomeNavBar = component$(({ getUserProfile$ }: Props) => {
const nav = useNavigate()
const inviteEmail = useSignal('');
const location = useLocation().url
const inviteName = useSignal('');
const isHolding = useSignal(false);
const newTeamName = useSignal('');
const isNewTeam = useSignal(false);
const hasScrolled = useSignal(false);
const isNewMember = useSignal(false);
const showInviteSuccess = useSignal(false);
const userProfile = useSignal<Nestri.Users.UserRetrieveResponse.Data | undefined>()
const onDialogOpen = $((open: boolean) => {
if (open) {
disablePageScroll()
} else {
enablePageScroll()
}
})
const handlePointerDown = $(() => {
isHolding.value = true
});
const handlePointerUp = $(() => {
isHolding.value = false
});
const handleAddTeam = $((e: any) => {
e.preventDefault();
if (newTeamName.value.trim()) {
// teams.value = [...teams.value, { name: newTeamName.value.trim() }];
// selectedTeam.value = newTeamName.value.trim()
newTeamName.value = '';
isNewTeam.value = false;
}
});
const handleInvite = $((e: any) => {
e.preventDefault();
if (inviteName.value && inviteEmail.value) {
// Here you would typically make an API call to send the invitation
console.log('Sending invite to:', { name: inviteName.value, email: inviteEmail.value });
inviteName.value = '';
inviteEmail.value = '';
isNewMember.value = false;
showInviteSuccess.value = true;
setTimeout(() => {
showInviteSuccess.value = false;
}, 3000);
}
});
useOnDocument(
'scroll',
$(() => {
hasScrolled.value = window.scrollY > 0;
})
);
const handleLogout = $(async() => {
await nav(`/auth/logout`)
});
const handleLogoutAnimationComplete = $(() => {
if (isHolding.value) {
console.log("fucking hell")
// isDeleting.value = true;
// Reset the holding state
isHolding.value = false;
handleLogout();
}
});
useOnDocument("load", $(async () => {
const currentProfile = await getUserProfile$()
userProfile.value = currentProfile
}))
return (
<>
<nav class={cn("fixed w-screen justify-between top-0 z-50 px-2 sm:px-6 text-xs sm:text-sm leading-[1] text-gray-950/70 dark:text-gray-50/70 h-[66px] before:backdrop-blur-[15px] before:absolute before:-z-[1] before:top-0 before:left-0 before:w-full before:h-full flex items-center", hasScrolled.value && "shadow-[0_2px_20px_1px] shadow-gray-300 dark:shadow-gray-700")} >
<div class="flex flex-row justify-center relative items-center top-0 bottom-0">
<div class="flex-shrink-0 gap-2 flex justify-center items-center">
<svg
class="size-8 "
width="100%"
height="100%"
viewBox="0 0 12.8778 9.7377253"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg">
<path
d="m 2.093439,1.7855532 h 8.690922 V 2.2639978 H 2.093439 Z m 0,2.8440874 h 8.690922 V 5.1080848 H 2.093439 Z m 0,2.8440866 h 8.690922 V 7.952172 H 2.093439 Z"
style="font-size:12px;fill:#ff4f01;fill-opacity:1;fill-rule:evenodd;stroke:#ff4f01;stroke-width:1.66201;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1" />
</svg>
</div>
<div class="relative z-[5] items-center flex">
<hr class="dark:bg-gray-700/70 bg-gray-400/70 w-0.5 rounded-md mx-3 rotate-[16deg] h-7 border-none" />
{userProfile.value ? (
<Dropdown.Root class="animate-fade-in opacity-0 transition-all duration-200 ease-in" onOpenChange$={onDialogOpen}>
<Dropdown.Trigger class="text-sm [&>svg:first-child]:size-5 rounded-full h-8 focus:bg-gray-300/70 dark:focus:bg-gray-700/70 focus:ring-[#8f8f8f] dark:focus:ring-[#707070] focus:ring-2 outline-none dark:text-gray-400 text-gray-600 gap-2 px-3 cursor-pointer inline-flex transition-all duration-150 items-center hover:bg-gray-300/70 dark:hover:bg-gray-700/70 ">
<Avatar name={`${userProfile.value.username}'s Games`} />
<span class="truncate shrink max-w-[20ch]">{`${userProfile.value.username}'s Games`}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M72.61 83.06a8 8 0 0 1 1.73-8.72l48-48a8 8 0 0 1 11.32 0l48 48A8 8 0 0 1 176 88H80a8 8 0 0 1-7.39-4.94M176 168H80a8 8 0 0 0-5.66 13.66l48 48a8 8 0 0 0 11.32 0l48-48A8 8 0 0 0 176 168" /></svg>
</Dropdown.Trigger>
<Dropdown.Popover
class="bg-[hsla(0,0%,100%,.5)] dark:bg-[hsla(0,0%,100%,.026)] min-w-[160px] max-w-[240px] backdrop-blur-md rounded-lg py-1 px-2 border border-[#e8e8e8] dark:border-[#2e2e2e] [box-shadow:0_8px_30px_rgba(0,0,0,.12)]">
<Dropdown.RadioGroup class="w-full flex overflow-hidden flex-col gap-1 [&_*]:w-full [&_[data-checked]]:bg-[rgba(0,0,0,.071)] dark:[&_[data-checked]]:bg-[hsla(0,0%,100%,.077)] [&_[data-checked]]:rounded-md [&_[data-checked]]:text-[#171717] [&_[data-checked]_svg]:block cursor-pointer [&_[data-highlighted]]:text-[#171717] dark:[&_[data-checked]]:text-[#ededed] dark:[&_[data-highlighted]]:text-[#ededed] [&_[data-highlighted]]:bg-[rgba(0,0,0,.071)] dark:[&_[data-highlighted]]:bg-[hsla(0,0%,100%,.077)] [&_[data-highlighted]]:rounded-md">
<Dropdown.RadioItem
value={`${userProfile.value.username}'s Games`}
class="leading-none text-sm items-center flex px-2 h-8 rounded-md outline-none relative select-none w-full"
>
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate [&>svg]:size-5 text-[#6f6f6f] dark:text-[#a0a0a0]">
<Avatar class="flex-shrink-0 rounded-full" name={`${userProfile.value.username}'s Games`} />
{`${userProfile.value.username}'s Games`}
</span>
<span class="py-1 px-1 text-primary-500 [&>svg]:size-5 [&>svg]:hidden !w-max" >
<svg xmlns="http://www.w3.org/2000/svg" class="" viewBox="0 0 24 24"><path fill="currentColor" d="m10 13.6l5.9-5.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-6.6 6.6q-.3.3-.7.3t-.7-.3l-2.6-2.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275z" /></svg>
</span>
</Dropdown.RadioItem>
</Dropdown.RadioGroup>
<Dropdown.Separator class="w-full dark:bg-[#2e2e2e] bg-[#e8e8e8] border-0 h-[1px] my-1" />
<Dropdown.Group class="flex flex-col gap-1 w-full">
<Dropdown.Item
onClick$={() => isNewTeam.value = true}
class={cn("leading-none w-full text-sm items-center text-[#6f6f6f] dark:text-[#a0a0a0] hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none relative", "opacity-50 pointer-events-none !cursor-not-allowed")}
>
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14m-7-7h14" /></svg>
New Team
</span>
</Dropdown.Item>
<Dropdown.Item
// onClick$={() => isNewMember.value = true}
class={cn("leading-none w-full text-sm items-center text-[#6f6f6f] dark:text-[#a0a0a0] hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none relative", "opacity-50 pointer-events-none !cursor-not-allowed")}
>
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0-8 0m8 12h6m-3-3v6M6 21v-2a4 4 0 0 1 4-4h4" /></svg>
Send an invite
</span>
</Dropdown.Item>
<button
onPointerDown$={handlePointerDown}
onPointerUp$={handlePointerUp}
onPointerLeave$={handlePointerUp}
onKeyDown$={(e) => e.key === "Enter" && handlePointerDown()}
onKeyUp$={(e) => e.key === "Enter" && handlePointerUp()}
disabled={true}
class={cn("leading-none relative overflow-hidden transition-all duration-200 text-sm group items-center text-red-500 [&>*]:w-full [&>qwik-react]:absolute [&>qwik-react]:h-full [&>qwik-react]:left-0 hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none select-none w-full", "opacity-50 pointer-events-none !cursor-not-allowed")}>
<MotionComponent
client:load
class="absolute left-0 top-0 bottom-0 bg-red-500 opacity-50 w-full h-full rounded-md"
initial={{ scaleX: 0 }}
animate={{
scaleX: isHolding.value ? 1 : 0
}}
style={{
transformOrigin: 'left',
}}
transition={{
duration: isHolding.value ? 2 : 0.5,
ease: "linear"
}}
/>
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m19.5 5.5l-.402 6.506M4.5 5.5l.605 10.025c.154 2.567.232 3.85.874 4.774c.317.456.726.842 1.2 1.131c.671.41 1.502.533 2.821.57m10-7l-7 7m7 0l-7-7M3 5.5h18m-4.944 0l-.683-1.408c-.453-.936-.68-1.403-1.071-1.695a2 2 0 0 0-.275-.172C13.594 2 13.074 2 12.035 2c-1.066 0-1.599 0-2.04.234a2 2 0 0 0-.278.18c-.395.303-.616.788-1.058 1.757L8.053 5.5" color="currentColor" /></svg>
<span class="group-hover:hidden">Delete Team</span>
<span class="hidden group-hover:block">Hold to delete</span>
</span>
</button>
</Dropdown.Group>
</Dropdown.Popover>
</Dropdown.Root>) : (<div class="h-6 w-40 rounded-md bg-gray-200 dark:bg-gray-800 animate-pulse" />)}
{location.pathname == "/machine" &&
(<>
<hr class="dark:bg-gray-700/70 bg-gray-400/70 w-0.5 rounded-md mx-3 rotate-[16deg] h-7 border-none" />
{userProfile.value ? (
<div class="text-sm [&>svg:first-child]:size-5 rounded-full h-8 focus:bg-gray-300/70 dark:focus:bg-gray-700/70 focus:ring-[#8f8f8f] dark:focus:ring-[#707070] focus:ring-2 outline-none dark:text-gray-400 text-gray-600 gap-2 px-3 cursor-pointer inline-flex transition-all duration-150 items-center hover:bg-gray-300/70 dark:hover:bg-gray-700/70 ">
<span class="truncate shrink max-w-[20ch]">{`Steam Machine`}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M72.61 83.06a8 8 0 0 1 1.73-8.72l48-48a8 8 0 0 1 11.32 0l48 48A8 8 0 0 1 176 88H80a8 8 0 0 1-7.39-4.94M176 168H80a8 8 0 0 0-5.66 13.66l48 48a8 8 0 0 0 11.32 0l48-48A8 8 0 0 0 176 168" /></svg>
</div>
) : (<div class="h-6 w-40 rounded-md bg-gray-200 dark:bg-gray-800 animate-pulse" />)}
</>
)
}
</div>
</div>
<div class="gap-4 flex flex-row justify-center h-full items-center">
{userProfile.value ?
(<Dropdown.Root class="animate-fade-in opacity-0 transition-all duration-200 ease-in" onOpenChange$={onDialogOpen}>
<Dropdown.Trigger class="focus:bg-gray-300/70 dark:focus:bg-gray-700/70 focus:ring-[#8f8f8f] dark:focus:ring-[#707070] text-gray-600 dark:text-gray-400 [&>svg:first-child]:size-5 text-sm focus:ring-2 outline-none rounded-full transition-all flex items-center duration-150 select-none cursor-pointer hover:bg-gray-300/70 dark:hover:bg-gray-700/70 gap-1 px-3 h-8" >
{userProfile.value.avatarUrl ? (<img src={userProfile.value.avatarUrl} height={20} width={20} class="size-6 rounded-full" alt="Avatar" />) : (<Avatar name={`${userProfile.value.username}#${userProfile.value.discriminator}`} />)}
<span class="truncate shrink max-w-[20ch] sm:flex hidden">{`${userProfile.value.username}#${userProfile.value.discriminator}`}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:block hidden" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M72.61 83.06a8 8 0 0 1 1.73-8.72l48-48a8 8 0 0 1 11.32 0l48 48A8 8 0 0 1 176 88H80a8 8 0 0 1-7.39-4.94M176 168H80a8 8 0 0 0-5.66 13.66l48 48a8 8 0 0 0 11.32 0l48-48A8 8 0 0 0 176 168" /></svg>
</Dropdown.Trigger>
<Dropdown.Popover
class="bg-[hsla(0,0%,100%,.5)] dark:bg-[hsla(0,0%,100%,.026)] min-w-[160px] max-w-[240px] backdrop-blur-md rounded-lg py-1 px-2 border border-[#e8e8e8] dark:border-[#2e2e2e] [box-shadow:0_8px_30px_rgba(0,0,0,.12)]">
<Dropdown.Group class="flex flex-col gap-1">
<Dropdown.Item
onClick$={() => window.location.href = "mailto:feedback@nestri.io"}
class="leading-none text-sm items-center text-[#6f6f6f] dark:text-[#a0a0a0] hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none relative select-none "
>
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="currentColor" d="M22 8.5a6.5 6.5 0 0 0-11.626-3.993A9.5 9.5 0 0 1 19.5 14q0 .165-.006.33l.333.088a1.3 1.3 0 0 0 1.592-1.591l-.128-.476c-.103-.385-.04-.791.125-1.153A6.5 6.5 0 0 0 22 8.5" /><path fill="currentColor" fill-rule="evenodd" d="M18 14a8 8 0 0 1-11.45 7.22a1.67 1.67 0 0 0-1.15-.13l-1.227.329a1.3 1.3 0 0 1-1.591-1.592L2.91 18.6a1.67 1.67 0 0 0-.13-1.15A8 8 0 1 1 18 14M6.5 15a1 1 0 1 0 0-2a1 1 0 0 0 0 2m3.5 0a1 1 0 1 0 0-2a1 1 0 0 0 0 2m3.5 0a1 1 0 1 0 0-2a1 1 0 0 0 0 2" clip-rule="evenodd" /></svg>
Send Feedback
</span>
</Dropdown.Item>
{/* <button
onPointerDown$={handlePointerDown}
onPointerUp$={handlePointerUp}
onPointerLeave$={handlePointerUp}
onKeyDown$={(e) => e.key === "Enter" && handlePointerDown()}
onKeyUp$={(e) => e.key === "Enter" && handlePointerUp()}
class="leading-none relative overflow-hidden transition-all duration-200 text-sm group items-center text-red-500 [&>*]:w-full [&>qwik-react]:absolute [&>qwik-react]:h-full [&>qwik-react]:left-0 hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none select-none w-full">
<MotionComponent
client:load
class="absolute left-0 top-0 bottom-0 bg-red-500 opacity-50 w-full h-full rounded-md"
initial={{ scaleX: 0 }}
animate={{
scaleX: isHolding.value ? 1 : 0
}}
style={{
transformOrigin: 'left',
}}
transition={{
duration: isHolding.value ? 2 : 0.5,
ease: "linear"
}}
onAnimationComplete$={handleLogoutAnimationComplete}
/>
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><g fill="none"><path fill="currentColor" fill-rule="evenodd" d="M10.138 1.815A3 3 0 0 1 14 4.688v14.624a3 3 0 0 1-3.862 2.873l-6-1.8A3 3 0 0 1 2 17.512V6.488a3 3 0 0 1 2.138-2.873zM15 4a1 1 0 0 1 1-1h3a3 3 0 0 1 3 3v1a1 1 0 1 1-2 0V6a1 1 0 0 0-1-1h-3a1 1 0 0 1-1-1m6 12a1 1 0 0 1 1 1v1a3 3 0 0 1-3 3h-3a1 1 0 1 1 0-2h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1M9 11a1 1 0 1 0 0 2h.001a1 1 0 1 0 0-2z" clip-rule="evenodd" /><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 12h5m0 0l-2-2m2 2l-2 2" /></g></svg>
<span class="group-hover:hidden">Log out</span>
<span class="hidden group-hover:block">Hold to logout</span>
</span>
</button> */}
<button
onPointerDown$={handlePointerDown}
onPointerUp$={handlePointerUp}
onPointerLeave$={handlePointerUp}
onKeyDown$={(e) => e.key === "Enter" && handlePointerDown()}
onKeyUp$={(e) => e.key === "Enter" && handlePointerUp()}
class={cn("leading-none relative overflow-hidden transition-all duration-200 text-sm group items-center text-red-500 [&>*]:w-full [&>qwik-react]:absolute [&>qwik-react]:h-full [&>qwik-react]:left-0 hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none select-none w-full")}>
<MotionComponent
client:load
class="absolute left-0 top-0 bottom-0 bg-red-500 opacity-50 w-full h-full rounded-md"
initial={{ scaleX: 0 }}
animate={{
scaleX: isHolding.value ? 1 : 0
}}
style={{
transformOrigin: 'left',
}}
transition={{
duration: isHolding.value ? 2 : 0.5,
ease: "linear"
}}
onAnimationComplete$={handleLogoutAnimationComplete}
/>
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><g fill="none"><path fill="currentColor" fill-rule="evenodd" d="M10.138 1.815A3 3 0 0 1 14 4.688v14.624a3 3 0 0 1-3.862 2.873l-6-1.8A3 3 0 0 1 2 17.512V6.488a3 3 0 0 1 2.138-2.873zM15 4a1 1 0 0 1 1-1h3a3 3 0 0 1 3 3v1a1 1 0 1 1-2 0V6a1 1 0 0 0-1-1h-3a1 1 0 0 1-1-1m6 12a1 1 0 0 1 1 1v1a3 3 0 0 1-3 3h-3a1 1 0 1 1 0-2h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1M9 11a1 1 0 1 0 0 2h.001a1 1 0 1 0 0-2z" clip-rule="evenodd" /><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 12h5m0 0l-2-2m2 2l-2 2" /></g></svg>
<span class="group-hover:hidden">Log out</span>
<span class="hidden group-hover:block">Hold to logout</span>
</span>
</button>
</Dropdown.Group>
{/* <Dropdown.Separator class="w-full dark:bg-[#2e2e2e] bg-[#e8e8e8] border-0 h-[1px] my-1" />
<Dropdown.Group class="flex flex-col gap-1">
<Dropdown.Item
class="leading-none transition-all duration-200 text-sm group items-center text-red-500 hover:text-[#171717] dark:hover:text-[#ededed] hover:bg-[rgba(0,0,0,.071)] dark:hover:bg-[hsla(0,0%,100%,.077)] flex px-2 gap-2 h-8 rounded-md cursor-pointer outline-none relative select-none "
>
<span class="w-full max-w-[20ch] flex items-center gap-2 truncate overflow-visible [&>svg]:size-5 ">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 22H6.59c-1.545 0-2.774-.752-3.877-1.803c-2.26-2.153 1.45-3.873 2.865-4.715a10.67 10.67 0 0 1 7.922-1.187m3-7.795a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0M16 22l3-3m0 0l3-3m-3 3l-3-3m3 3l3 3" color="currentColor" /></svg>
<span class="group-hover:hidden">Leave Team</span>
<span class="hidden group-hover:block">Hold to leave</span>
</span>
</Dropdown.Item>
</Dropdown.Group> */}
</Dropdown.Popover>
</Dropdown.Root>) :
(<div class="flex gap-2 justify-center items-center animate-pulse " >
<div class="h-6 w-20 rounded-md bg-gray-200 dark:bg-gray-800 right-4" />
<div class="size-8 rounded-full bg-gray-200 dark:bg-gray-800 right-4" />
</div>)
}
</div>
</nav>
<Modal.Root bind:show={isNewTeam} class="w-full">
<Modal.Panel
class="dark:bg-black bg-white [box-shadow:0_8px_30px_rgba(0,0,0,.12)]
dark:backdrop:bg-[#0009] backdrop:bg-[#b3b5b799] backdrop:backdrop-grayscale-[.3] max-h-[75vh] rounded-xl
backdrop-blur-md modal max-w-[400px] w-full border dark:border-gray-800 border-gray-200">
<form preventdefault:submit onSubmit$={handleAddTeam}>
<main class="size-full flex flex-col relative py-4 px-5">
<div class="dark:text-white text-black">
<h3 class="font-semibold text-2xl tracking-tight mb-1 font-title">Create a team</h3>
<div class="text-sm dark:text-gray-200/70 text-gray-800/70" >
Continue to start playing with on Pro with increased usage, additional security features, and support
</div>
</div>
<div class="mt-3 flex flex-col gap-3" >
<div>
<label for="name" class="text-sm dark:text-gray-200 text-gray-800 pb-2 pt-1" >
Team Name
</label>
<input
//@ts-expect-error
onInput$={(e) => newTeamName.value = e.target!.value}
required value={newTeamName.value} id="name" type="text" placeholder="Enter team name" class="[transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] w-full bg-transparent px-2 py-3 h-10 border text-black dark:text-white dark:border-gray-700/70 border-gray-300/70 rounded-md text-sm outline-none leading-none focus:ring-gray-300 dark:focus:ring-gray-700 focus:ring-2" />
</div>
</div>
</main>
<footer class="dark:text-gray-200/70 text-gray-800/70 dark:bg-gray-900 bg-gray-100 ring-1 ring-gray-200 dark:ring-gray-800 select-none flex gap-2 items-center justify-between w-full bottom-0 left-0 py-3 px-5 text-sm leading-none">
<Modal.Close class="rounded-lg [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] py-3 px-4 hover:bg-gray-200 dark:hover:bg-gray-800 flex items-center justify-center">
Cancel
</Modal.Close>
<button
type="submit"
class="flex items-center justify-center gap-2 border-2 border-gray-300 dark:border-gray-700 rounded-lg bg-gray-200 dark:bg-gray-800 py-3 px-4 hover:bg-gray-300 dark:hover:bg-gray-700 [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)]" >
Continue
</button>
</footer>
</form>
</Modal.Panel>
</Modal.Root >
<Modal.Root bind:show={isNewMember} class="w-full">
<Modal.Panel
class="dark:bg-black bg-white [box-shadow:0_8px_30px_rgba(0,0,0,.12)]
dark:backdrop:bg-[#0009] backdrop:bg-[#b3b5b799] backdrop:backdrop-grayscale-[.3] max-h-[75vh] rounded-xl
backdrop-blur-md modal max-w-[400px] w-full border dark:border-gray-800 border-gray-200">
<form preventdefault:submit onSubmit$={handleInvite}>
<main class="size-full flex flex-col relative py-4 px-5">
<div class="dark:text-white text-black">
<h3 class="font-semibold text-2xl tracking-tight mb-1 font-title">Send an invite</h3>
<div class="text-sm dark:text-gray-200/70 text-gray-800/70" >
Friends will receive an email allowing them to join this team
</div>
</div>
<div class="mt-3 flex flex-col gap-3" >
<div>
<label for="name" class="text-sm dark:text-gray-200 text-gray-800 pb-2 pt-1" >
Name
</label>
<input
value={inviteName.value}
//@ts-expect-error
onInput$={(e) => inviteName.value = e.target!.value}
id="name" type="text" placeholder="Jane Doe" class="[transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] w-full bg-transparent px-2 py-3 h-10 border text-black dark:text-white dark:border-gray-700/70 border-gray-300/70 rounded-md text-sm outline-none leading-none focus:ring-gray-300 dark:focus:ring-gray-700 focus:ring-2" />
</div>
<div>
<label for="email" class="text-sm dark:text-gray-200 text-gray-800 pb-2 pt-1" >
Email
</label>
<input
value={inviteEmail.value}
//@ts-expect-error
onInput$={(e) => inviteEmail.value = e.target!.value}
id="email" type="email" placeholder="jane@doe.com" class="[transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] w-full px-2 bg-transparent py-3 h-10 border text-black dark:text-white dark:border-gray-700/70 border-gray-300/70 rounded-md text-sm outline-none leading-none focus:ring-gray-300 dark:focus:ring-gray-700 focus:ring-2" />
</div>
</div>
</main>
<footer class="dark:text-gray-200/70 text-gray-800/70 dark:bg-gray-900 bg-gray-100 ring-1 ring-gray-200 dark:ring-gray-800 select-none flex gap-2 items-center justify-between w-full bottom-0 left-0 py-3 px-5 text-sm leading-none">
<Modal.Close class="rounded-lg [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)] py-3 px-4 hover:bg-gray-200 dark:hover:bg-gray-800 flex items-center justify-center">
Cancel
</Modal.Close>
<button type="submit" class="flex items-center justify-center gap-2 border-2 border-gray-300 dark:border-gray-700 rounded-lg bg-gray-200 dark:bg-gray-800 py-3 px-4 hover:bg-gray-300 dark:hover:bg-gray-700 [transition:all_0.3s_cubic-bezier(0.4,0,0.2,1)]" >
Send an invite
</button>
</footer>
</form>
</Modal.Panel>
</Modal.Root>
</>
)
})

View File

@@ -1,107 +0,0 @@
/* eslint-disable qwik/no-use-visible-task */
import { cn } from '@nestri/ui/design';
import { component$, useSignal, useTask$, useStyles$, useVisibleTask$ } from '@builder.io/qwik';
interface ImageLoaderProps {
src: string;
alt: string;
width?: number;
height?: number;
class?: string;
}
export const BasicImageLoader = component$((props: ImageLoaderProps) => {
const imageLoaded = useSignal(false);
const hasError = useSignal(false);
const imgRef = useSignal<HTMLImageElement>();
useStyles$(`
@keyframes gradientShift {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.loading-animation {
animation: gradientShift 1.5s infinite linear;
background-size: 200% 100%;
}
`);
useTask$(({ track }) => {
track(() => props.src);
imageLoaded.value = false;
hasError.value = false;
});
useVisibleTask$(async ({ cleanup }) => {
const img = imgRef.value;
if (!img) return;
// const imageData = await imageGetter();
const checkImageLoaded = async () => {
if (img.complete && img.naturalHeight !== 0) {
imageLoaded.value = true;
}
};
// Check immediately in case the image is already loaded
await checkImageLoaded();
// Set up event listeners
const loadHandler = async () => {
imageLoaded.value = true;
};
const errorHandler = () => {
hasError.value = true;
};
img.addEventListener('load', loadHandler);
img.addEventListener('error', errorHandler);
// Use MutationObserver to detect src changes
const observer = new MutationObserver(checkImageLoaded);
observer.observe(img, { attributes: true, attributeFilter: ['src'] });
cleanup(() => {
img.removeEventListener('load', loadHandler);
img.removeEventListener('error', errorHandler);
observer.disconnect();
});
});
return (
<>
{!imageLoaded.value && !hasError.value && (
<div
class={
cn(
" w-full h-full loading-animation bg-gradient-to-r from-gray-200 via-gray-300 to-gray-200 dark:from-gray-800 dark:via-gray-900 dark:to-gray-800",
props.class
)
}
/>
)}
<img
src={props.src}
draggable={false}
alt={props.alt}
width={props.width}
// crossOrigin='anonymous'
height={props.height}
ref={imgRef}
class={{
'z-[5] relative': imageLoaded.value,
'hidden': !imageLoaded.value && !hasError.value,
'w-full h-full': imageLoaded.value,
'w-16 h-16 text-red-500': hasError.value,
[props.class || '']: !!props.class,
}}
/>
{hasError.value && (
<p class="text-red-500 text-sm" >
Error loading image
</p>
)}
</>
);
});

View File

@@ -1,195 +0,0 @@
/* eslint-disable qwik/no-use-visible-task */
import { cn } from '@nestri/ui/design';
import { component$, useSignal, useTask$, useStyles$, useVisibleTask$, $ } from '@builder.io/qwik';
interface ImageLoaderProps {
src: string;
alt: string;
width?: number;
height?: number;
class?: string;
}
interface Color {
r: number;
g: number;
b: number;
}
export const ImageLoader = component$((props: ImageLoaderProps) => {
const imageLoaded = useSignal(false);
const hasError = useSignal(false);
const imgRef = useSignal<HTMLImageElement>();
const shadowColor = useSignal<string>('');
const imageUrl = `http://localhost:8787/image/cover/${props.src}.avif?width=${props.width}&height=${props.height}&quality=100`;
useStyles$(`
@keyframes gradientShift {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.loading-animation {
animation: gradientShift 1.5s infinite linear;
background-size: 200% 100%;
}
`);
useTask$(({ track }) => {
track(() => props.src);
imageLoaded.value = false;
hasError.value = false;
shadowColor.value = '';
});
const analyzeImage = $((img: HTMLImageElement) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) return;
// img.crossOrigin = "anonymous"
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const sampleSize = 20;
const colors: Color[] = [];
for (let x = 0; x < sampleSize; x++) {
for (let y = 0; y < sampleSize; y++) {
const px = Math.floor((x / sampleSize) * canvas.width);
const py = Math.floor((y / sampleSize) * canvas.height);
const pixelData = ctx.getImageData(px, py, 1, 1).data;
colors.push({ r: pixelData[0], g: pixelData[1], b: pixelData[2] });
}
}
// Function to calculate color saturation
const calculateSaturation = (color: Color) => {
const max = Math.max(color.r, color.g, color.b);
const min = Math.min(color.r, color.g, color.b);
return max === 0 ? 0 : (max - min) / max;
};
// Function to calculate color brightness
const calculateBrightness = (color: Color) => {
return (color.r * 299 + color.g * 587 + color.b * 114) / 1000;
};
// Find the color with high saturation and brightness
const vibrantColor = colors.reduce((mostVibrant, color) => {
const saturation = calculateSaturation(color);
const brightness = calculateBrightness(color);
const currentSaturation = calculateSaturation(mostVibrant);
const currentBrightness = calculateBrightness(mostVibrant);
// Prefer colors with high saturation and high brightness
if (saturation > 0.5 && brightness > 100 && (saturation + brightness * 0.01) > (currentSaturation + currentBrightness * 0.01)) {
return color;
}
return mostVibrant;
}, colors[0]);
// Increase the brightness of the selected color
const enhancedColor = {
r: Math.min(255, vibrantColor.r * 1.2),
g: Math.min(255, vibrantColor.g * 1.2),
b: Math.min(255, vibrantColor.b * 1.2)
};
shadowColor.value = `rgb(${Math.round(enhancedColor.r)},${Math.round(enhancedColor.g)},${Math.round(enhancedColor.b)})`;
});
useVisibleTask$(async ({ cleanup }) => {
const img = imgRef.value;
if (!img) return;
// const imageData = await imageGetter();
const checkImageLoaded = async () => {
if (img.complete && img.naturalHeight !== 0) {
imageLoaded.value = true;
await analyzeImage(img);
}
};
// Check immediately in case the image is already loaded
await checkImageLoaded();
// Set up event listeners
const loadHandler = async () => {
imageLoaded.value = true;
await analyzeImage(img);
};
const errorHandler = () => {
hasError.value = true;
};
img.addEventListener('load', loadHandler);
img.addEventListener('error', errorHandler);
// Use MutationObserver to detect src changes
const observer = new MutationObserver(checkImageLoaded);
observer.observe(img, { attributes: true, attributeFilter: ['src'] });
cleanup(() => {
img.removeEventListener('load', loadHandler);
img.removeEventListener('error', errorHandler);
observer.disconnect();
});
});
return (
<div
style={{
width: props.width ? `${props.width}px` : '100%',
height: props.height ? `${props.height}px` : 'auto',
"--shadow-color": shadowColor.value ? shadowColor.value : 'none',
transition: 'box-shadow 0.3s ease-in-out',
aspectRatio: props.width && props.height ? `${props.width} / ${props.height}` : 'auto'
}}
class={cn("relative overflow-hidden", props.class, "dark:shadow-[var(--shadow-color)]")}>
{!imageLoaded.value && !hasError.value && (
<div
class={cn("relative x-[20] inset-0 h-full loading-animation bg-gradient-to-r from-gray-200 via-gray-300 to-gray-200 dark:from-gray-800 dark:via-gray-900 dark:to-gray-800", props.class)}
style={{
height: props.height,
aspectRatio: props.width && props.height ? `${props.width} / ${props.height}` : 'auto'
}}
/>
)}
{/* {imageLoaded.value && (
<div
class="dark:block hidden k w-full h-full absolute z-0 inset-0 blur-lg left-0 right-0 bottom-0 top-0 scale-105 opacity-50"
style={{
backgroundImage: `url(${imageUrl})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
)} */}
<img
src={imageUrl}
draggable={false}
alt={props.alt}
width={props.width}
height={props.height}
ref={imgRef}
style={{
transition: 'box-shadow 0.3s ease-in-out'
}}
class={{
'z-[5] relative': imageLoaded.value,
'hidden': !imageLoaded.value && !hasError.value,
'w-full h-full': imageLoaded.value,
'w-16 h-16 text-red-500': hasError.value,
[props.class || '']: !!props.class,
'dark:shadow-[var(--shadow-color)]': shadowColor.value
}}
/>
{hasError.value && (
<p class="text-red-500 text-sm" >
Error loading image
</p>
)}
</div>
);
});

View File

@@ -1,77 +0,0 @@
import { $, useVisibleTask$ } from '@builder.io/qwik';
export const setupImageLoader = $(() => {
const imageCache = new Map();
const loadImage = async (img: HTMLImageElement) => {
const src = img.getAttribute('data-src');
console.log('src', src);
if (!src) return;
// Check if the image is already in the cache
if (imageCache.has(src)) {
img.src = imageCache.get(src);
img.classList.add('loaded');
return;
}
// Check if the image is in the browser's cache
const cache = await caches.open('image-cache');
console.log('cache', cache);
const cachedResponse = await cache.match(src);
if (cachedResponse) {
const blob = await cachedResponse.blob();
const objectURL = URL.createObjectURL(blob);
img.src = objectURL;
imageCache.set(src, objectURL);
img.classList.add('loaded');
} else {
// If not in cache, load the image
try {
const response = await fetch(src);
const blob = await response.blob();
const objectURL = URL.createObjectURL(blob);
img.src = objectURL;
imageCache.set(src, objectURL);
img.classList.add('loaded');
// Cache the image for future use
cache.put(src, new Response(blob));
} catch (error) {
console.error('Error loading image:', error);
img.classList.add('error');
}
}
};
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadImage(entry.target as HTMLImageElement);
observer.unobserve(entry.target);
}
});
},
{ rootMargin: '50px' }
);
const setupImages = () => {
const images = document.querySelectorAll('img[data-src]');
images.forEach((img) => {
observer.observe(img);
});
};
return setupImages;
});
export const useImageLoader = () => {
// eslint-disable-next-line qwik/no-use-visible-task
useVisibleTask$(async () => {
const setup = await setupImageLoader();
setup();
});
};

View File

@@ -1,2 +0,0 @@
export * from "./image-loader"
export * from "./basic-image-loader"

View File

@@ -1,23 +0,0 @@
export * from "./nav-progress"
export * from "./nav-bar"
export * from "./fonts"
export * from "./input"
export * from "./home/nav-bar"
export * from "./home/machines"
export * from "./home/games"
export * from "./home/friends"
export * from "./game-card"
export * from "./tooltip"
export * from "./react/footer"
export * from "./card"
export * from "./router-head"
export * from "./team-counter"
export * from "./constants"
export * from "./svg"
export * as auth from "./popup"
export * as Modal from "./modal"
export { default as Book } from "./book"
export { default as Portal } from "./portal"
export { default as Avatar } from "./avatar"
export { default as SimpleFooter } from "./simple-footer"
export { default as GameStoreButton } from "./game-store"

View File

@@ -1,35 +0,0 @@
import {
form,
cn,
type InputProps as InputVariants,
} from "./design"
import { type QwikIntrinsicElements, component$ } from '@builder.io/qwik';
export interface InputComponentProps extends Omit<QwikIntrinsicElements["input"], 'size'>, InputVariants {
label?: string;
includeName?: string;
labelFor?: string;
description?: string;
}
export const Input = component$(({ labelFor,description, label, variant = "mixed", fancy = false, size = "md", includeName, class: className, ...props }: InputComponentProps) => {
const { input } = form();
return (
<div class="text-start w-full gap-2 flex flex-col" >{
label && labelFor && <label for={labelFor} class='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'>{label}</label>
}
{description && <p class='text-[0.8rem]'>{description}</p>}
<div class="flex flex-row w-full h-max relative" >
{includeName && <p class="absolute top-1/2 -translate-y-1/2 left-3 text-gray-950 dark:text-gray-50" >{includeName}&nbsp;</p>}
<input
id={labelFor}
class={input({ variant, fancy, size, className: cn("rounded-md w-full data-[invalid]:animate-shake", className as any) })}
{...props}
/>
</div>
</div>
)
})
// export const Input = qwikify$(ReactInput)

View File

@@ -1,13 +0,0 @@
//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';

View File

@@ -1,16 +0,0 @@
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>
);
});

View File

@@ -1,12 +0,0 @@
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>
);
});

View File

@@ -1,13 +0,0 @@
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;
};

View File

@@ -1,16 +0,0 @@
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>
);
});

View File

@@ -1,12 +0,0 @@
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>
);
});

View File

@@ -1,12 +0,0 @@
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>
);
});

View File

@@ -1,124 +0,0 @@
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>
);
});

View File

@@ -1,51 +0,0 @@
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>
);
});

View File

@@ -1,16 +0,0 @@
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>
);
});

View File

@@ -1,23 +0,0 @@
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>
);
});

View File

@@ -1,9 +0,0 @@
@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;
}
}

View File

@@ -1,134 +0,0 @@
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,
};
}

View File

@@ -1,53 +0,0 @@
import { $, component$, useOnDocument, useSignal } from "@builder.io/qwik";
import { Link, useLocation } from "@builder.io/qwik-city";
import { buttonVariants, cn } from "./design";
const navLinks = [
{
name: "About Us",
href: "/about"
},
{
name: "Pricing",
href: "/pricing"
},
{
name: "Login",
href: "/auth/login"
}
]
export const NavBar = component$(() => {
const location = useLocation()
const hasScrolled = useSignal(false);
useOnDocument(
'scroll',
$(() => {
hasScrolled.value = window.scrollY > 0;
})
);
return (
<nav class={cn("w-full sticky top-0 z-50 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 max-w-full overflow-hidden", hasScrolled.value && "shadow-[0_2px_20px_1px] shadow-gray-300 dark:shadow-gray-700")} >
<div class="px-4 mx-auto flex max-w-[600px] items-center sm:border-b-2 dark:border-gray-600 border-gray-400" >
<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-bricolage font-semibold" >
Nestri
</h1>
</Link>
<ul class="ml-0 -mr-4 flex font-medium m-4 font-mona tracking-tight flex-1 gap-1 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} prefetch={linkItem.name === "Login" && false} 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>
))}
</ul>
</div>
</nav>
)
})

View File

@@ -1,43 +0,0 @@
import { component$, useStyles$, useVisibleTask$ } from '@builder.io/qwik'
import { useLocation } from '@builder.io/qwik-city'
import type { NProgressOptions } from 'nprogress'
import nProgress from 'nprogress'
import nProgressStyles from 'nprogress/nprogress.css?inline'
import nPrStyles from './npr.css?inline'
interface Props {
options?: Partial<NProgressOptions> &
Partial<{
color: string
height: string
}>
}
export const NavProgress = component$(({ options = {} }: Props) => {
// const CSS_VAR_PREFIX = '--nav-progress-'
useStyles$(nProgressStyles + nPrStyles)
nProgress.configure({ showSpinner: false, ...options })
const location = useLocation()
// eslint-disable-next-line qwik/no-use-visible-task
useVisibleTask$(({ track }) => {
const isNavigating = track(() => location.isNavigating)
if (isNavigating) nProgress.start()
else nProgress.done()
})
// return (
// <style
// dangerouslySetInnerHTML={`
// :root {
// ${options.color ? `${CSS_VAR_PREFIX}color: ${options.color};` : ''}
// ${options.height ? `${CSS_VAR_PREFIX}height: ${options.height};` : ''}
// }
// `}
// ></style>
// )
return <div data-name="progress" />
})

View File

@@ -1,53 +0,0 @@
#nprogress .bar {
background: theme("colors.primary.500");
height: 2px;
z-index: 80;
}
/* Fancy blur effect */
#nprogress .peg {
box-shadow: 0 0 10px theme("colors.primary.500"),
0 0 5px theme("colors.primary.500");
z-index: 80;
}
#nprogress .spinner-icon {
border-top-color: theme("colors.primary.500");
border-left-color: theme("colors.primary.500");
display: none;
}
html.dark #nprogress .bar {
background: theme("colors.primary.500");
height: 2px;
}
html.dark #nprogress .peg {
box-shadow: 0 0 10px theme("colors.primary.500"),
0 0 5px theme("colors.primary.500");
}
html.dark .spinner-icon {
border-top-color: theme("colors.primary.500");
border-left-color: theme("colors.primary.500");
display: none;
}
@media (prefers-color-scheme: dark) {
#nprogress .bar {
background: theme("colors.primary.500");
height: 2px;
}
#nprogress .peg {
box-shadow: 0 0 10px theme("colors.primary.500"),
0 0 5px theme("colors.primary.500");
}
.spinner-icon {
border-top-color: theme("colors.primary.500");
border-left-color: theme("colors.primary.500");
display: none;
}
}

View File

@@ -1,67 +0,0 @@
export const openWindow = (url: string): Promise<void> => {
return new Promise((resolve) => {
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
// Open in a new tab for mobile devices
const newTab = window.open(url, '_blank');
localStorage.removeItem("auth_success")
if (newTab) {
const pollTimer = setInterval(() => {
if (newTab.closed) {
clearInterval(pollTimer);
resolve();
return;
} else if (localStorage.getItem("auth_success")) {
clearInterval(pollTimer);
newTab.location.href = 'about:blank';
setTimeout(() => {
newTab.close();
window.focus();
//TODO: Navigate and start onboarding
}, 50);
localStorage.removeItem("auth_success")
resolve();
return;
}
}, 300);
} else {
resolve(); // Resolve if popup couldn't be opened
}
} else {
// Open in a popup for desktop devices
const width = 600;
const height = 600;
const top = window.top!.outerHeight / 2 + window.top!.screenY - (height / 2);
const left = window.top!.outerWidth / 2 + window.top!.screenX - (width / 2);
const popup = window.open(
url,
'Nestri Auth Popup',
`width=${width},height=${height},left=${left},top=${top},toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no,`
);
if (popup) {
const pollTimer = setInterval(() => {
if (popup.closed) {
clearInterval(pollTimer);
localStorage.removeItem("auth_success")
resolve();
return;
}
}, 300);
} else {
resolve(); // Resolve if popup couldn't be opened
}
}
});
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1022 KiB

View File

@@ -1,588 +0,0 @@
{"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$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

View File

@@ -1,237 +0,0 @@
{"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$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -1,237 +0,0 @@
{"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$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1,237 +0,0 @@
{"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$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,71 +0,0 @@
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 inset-0 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 inset-0" height={100} width={100} ref={buttonRef} />
<canvas class="relative w-full h-full z-[5] inset-0" height={100} width={100} ref={iconRef} />
</button>
)
})

View File

@@ -1,309 +0,0 @@
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;

View File

@@ -1,32 +0,0 @@
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
import { type MotionProps, AnimatePresence } from 'framer-motion';
import React, { type ReactNode } from 'react';
interface ReactAnimateComponentProps extends MotionProps {
// as?: keyof JSX.IntrinsicElements;
children?: ReactNode;
// class?: string;
// id: string;
}
export const ReactAnimateComponent = ({
// as = 'div',
// id,
children,
// class: className,
// ...motionProps
}: ReactAnimateComponentProps) => {
// const MotionTag = motion[as as keyof typeof motion] as React.ComponentType<any>;
return (
<AnimatePresence mode='wait'>
{children}
{/* <MotionTag id={id} className={className} {...(motionProps as any)}>
</MotionTag> */}
</AnimatePresence>
);
};
export const AnimateComponent = qwikify$(ReactAnimateComponent);

View File

@@ -1,147 +0,0 @@
/** @jsxImportSource react */
import * as React from "react"
import { motion } from "framer-motion"
import { qwikify$ } from "@builder.io/qwik-react";
import { cn, buttonIcon, buttonVariants, type ButtonVariantProps, type ButtonVariantIconProps } from "@nestri/ui/design";
import {cloneElement,} from "./utils"
export interface ButtonRootProps extends React.HTMLAttributes<HTMLButtonElement | HTMLAnchorElement>, ButtonVariantProps {
class?: string;
children?: React.ReactElement;
onClick?: () => void | Promise<void>;
setIsLoading?: (v: boolean) => void;
isLoading: boolean;
disabled?: boolean;
loadingTime?: number;
href?: string;
}
export interface ButtonIconProps extends React.HTMLAttributes<HTMLElement>, ButtonVariantIconProps {
isLoading: boolean;
height?: number;
width?: number;
class?:string;
}
export interface ButtonLabelProps extends React.HTMLAttributes<HTMLElement>, ButtonVariantIconProps {
loadingText?: string;
class?: string;
isLoading: boolean;
}
export const Icon: React.FC<ButtonIconProps> = (({
class:className,
children,
size = "md",
isLoading,
type = "leading",
height = 20,
width = 20,
}) => {
if (isLoading) {
return (
<motion.svg
height={height}
width={width}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
// className="h-4 w-4 fill-white"
fill="currentColor"
animate={{ rotate: 1080 }}
transition={{
repeat: 1 / 0,
duration: 1,
ease: [.1, .21, .355, .68],
repeatType: "loop"
}}
className={buttonIcon({ size, type, className })}
>
<path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"></path>
<path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"></path>
</motion.svg>
)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (!isLoading && children) {
return cloneElement(children as React.ReactElement, buttonIcon({ size, type, className }))
}
})
export const Label = React.forwardRef<HTMLElement, ButtonLabelProps>(({
class:className,
children,
loadingText,
isLoading,
...props
}, forwardedRef) => {
return (
<span className={className} {...props} ref={forwardedRef}>{isLoading && loadingText ? loadingText : children}</span>
)
})
const Root = React.forwardRef<HTMLButtonElement & HTMLAnchorElement, ButtonRootProps>(({
class: className,
href,
disabled = false,
loadingTime,
intent = "primary",
variant = "solid",
size = "md",
isLoading,
setIsLoading,
onClick,
children,
...props }, forwardedRef) => {
const handleClick = async () => {
if (setIsLoading) {
if (!isLoading && !disabled) {
setIsLoading(true);
if (onClick) {
await onClick();
} else {
// Simulate an async operation
await new Promise(resolve => setTimeout(resolve, loadingTime));
}
setIsLoading(false);
}
}
};
const Component = href ? 'a' : 'button';
const iconOnly = React.Children.toArray(children).some(child =>
React.isValidElement(child) && child.type === Icon && child.props.type === 'only'
);
const buttonSize = iconOnly ? 'iconOnlyButtonSize' : 'size';
return (
<Component
ref={forwardedRef}
onClick={handleClick}
disabled={isLoading || disabled}
className={cn(buttonVariants[variant as keyof typeof buttonVariants]({ intent, [buttonSize]: size, className }))}
{...props}
>
{children}
</Component>
)
})
export type ButtonRoot = typeof Root;
export type ButtonIcon = typeof Icon;
export type ButtonLabel = typeof Label;
Root.displayName = 'Root';
Icon.displayName = "Icon";
Label.displayName = "Label";
export const Button = {
Root: qwikify$(Root),
Icon: qwikify$(Icon),
Label: qwikify$(Label)
}
//The ✔️ SVG is here:
{/* <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 shrink-0 fill-white"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1ZM17.2071 9.70711C17.5976 9.31658 17.5976 8.68342 17.2071 8.29289C16.8166 7.90237 16.1834 7.90237 15.7929 8.29289L10.5 13.5858L8.20711 11.2929C7.81658 10.9024 7.18342 10.9024 6.79289 11.2929C6.40237 11.6834 6.40237 12.3166 6.79289 12.7071L9.79289 15.7071C10.1834 16.0976 10.8166 16.0976 11.2071 15.7071L17.2071 9.70711Z"></path></svg> */ }

View File

@@ -1,112 +0,0 @@
/** @jsxImportSource react */
import React from 'react'
import { motion } from 'framer-motion'
import { cn } from "@nestri/ui/design"
import { qwikify$ } from '@builder.io/qwik-react';
export const CursorSVG = ({ flip }: { flip?: boolean }) => (
<svg fill="none" height="18"
className={cn(flip ? 'scale-x-[-1]' : '', 'mb-9')}
viewBox="0 0 17 18" width="17">
<path
d="M15.5036 3.11002L12.5357 15.4055C12.2666 16.5204 10.7637 16.7146 10.22 15.7049L7.4763 10.6094L2.00376 8.65488C0.915938 8.26638 0.891983 6.73663 1.96711 6.31426L13.8314 1.65328C14.7729 1.28341 15.741 2.12672 15.5036 3.11002ZM7.56678 10.6417L7.56645 10.6416C7.56656 10.6416 7.56667 10.6416 7.56678 10.6417L7.65087 10.4062L7.56678 10.6417Z"
fill="currentColor"
style={{
stroke: `var(--cursor-color)`,
fill: `var(--cursor-color-light)`,
}}
stroke="currentColor"
// className={cn(color ? `stroke-${color}-400 text-${color}-500` : 'stroke-primary-400 text-primary-500')}
strokeWidth="1.5"
/>
</svg>
)
type CursorProps = { class?: string; text: string, color?: string, flip?: boolean }
const hexToRGBA = (hex: string, alpha: number = 1) => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
export const ReactCursor: React.FC<CursorProps> = ({
class: className,
text,
color = "#3B82F6",
flip = false,
}) => {
const [randomVariant, setRandomVariant] = React.useState({});
React.useEffect(() => {
const generateRandomVariant = () => {
const randomX = Math.random() * 40 - 20; // Random value between -20 and 20
const randomY = Math.random() * 40 - 20; // Random value between -40 and 40
const randomDuration = 3 + Math.random() * 7 ; // Random duration between 3 and 5 seconds
return {
animate: {
translateX: [0, randomX, 0],
translateY: [0, randomY, 0],
},
transition: {
duration: randomDuration,
repeat: Infinity,
repeatType: "reverse" as const,
ease: "easeInOut",
},
};
};
setRandomVariant(generateRandomVariant());
}, []);
const cursorElement = <CursorSVG flip={flip} />;
const textElement = (
<div
style={{
backgroundColor: `var(--cursor-color)`,
borderColor: `var(--cursor-color-dark)`,
}}
className={cn(
'w-fit rounded-full py-1 px-2 text-white',
)}
>
{text}
</div>
);
const colorStyles = {
'--cursor-color': color,
'--cursor-color-light': hexToRGBA(color, 0.7),
'--cursor-color-dark': hexToRGBA(color, 0.8),
} as React.CSSProperties;
return (
<motion.div
initial={{ translateX: 0, translateY: 0 }}
//
{...randomVariant}
style={colorStyles}
className="flex items-center"
>
<div className={cn('flex items-center', className)}>
{flip ? (
<>
{cursorElement}
{textElement}
</>
) : (
<>
{textElement}
{cursorElement}
</>
)}
</div>
</motion.div>
);
}
export const Cursor = qwikify$(ReactCursor)

View File

@@ -1,58 +0,0 @@
/** @jsxImportSource react */
import React from "react"
import {
display,
type DisplayProps as DisplayVariants,
type TextAlignProp,
type TextWeightProp
} from "@nestri/ui/design"
import * as ReactBalancer from "react-wrap-balancer"
import { cn } from "@nestri/ui/design"
import { qwikify$ } from "@builder.io/qwik-react"
type DisplaySize = DisplayVariants["size"]
type DisplaySizeProp = DisplaySize | {
initial?: DisplaySize,
sm?: DisplaySize,
md?: DisplaySize,
lg?: DisplaySize,
xl?: DisplaySize,
xxl?: DisplaySize,
}
export interface DisplayProps extends React.HTMLAttributes<HTMLHeadingElement> {
as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "div" | "span",
className?: string,
size?: DisplaySizeProp;
align?: TextAlignProp;
weight?: TextWeightProp
}
export const ReactDisplay = ({
size,
as = "h1",
weight,
align,
children,
className,
...props
}: DisplayProps) => {
const DisplayElement = as
return (
<DisplayElement className={display({
size,
weight,
align,
className: cn("font-basement font-extrabold", className)
})} {...props}>
<ReactBalancer.Balancer>
{children}
</ReactBalancer.Balancer>
</DisplayElement>
)
}
ReactDisplay.displayName = "Display"
export const Display = qwikify$(ReactDisplay)

View File

@@ -1,127 +0,0 @@
/** @jsxImportSource react */
// import { FooterBanner } from "../footer-banner";
import { motion } from "framer-motion"
import { ReactDisplay } from "./display"
import { qwikify$ } from "@builder.io/qwik-react";
const transition = {
type: "spring",
stiffness: 100,
damping: 15,
restDelta: 0.001,
duration: 0.01,
}
type Props = {
children?: React.ReactNode;
}
export function ReactFooter({ children }: Props) {
return (
<>
<footer className="flex justify-center flex-col items-center w-screen py-20 sm:pb-0 [&>*]:w-full px-3">
<section className="mx-auto flex flex-col justify-center items-center max-w-[600px] pt-20">
<motion.img
initial={{
opacity: 0,
y: 120
}}
whileInView={{
y: 0,
opacity: 1
}}
viewport={{ once: true }}
transition={{
...transition
}}
src="/logo.webp" alt="Nestri Logo" height={80} width={80} draggable={false} className="w-[50px] md:w-[80px] aspect-[90/69] select-none" />
<div className="my-4 sm:mt-8 w-full flex flex-col justify-center items-center">
<ReactDisplay className="mb-4 sm:text-[5.6rem] text-[3.2rem] text-balance text-center tracking-tight leading-none" >
<motion.span
initial={{
opacity: 0,
y: 100
}}
whileInView={{
y: 0,
opacity: 1
}}
transition={{
delay: 0.1,
...transition
}}
viewport={{ once: true }}
className="inline-block" >
Your games
</motion.span>
<motion.span
initial={{
opacity: 0,
y: 80
}}
transition={{
delay: 0.2,
...transition
}}
whileInView={{
y: 0,
opacity: 1
}}
viewport={{ once: true }}
className="inline-block" >
Your rules
</motion.span>
</ReactDisplay>
<motion.div
initial={{
opacity: 0,
y: 60
}}
transition={{
delay: 0.4,
...transition
}}
whileInView={{
y: 0,
opacity: 1
}}
viewport={{ once: true }}
className="flex items-center justify-center mt-4 w-full"
>
{children}
</motion.div>
</div>
</section>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{
...transition,
duration: 0.8,
delay: 0.7
}}
className="w-full sm:flex z-[1] hidden pointer-events-none overflow-hidden -mt-[80px] justify-center items-center flex-col" >
<section className='my-0 bottom-0 text-[100%] max-w-[1440px] pointer-events-none w-full flex items-center translate-y-[40%] justify-center relative overflow-hidden px-2 z-10 [&_svg]:w-full [&_svg]:max-w-[1440px] [&_svg]:h-full [&_svg]:opacity-70' >
<svg viewBox="0 0 498.05 70.508" xmlns="http://www.w3.org/2000/svg" height={157} width={695} >
<g strokeLinecap="round" fillRule="evenodd" fontSize="9pt" stroke="currentColor" strokeWidth="0.25mm" fill="currentColor">
<path
fill="url(#paint1)"
pathLength="1"
stroke="url(#paint1)"
d="M 261.23 41.65 L 212.402 41.65 Q 195.313 41.65 195.313 27.002 L 195.313 14.795 A 17.814 17.814 0 0 1 196.311 8.57 Q 199.443 0.146 212.402 0.146 L 283.203 0.146 L 283.203 14.844 L 217.236 14.844 Q 215.337 14.844 214.945 16.383 A 3.67 3.67 0 0 0 214.844 17.285 L 214.844 24.561 Q 214.844 27.002 217.236 27.002 L 266.113 27.002 Q 283.203 27.002 283.203 41.65 L 283.203 53.857 A 17.814 17.814 0 0 1 282.205 60.083 Q 279.073 68.506 266.113 68.506 L 195.313 68.506 L 195.313 53.809 L 261.23 53.809 A 3.515 3.515 0 0 0 262.197 53.688 Q 263.672 53.265 263.672 51.367 L 263.672 44.092 A 3.515 3.515 0 0 0 263.551 43.126 Q 263.128 41.65 261.23 41.65 Z M 185.547 53.906 L 185.547 68.506 L 114.746 68.506 Q 97.656 68.506 97.656 53.857 L 97.656 14.795 A 17.814 17.814 0 0 1 98.655 8.57 Q 101.787 0.146 114.746 0.146 L 168.457 0.146 Q 185.547 0.146 185.547 14.795 L 185.547 31.885 A 17.827 17.827 0 0 1 184.544 38.124 Q 181.621 45.972 170.174 46.538 A 36.906 36.906 0 0 1 168.457 46.582 L 117.188 46.582 L 117.236 51.465 Q 117.236 53.906 119.629 53.955 L 185.547 53.906 Z M 19.531 14.795 L 19.531 68.506 L 0 68.506 L 0 0.146 L 70.801 0.146 Q 87.891 0.146 87.891 14.795 L 87.891 68.506 L 68.359 68.506 L 68.359 17.236 Q 68.359 14.795 65.967 14.795 L 19.531 14.795 Z M 449.219 68.506 L 430.176 46.533 L 400.391 46.533 L 400.391 68.506 L 380.859 68.506 L 380.859 0.146 L 451.66 0.146 A 24.602 24.602 0 0 1 458.423 0.994 Q 466.007 3.166 468.021 10.907 A 25.178 25.178 0 0 1 468.75 17.236 L 468.75 31.885 A 18.217 18.217 0 0 1 467.887 37.73 Q 465.954 43.444 459.698 45.455 A 23.245 23.245 0 0 1 454.492 46.436 L 473.633 68.506 L 449.219 68.506 Z M 292.969 0 L 371.094 0.098 L 371.094 14.795 L 341.846 14.795 L 341.846 68.506 L 322.266 68.506 L 322.217 14.795 L 292.969 14.844 L 292.969 0 Z M 478.516 0.146 L 498.047 0.146 L 498.047 68.506 L 478.516 68.506 L 478.516 0.146 Z M 400.391 14.844 L 400.391 31.885 L 446.826 31.885 Q 448.726 31.885 449.117 30.345 A 3.67 3.67 0 0 0 449.219 29.443 L 449.219 17.285 Q 449.219 14.844 446.826 14.844 L 400.391 14.844 Z M 117.188 31.836 L 163.574 31.934 Q 165.528 31.895 165.918 30.355 A 3.514 3.514 0 0 0 166.016 29.492 L 166.016 17.236 Q 166.016 15.337 164.476 14.945 A 3.67 3.67 0 0 0 163.574 14.844 L 119.629 14.795 Q 117.188 14.795 117.188 17.188 L 117.188 31.836 Z" />
</g>
<defs>
<linearGradient gradientUnits="userSpaceOnUse" id="paint1" x1="317.5" x2="314.007" y1="-51.5" y2="126">
<stop stopColor="white"></stop>
<stop offset="1" stopOpacity="0"></stop>
</linearGradient>
</defs>
</svg>
</section>
</motion.div>
</footer>
</>
);
}
export const Footer = qwikify$(ReactFooter)

View File

@@ -1,119 +0,0 @@
/* eslint-disable qwik/no-react-props */
/** @jsxImportSource react */
import { qwikify$ } from "@builder.io/qwik-react";
import { motion } from "framer-motion"
import { ReactDisplay } from "./display"
import * as React from "react"
const transition = {
type: "spring",
stiffness: 100,
damping: 15,
restDelta: 0.001,
duration: 0.01,
}
type Props = {
children?: React.ReactNode;
}
export function ReactHeroSection({ children }: Props) {
return (
<>
<section className="px-4 w-screen pt-10 sm:pt-0" >
<header className="mx-auto flex flex-col justify-center items-center max-w-[600px] pt-20 pb-1">
<motion.img
initial={{
opacity: 0,
y: 120
}}
whileInView={{
y: 0,
opacity: 1
}}
viewport={{ once: true }}
transition={{
...transition
}}
src="/logo.webp" alt="Nestri Logo" height={80} width={80} draggable={false} className="w-[50px] md:w-[80px] aspect-[90/69] select-none" />
<div className="my-4 sm:mt-8 w-full flex flex-col justify-center items-center">
<ReactDisplay className="mb-4 sm:text-[5.6rem] text-[3.2rem] text-balance text-center tracking-tight leading-none" >
<motion.span
initial={{
opacity: 0,
y: 100
}}
whileInView={{
y: 0,
opacity: 1
}}
transition={{
delay: 0.1,
...transition
}}
viewport={{ once: true }}
className="inline-block" >
Your games
</motion.span>
<motion.span
initial={{
opacity: 0,
y: 80
}}
transition={{
delay: 0.2,
...transition
}}
whileInView={{
y: 0,
opacity: 1
}}
viewport={{ once: true }}
className="inline-block" >
Your rules
</motion.span>
</ReactDisplay>
<motion.p
initial={{
opacity: 0,
y: 50
}}
transition={{
delay: 0.3,
...transition
}}
whileInView={{
y: 0,
opacity: 1
}}
viewport={{ once: true }}
className="dark:text-gray-50/70 text-gray-950/70 text-base font-normal text-center leading-tight text-balance tracking-tight sm:text-xl"
>
Nestri is an open-source cloud gaming platform that lets you play games on your own terms invite friends to join your gaming sessions, share your game library, and take even more control by running it on your own GPU instance
</motion.p>
<motion.div
initial={{
opacity: 0,
y: 60
}}
transition={{
delay: 0.4,
...transition
}}
whileInView={{
y: 0,
opacity: 1
}}
viewport={{ once: true }}
className="flex items-center justify-center mt-4 w-full"
>
{children}
</motion.div>
</div>
</header>
</section>
</>
)
}
export const HeroSection = qwikify$(ReactHeroSection)

View File

@@ -1,9 +0,0 @@
export * from "./hero-section"
export * from "./react-example"
export * from "./title-section"
export * from "./button"
export * from "./cursor"
export * from "./motion"
export * from "./title"
export * from "./text"
export * from "./animate"

View File

@@ -1,98 +0,0 @@
import { cn } from "@nestri/ui/design";
import { component$, useStore, type Component } from "@builder.io/qwik";
interface MarqueeProps<T> {
parentClass?: string;
itemClass?: string;
items: T[];
pauseOnHover?: boolean;
renderItem: Component<{ item: T; index: number }>;
reverse?: boolean;
direction?: 'horizontal' | 'vertical';
pad?: boolean;
translate?: 'track' | 'items';
state?: 'running' | 'paused';
spill?: boolean;
diff?: boolean;
inset?: number;
outset?: number;
speed?: number;
scale?: number;
}
const renderStamp = Date.now()
export const Marquee = component$(<T,>({
parentClass,
itemClass,
reverse = false,
items,
renderItem: RenderItem,
direction = 'horizontal',
translate = 'items',
state = 'running',
pad = false,
spill = false,
diff = false,
inset = 0,
outset = 0,
pauseOnHover = false,
speed = 10,
scale = 1,
}: MarqueeProps<T>) => {
const store = useStore({
indices: Array.from({ length: items.length }, (_, i) => i),
});
return (
<div
class={cn("marquee-container", parentClass)}
data-direction={direction}
data-pad={pad}
data-pad-diff={diff}
data-pause-on-hover={pauseOnHover ? 'true' : 'false'}
data-translate={translate}
data-play-state={state}
data-spill={spill}
data-reverse={reverse}
style={{ '--speed': speed, '--count': items.length, '--scale': scale, '--inset': inset, '--outset': outset }}
>
<ul>
{pad && translate === 'track'
? store.indices.map((index) => {
return (
<li
aria-hidden="true"
class={cn("pad pad--negative", itemClass)}
key={`pad-negative-${renderStamp}--${index}`}
>
<RenderItem item={items[index]} index={index} />
</li>
)
})
: null}
{store.indices.map((index) => {
return (
<li key={`index-${renderStamp}--${index}`} style={{ '--index': index }}>
<RenderItem item={items[index]} index={index} />
</li>
)
})}
{pad && translate === 'track'
? store.indices.map((index) => {
return (
<li
aria-hidden="true"
class={cn("pad pad--positive", itemClass)}
key={`pad-positive-${renderStamp}--${index}`}
>
<RenderItem item={items[index]} index={index} />
</li>
)
})
: null}
</ul>
</div>
);
})

View File

@@ -1,36 +0,0 @@
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
import { motion, type MotionProps } from 'framer-motion';
import React, { type ReactNode } from 'react';
interface MotionComponentProps extends MotionProps {
as?: keyof JSX.IntrinsicElements;
children?: ReactNode;
class?: string;
}
export const transition = {
type: "spring",
stiffness: 100,
damping: 15,
restDelta: 0.001,
duration: 0.01,
delay: 0.4,
}
export const ReactMotionComponent = ({
as = 'div',
children,
class: className,
...motionProps
}: MotionComponentProps) => {
const MotionTag = motion[as as keyof typeof motion] as React.ComponentType<any>;
return (
<MotionTag className={className} {...(motionProps as any)}>
{children}
</MotionTag>
);
};
export const MotionComponent = qwikify$(ReactMotionComponent);

View File

@@ -1,116 +0,0 @@
/** @jsxImportSource react */
import React from 'react'
import { cn } from "@nestri/ui/design"
import { qwikify$ } from '@builder.io/qwik-react';
export const CursorSVG = ({ flip }: { flip?: boolean }) => (
<svg fill="none" height="18"
className={cn(flip ? 'scale-x-[-1]' : '', 'mb-9')}
viewBox="0 0 17 18" width="17">
<path
d="M15.5036 3.11002L12.5357 15.4055C12.2666 16.5204 10.7637 16.7146 10.22 15.7049L7.4763 10.6094L2.00376 8.65488C0.915938 8.26638 0.891983 6.73663 1.96711 6.31426L13.8314 1.65328C14.7729 1.28341 15.741 2.12672 15.5036 3.11002ZM7.56678 10.6417L7.56645 10.6416C7.56656 10.6416 7.56667 10.6416 7.56678 10.6417L7.65087 10.4062L7.56678 10.6417Z"
fill="currentColor"
style={{
stroke: `var(--cursor-color)`,
fill: `var(--cursor-color-light)`,
}}
stroke="currentColor"
// className={cn(color ? `stroke-${color}-400 text-${color}-500` : 'stroke-primary-400 text-primary-500')}
strokeWidth="1.5"
/>
</svg>
)
type CursorProps = { class?: string; text: string, color?: string, flip?: boolean }
const hexToRGBA = (hex: string, alpha: number = 1) => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
export const ReactMyCursor: React.FC<CursorProps> = ({
class: className,
text,
color = "#3B82F6",
flip = false,
}) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const [position, setPosition] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleMouseMove = (event: MouseEvent) => {
const rect = container.getBoundingClientRect();
setPosition({
x: event.clientX - rect.left,
y: event.clientY - rect.top
});
console.log(event.clientX - rect.left, event.clientY - rect.top)
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
const cursorElement = <CursorSVG flip={flip} />;
const textElement = (
<div
style={{
backgroundColor: `var(--cursor-color)`,
borderColor: `var(--cursor-color-dark)`,
}}
className={cn(
'w-fit rounded-full py-1 px-2 text-white',
)}
>
{text}
</div>
);
const colorStyles = {
'--cursor-color': color,
'--cursor-color-light': hexToRGBA(color, 0.7),
'--cursor-color-dark': hexToRGBA(color, 0.8),
} as React.CSSProperties;
return (
<div
ref={containerRef}
style={{
...colorStyles,
position: 'fixed',
pointerEvents: 'none',
zIndex: 9999,
transform: `translate(${position.x}px, ${position.y}px)`,
}}
className="flex items-center"
>
<div className={cn('flex items-center', className)}>
{flip ? (
<>
{cursorElement}
{textElement}
</>
) : (
<>
{textElement}
{cursorElement}
</>
)}
</div>
</div>
);
}
export const MyCursor = qwikify$(ReactMyCursor)

View File

@@ -1,28 +0,0 @@
/** @jsxImportSource react */
//This is used for testing whether the Qwik React components are working
import { qwikify$ } from "@builder.io/qwik-react"
import { motion } from "framer-motion"
export const ReactExample = () => {
return (
<div>
<motion.div
style={{
width: "100px",
height: "100px",
backgroundColor: "red",
}}
animate={{y:0}}
initial={{y:100}}
// whileHover={{ scale: 1.2, rotate: 90 }}
// whileTap={{
// scale: 0.8,
// rotate: -90,
// borderRadius: "100%"
// }}
/>
</div>
)
}
export const Example = qwikify$(ReactExample)

View File

@@ -1,119 +0,0 @@
/** @jsxImportSource react */
import { cn } from "@nestri/ui/design";
import { qwikify$ } from "@builder.io/qwik-react";
import React, { useEffect, useRef, useState } from "react";
import {
motion,
useAnimationFrame,
useMotionValue,
useScroll,
useSpring,
useTransform,
useVelocity,
} from "framer-motion";
interface VelocityScrollProps {
text: string;
default_velocity?: number;
class?: string;
}
interface ParallaxProps {
children: string;
baseVelocity: number;
class?: string;
}
export const wrap = (min: number, max: number, v: number) => {
const rangeSize = max - min;
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
};
export function ReactMarquee({
class: className,
text,
default_velocity = 5,
}: VelocityScrollProps) {
function ParallaxText({
children,
baseVelocity = 100,
class:className,
}: ParallaxProps) {
const baseX = useMotionValue(0);
const { scrollY } = useScroll();
const scrollVelocity = useVelocity(scrollY);
const smoothVelocity = useSpring(scrollVelocity, {
damping: 50,
stiffness: 400,
});
const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], {
clamp: false,
});
const [repetitions, setRepetitions] = useState(1);
const containerRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
const calculateRepetitions = () => {
if (containerRef.current && textRef.current) {
const containerWidth = containerRef.current.offsetWidth;
const textWidth = textRef.current.offsetWidth;
const newRepetitions = Math.ceil(containerWidth / textWidth) + 2;
setRepetitions(newRepetitions);
}
};
calculateRepetitions();
window.addEventListener("resize", calculateRepetitions);
return () => window.removeEventListener("resize", calculateRepetitions);
}, [children]);
const x = useTransform(baseX, (v) => `${wrap(-100 / repetitions, 0, v)}%`);
const directionFactor = React.useRef<number>(1);
useAnimationFrame((t, delta) => {
let moveBy = directionFactor.current * baseVelocity * (delta / 1000);
if (velocityFactor.get() < 0) {
directionFactor.current = -1;
} else if (velocityFactor.get() > 0) {
directionFactor.current = 1;
}
moveBy += directionFactor.current * moveBy * velocityFactor.get();
baseX.set(baseX.get() + moveBy);
});
return (
<div
className="w-full overflow-hidden whitespace-nowrap"
ref={containerRef}
>
<motion.div className={cn("inline-block", className)} style={{ x }}>
{Array.from({ length: repetitions }).map((_, i) => (
<span key={i} ref={i === 0 ? textRef : null}>
{children}{" "}
</span>
))}
</motion.div>
</div>
);
}
return (
<section className="relative w-full">
<ParallaxText baseVelocity={default_velocity} class={className}>
{text}
</ParallaxText>
<ParallaxText baseVelocity={-default_velocity} class={className}>
{text}
</ParallaxText>
</section>
);
}
export const Marquee = qwikify$(ReactMarquee);

View File

@@ -1,70 +0,0 @@
/** @jsxImportSource react */
import React from "react"
import {
text,
type TextProps as TextVariants,
type TextAlignProp,
type TextWeightProp,
cn
} from "@nestri/ui/design"
// import * as ReactBalancer from "react-wrap-balancer"
import { qwikify$ } from "@builder.io/qwik-react"
type TextSize = TextVariants["size"]
type TitleSizeProp = TextSize | {
initial?: TextSize,
sm?: TextSize,
md?: TextSize,
lg?: TextSize,
xl?: TextSize,
xxl?: TextSize,
}
export interface TextProps extends React.HTMLAttributes<HTMLParagraphElement | HTMLSpanElement | HTMLDivElement> {
as?: "p" | "div" | "span" | "em" | "strong",
className?: string,
size?: TitleSizeProp;
align?: TextAlignProp;
weight?: TextWeightProp;
neutral?: boolean;
}
export const ReactText: React.FC<TextProps> = ({
size,
as = "p",
weight,
align,
neutral,
children,
className,
...props
}) => {
const TextElement = as
if (as === "strong") {
weight = weight || "medium"
neutral = neutral || true
} else if (as === "em") {
neutral = neutral || true
}
return (
<TextElement className={text({
size,
weight,
align,
neutral,
className: cn("dark:text-primary-50/70 text-primary-950/70", className)
})} {...props}>
{/* <ReactBalancer.Balancer> */}
{children}
{/* </ReactBalancer.Balancer> */}
</TextElement>
)
}
ReactText.displayName = "Text"
export const Text = qwikify$(ReactText)

View File

@@ -1,88 +0,0 @@
/* eslint-disable qwik/no-react-props */
/** @jsxImportSource react */
import { qwikify$ } from "@builder.io/qwik-react";
import { motion } from "framer-motion"
import { ReactDisplay } from "./display"
// type Props = {
// children?: React.ReactElement[]
// }
const transition = {
type: "spring",
stiffness: 100,
damping: 15,
restDelta: 0.001,
duration: 0.01,
}
type Props = {
title: string
description?: string | string[]
}
export function ReactTitleSection({ title, description }: Props) {
return (
<>
<section className="px-4" >
<header className="overflow-hidden mx-auto max-w-xl pt-20 pb-4 flex justify-center items-center flex-col">
<motion.img
initial={{
opacity: 0,
y: 120
}}
whileInView={{
y: 0,
opacity: 1
}}
transition={{
...transition
}}
viewport={{ once: true }}
src="/logo.webp" alt="Nestri Logo" height={80} width={80} draggable={false} className="w-[70px] md:w-[80px] aspect-[90/69]" />
<div className="my-4 sm:my-8 flex justify-center items-center flex-col text-center">
<ReactDisplay className="mb-4 sm:text-8xl text-[3.5rem] text-balance tracking-tight leading-none" >
<motion.span
initial={{
opacity: 0,
y: 100
}}
whileInView={{
y: 0,
opacity: 1
}}
transition={{
delay: 0.1,
...transition
}}
viewport={{ once: true }}
className="inline-block" >
{title}
</motion.span>
</ReactDisplay>
{description && (<motion.p
initial={{
opacity: 0,
y: 50
}}
transition={{
delay: 0.3,
...transition
}}
whileInView={{
y: 0,
opacity: 1
}}
viewport={{ once: true }}
className="dark:text-gray-50/70 text-gray-950/70 text-lg font-normal tracking-tight sm:text-xl"
>
{Array.isArray(description) ? description.map((item, index) => {
return <span key={`id-${index}`}>{item} <br /> </span>
}) : description}
</motion.p>)}
</div>
</header>
</section>
</>
)
}
export const TitleSection = qwikify$(ReactTitleSection)

View File

@@ -1,56 +0,0 @@
/** @jsxImportSource react */
import React from "react"
import {
title,
type TitleProps as TitleVariants,
type TextAlignProp,
type TextWeightProp,
cn
} from "@nestri/ui/design"
import { qwikify$ } from "@builder.io/qwik-react";
type TitleSize = TitleVariants["size"]
type TitleSizeProp = TitleSize | {
initial?: TitleSize,
sm?: TitleSize,
md?: TitleSize,
lg?: TitleSize,
xl?: TitleSize,
xxl?: TitleSize,
}
export interface TitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "div" | "span",
className?: string,
size?: TitleSizeProp;
align?: TextAlignProp;
weight?: TextWeightProp
}
export const ReactTitle: React.FC<TitleProps> = ({
size,
as = "h1",
weight,
align,
children,
className,
...props
}) => {
const TitleElement = as
return (
<TitleElement className={title({
size,
weight,
align,
className: cn("font-title dark:text-primary-50 text-primary-950", className)
})} {...props}>
{children}
</TitleElement>
)
}
ReactTitle.displayName = "Title"
export const Title = qwikify$(ReactTitle)

View File

@@ -1,15 +0,0 @@
import React from "react"
import { twMerge } from "tailwind-merge"
/**
* Clone React element.
* The function clones React element and adds Tailwind CSS classnames to the cloned element
* @param element the React element to clone
* @param classNames Tailwind CSS classnames
* @returns { React.ReactElement } - Cloned React element
*/
export function cloneElement(element: React.ReactElement, classNames: string) {
return React.cloneElement(element, {
className: twMerge(element.props.className, classNames)
});
}

View File

@@ -1,84 +0,0 @@
import { component$ } from "@builder.io/qwik";
import { useDocumentHead, useLocation } from "@builder.io/qwik-city";
/**
* The RouterHead component is placed inside of the document `<head>` element.
*/
export const RouterHead = component$(() => {
const head = useDocumentHead();
const loc = useLocation();
const domain = loc.url.origin;
return (
<>
<title>
{/* {head.title} */}
{loc.url.pathname === "/"
? "Nestri Your games. Your rules.":
loc.url.pathname.startsWith("/moq/")
?
`MoQ Nestri`
: `${loc.url.pathname.split("/")[1].charAt(0).toUpperCase() + loc.url.pathname.split("/")[1].slice(1)} Nestri`
}
</title>
<link rel="canonical" href={loc.url.href} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{/**For SEO optimisation purposes refrain from SVG favicons and use PNG instead */}
<link rel="apple-touch-icon" sizes="180x180" href="/seo/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/seo/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/seo/favicon-16x16.png" />
<link rel="manifest" href="/manifest.json" />
{/**@ts-ignore */}
<link rel="mask-icon" href="/seo/safari-pinned-tab.svg" color="#ffede5" />
<link rel="shortcut icon" href="/seo/favicon.ico" />
<meta name="msapplication-TileColor" content="#ffede5" />
<meta name="msapplication-config" content="/seo/browserconfig.xml" />
<meta property="og:url" content={domain} />
<meta property="twitter:url" content={domain} />
<meta property="twitter:domain" content={domain.replace("https://", "")} />
<meta property="og:type" content="website" />
{!loc.url.pathname.startsWith("/blog/") && (
<>
<meta property="og:image" content={`${domain}/seo/banner.png`} />
<meta property="twitter:image" content={`${domain}/seo/banner.png`} />
</>
)}
{
head.meta.map((m) => (
<meta key={m.key} {...m} />
))
}
{
head.links.map((l) => (
<link key={l.key} {...l} />
))
}
{
head.styles.map((s) => (
<style
key={s.key}
{...s.props}
{...(s.props?.dangerouslySetInnerHTML
? {}
: { dangerouslySetInnerHTML: s.style })}
/>
))
}
{
head.scripts.map((s) => (
<script
key={s.key}
{...s.props}
{...(s.props?.dangerouslySetInnerHTML
? {}
: { dangerouslySetInnerHTML: s.script })}
/>
))
}
</>
);
});

View File

@@ -1,55 +0,0 @@
import { component$ } from "@builder.io/qwik";
import { cn } from "./design";
import { Link } from "@builder.io/qwik-city";
type Props = {
class?: string;
}
export default component$(({ class: className }: Props) => {
return (
<footer class={cn("w-full max-w-[750px] px-4 mx-auto z-20 pb-16 text-sm text-gray-600/70 dark:text-gray-400/70", className)}>
<hr class="border-none w-full h-[1.5px] bg-gray-300 dark:bg-gray-700" />
<div class="py-4 px-3 flex justify-between">
<div class="flex gap-4 items-center h-6 leading-none -ml-3">
<Link href="/" class="hover:text-gray-700 dark:hover:text-gray-300 -mt-1 transition-all duration-200">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
class="size-6"
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>
</Link>
<Link href="/about" class="hover:text-gray-700 dark:hover:text-gray-300 transition-all duration-200">
About Us
</Link>
<Link href="/pricing" class="hover:text-gray-700 dark:hover:text-gray-300 transition-all duration-200">
Pricing
</Link>
<Link href="/docs" class="hover:text-gray-700 dark:hover:text-gray-300 transition-all duration-200">
Docs
</Link>
</div>
<div class="flex items-center [&_svg]:size-5 -mr-3 gap-4">
<Link href="mailto:hi@nestri.io" target="_blank" class="hover:text-gray-700 dark:hover:text-gray-300 transition-all duration-200">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" color="currentColor"><path d="m7 8.5l2.942 1.74c1.715 1.014 2.4 1.014 4.116 0L17 8.5" /><path d="M2.016 13.476c.065 3.065.098 4.598 1.229 5.733c1.131 1.136 2.705 1.175 5.854 1.254c1.94.05 3.862.05 5.802 0c3.149-.079 4.723-.118 5.854-1.254c1.131-1.135 1.164-2.668 1.23-5.733c.02-.986.02-1.966 0-2.952c-.066-3.065-.099-4.598-1.23-5.733c-1.131-1.136-2.705-1.175-5.854-1.254a115 115 0 0 0-5.802 0c-3.149.079-4.723.118-5.854 1.254c-1.131 1.135-1.164 2.668-1.23 5.733a69 69 0 0 0 0 2.952" /></g></svg>
</Link>
<Link href="https://github.com/nestrilabs/nestri" target="_blank" class="hover:text-gray-700 dark:hover:text-gray-300 transition-all duration-200 -mt-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 .999c-6.074 0-11 5.05-11 11.278c0 4.983 3.152 9.21 7.523 10.702c.55.104.727-.246.727-.543v-2.1c-3.06.683-3.697-1.33-3.697-1.33c-.5-1.304-1.222-1.65-1.222-1.65c-.998-.7.076-.686.076-.686c1.105.08 1.686 1.163 1.686 1.163c.98 1.724 2.573 1.226 3.201.937c.098-.728.383-1.226.698-1.508c-2.442-.286-5.01-1.253-5.01-5.574c0-1.232.429-2.237 1.132-3.027c-.114-.285-.49-1.432.107-2.985c0 0 .924-.303 3.026 1.156c.877-.25 1.818-.375 2.753-.38c.935.005 1.876.13 2.755.38c2.1-1.459 3.023-1.156 3.023-1.156c.598 1.554.222 2.701.108 2.985c.706.79 1.132 1.796 1.132 3.027c0 4.332-2.573 5.286-5.022 5.565c.394.35.754 1.036.754 2.088v3.095c0 .3.176.652.734.542C19.852 21.484 23 17.258 23 12.277C23 6.048 18.075.999 12 .999" /></svg>
</Link>
<Link href="https://discord.com/invite/Y6etn3qKZ3" target="_blank" class="hover:text-gray-700 dark:hover:text-gray-300 transition-all duration-200 -mt-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.1.1 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.1 16.1 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02M8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12m6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12" /></svg>
</Link>
</div>
</div>
</footer>
)
})

File diff suppressed because one or more lines are too long

View File

@@ -1,65 +0,0 @@
import { component$, useSignal } from "@builder.io/qwik"
import { cn } from "./design"
type Props = {
class?: string
}
const minValue = 2;
const maxValue = 6;
const teamSizes = Array.from({ length: maxValue - minValue + 1 }, (_, i) => i + minValue);
export const TeamCounter = component$(({ class: className }: Props) => {
const teammates = useSignal(2)
const shake = useSignal(false)
return (
<div class={cn("flex items-center justify-center", shake.value && "animate-shake", className)}>
<button
onClick$={() => {
if (teammates.value == minValue) {
shake.value = true
setTimeout(() => {
shake.value = false
}, 500);
} else {
teammates.value = Math.max(minValue, teammates.value - 1)
}
}}
class={cn("size-[30px] rounded-full bg-gray-300 dark:bg-gray-700 flex items-center justify-center active:scale-90", teammates.value <= minValue && "opacity-30")}>
<svg xmlns="http://www.w3.org/2000/svg" class="size-[15px]" viewBox="0 0 256 256"><path fill="currentColor" d="M228 128a12 12 0 0 1-12 12H40a12 12 0 0 1 0-24h176a12 12 0 0 1 12 12" /></svg>
</button>
<div class="flex items-center justify-center mx-2 mt-1">
<p class="text-lg font-normal w-full flex items-center justify-center">
<span
class="w-[2ch] relative inline-block h-[2.625rem] overflow-hidden [mask:linear-gradient(#0000,_#000_35%_65%,_#0000)]">
<span
style={{
translate: `0 calc((${teammates.value - (minValue + 1)} + 1) * (2.625rem * -1))`,
transition: `translate 0.625s linear(0 0%,0.5007 7.21%,0.7803 12.29%,0.8883 14.93%,0.9724 17.63%,1.0343 20.44%,1.0754 23.44%,1.0898 25.22%,1.0984 27.11%,1.1014 29.15%,1.0989 31.4%,1.0854 35.23%,1.0196 48.86%,1.0043 54.06%,0.9956 59.6%,0.9925 68.11%,1 100%)`
}}
class="absolute w-full flex top-0 flex-col">
{teamSizes.map((num) => (
<span
class="w-full font-title h-[2.625rem] flex flex-col items-center justify-center leading-[1rem]"
key={`team-member-${num}`} >+{num}</span>
))}
</span>
</span>
</p>
</div>
<button onClick$={() => {
if (teammates.value == maxValue) {
shake.value = true
setTimeout(() => {
shake.value = false
}, 500);
} else {
teammates.value = Math.min(maxValue, teammates.value + 1)
}
}}
class={cn("size-[30px] rounded-full bg-gray-300 dark:bg-gray-700 flex items-center justify-center active:scale-90", teammates.value >= maxValue && "opacity-30")}>
<svg xmlns="http://www.w3.org/2000/svg" class="size-[15px]" viewBox="0 0 256 256"><path fill="currentColor" d="M228 128a12 12 0 0 1-12 12h-76v76a12 12 0 0 1-24 0v-76H40a12 12 0 0 1 0-24h76V40a12 12 0 0 1 24 0v76h76a12 12 0 0 1 12 12" /></svg>
</button>
</div>
)
})

View File

@@ -1,24 +0,0 @@
import { component$, Slot } from "@builder.io/qwik"
import { cn } from "./design";
type Props = {
position: "bottom" | "top" | "left" | "right",
text: string;
}
const textPosition = {
top: "bottom-[125%] left-1/2 -ml-[60px] after:absolute after:left-1/2 after:top-[100%] after:-ml-[5px] after:border-[5px] after:border-[#000_transparent_transparent_transparent]"
}
export const Tooltip = component$(({ position, text }: Props) => {
return (
<>
<Slot />
{/**@ts-ignore */}
<span class={cn("invisible absolute w-[120px] group-hover:visible group-hover:opacity-100 text-white bg-black text-center py-1 rounded-md", textPosition[position])} >
{text}
</span>
</>
)
})

View File

@@ -1,4 +0,0 @@
declare module '*?inline' {
const content: string;
export default content;
}

View File

@@ -1,9 +0,0 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}

View File

@@ -1,195 +0,0 @@
import colors from "tailwindcss/colors";
import tailwindcssAnimate from "tailwindcss-animate"
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./{src,components,app}/**/*.{ts,tsx,html}',
],
theme: {
extend: {
colors: {
primary: {
50: "#FFEDE5",
100: "#FFDBCC",
200: "#FFB899",
300: "#FF9466",
400: "#FF7033",
500: "#FF4F01",
600: "#CC3D00",
700: "#992E00",
800: "#661F00",
900: "#330F00",
950: "#190800",
DEFAULT: "#FF4F01"
},
secondary: colors.orange,
accent: colors.amber,
gray: {
...colors.neutral,
925: "#111111",
},
danger: colors.red,
warning: colors.yellow,
success: colors.green,
info: colors.blue,
},
fontFamily: {
body: [
"Geist Sans",
"ui-sans-serif",
"system-ui",
"-apple-system",
"BlinkMacSystemFont",
"Inter",
"Segoe UI",
"Roboto",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji",
],
mono: [
"Geist Mono",
"ui-monospace",
"SFMono-Regular",
"Menlo",
"Monaco",
"Consolas",
"Liberation Mono",
"Courier New",
"monospace"
],
mona: [
"Mona Sans Variable",
"-apple-system",
"blinkmacsystemfont",
"segoe ui",
"roboto",
"oxygen",
"ubuntu",
"cantarell",
"fira",
"sans",
"droid sans",
"helvetica neue",
"sans-serif",
],
bricolage: [
"Bricolage Grotesque Variable",
"-apple-system",
"blinkmacsystemfont",
"segoe ui",
"roboto",
"oxygen",
"ubuntu",
"cantarell",
"fira",
"sans",
"droid sans",
"helvetica neue",
"sans-serif",
],
basement: [
"Basement Grotesque",
"Bricolage Grotesque Variable",
"-apple-system",
"blinkmacsystemfont",
"segoe ui",
"roboto",
"oxygen",
"ubuntu",
"cantarell",
"fira",
"sans",
"droid sans",
"helvetica neue",
"sans-serif",
]
},
boxShadow: {
"shadow-floating-light": "0px 6px 12px 0px rgba(99,99,99,.06),0px 22px 22px 0px rgba(99,99,99,.05),0px 50px 30px 0px rgba(99,99,99,.03),0px 89px 36px 0px rgba(99,99,99,.01),0px 140px 39px 0px rgba(99,99,99,0)",
"inner-shadow-dark-float": "0px 1px 0px 0px hsla(0,0%,100%,.03) inset,0px 0px 0px 1px hsla(0,0%,100%,.03) inset,0px 0px 0px 1px rgba(0,0,0,.1),0px 2px 2px 0px rgba(0,0,0,.1),0px 4px 4px 0px rgba(0,0,0,.1),0px 8px 8px 0px rgba(0,0,0,.1)",
"fullscreen": "0 0 0 1px rgba(0,0,0,.08),0px 1px 1px rgba(0,0,0,.02),0px 8px 16px -4px rgba(0,0,0,.04),0px 24px 32px -8px rgba(0,0,0,.06)",
"menu": "0 0 0 1px rgba(0,0,0,.08),0px 1px 1px rgba(0,0,0,.02),0px 4px 8px -4px rgba(0,0,0,.04),0px 16px 24px -8px rgba(0,0,0,.06)",
},
keyframes: {
"fade-up": {
"0%": {
opacity: "0",
transform: "translateY(10px)",
},
"80%": {
opacity: "0.7",
},
"100%": {
opacity: "1",
transform: "translateY(0px)",
},
},
"fade-down": {
"0%": {
opacity: "0",
transform: "translateY(-10px)",
},
"80%": {
opacity: "0.6",
},
"100%": {
opacity: "1",
transform: "translateY(0px)",
},
},
shake: {
"25%": {
translate: "0.25ch 0"
},
"75%": {
translate: "-0.25ch 0"
}
},
slide: {
"100%": {
translate: "var(--destination-x) var(--destination-y);",
},
},
"multicolor": {
"0%": {
transform: "translateX(0%)"
},
"100%": {
transform: "translateX(-50%)"
}
},
"zoom-out": {
"0%": {
transform: "scale(1.2)"
},
"100%": {
transform: "scale(1)"
}
},
"fade-in": {
" 0% ": {
opacity: 0
},
"100%": {
opacity: 1
}
}
},
animation: {
// Fade up and down
"fade-up": "fade-up 0.5s",
"fade-down": "fade-down 0.5s",
"shake": "shake 0.075s 8",
"multicolor": "multicolor 5s linear 0s infinite",
"zoom-out": "zoom-out 5s ease-out",
"fade-in": "fade-in .3s ease forwards",
},
},
plugins: [tailwindcssAnimate]
}
}

View File

@@ -1,23 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"target": "ES2017",
"rootDir": "./",
"module": "ES2022",
"lib": ["es2022", "DOM", "WebWorker", "DOM.Iterable"],
"jsx": "react-jsx",
"jsxImportSource": "@builder.io/qwik",
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"incremental": true,
"isolatedModules": true,
"outDir": "tmp",
"noEmit": true,
},
"files": ["./.eslintrc.cjs"],
"include": ["src", "./*.d.ts", "./*.config.ts","./*.config.js"]
}