diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..c53b492d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint", "unifiedjs.vscode-mdx"], + "unwantedRecommendations": [] +} diff --git a/apps/www/.vscode/launch.json b/.vscode/launch.json similarity index 100% rename from apps/www/.vscode/launch.json rename to .vscode/launch.json diff --git a/apps/www/.vscode/qwik-city.code-snippets b/.vscode/qwik-city.code-snippets similarity index 100% rename from apps/www/.vscode/qwik-city.code-snippets rename to .vscode/qwik-city.code-snippets diff --git a/apps/www/.vscode/qwik.code-snippets b/.vscode/qwik.code-snippets similarity index 100% rename from apps/www/.vscode/qwik.code-snippets rename to .vscode/qwik.code-snippets diff --git a/.vscode/settings.json b/.vscode/settings.json index 00ad71fb..18a64e7e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,11 @@ { - "typescript.tsdk": "node_modules\\typescript\\lib" -} \ No newline at end of file + "material-icon-theme.activeIconPack": "qwik", + "emmet.includeLanguages": { + "typescriptreact": "html" + }, + "emmet.preferences": { + // to ensure closing tags are used (e.g. not just like in HTML) + // https://github.com/microsoft/vscode/commit/083bf9020407ea5a91199eb1f0b373859df8d600#diff-88456bc9b7caa2f8126aea0107b4671db0f094961aaf39a7c689f890e23aaaba + "output.selfClosingStyle": "xhtml" + } +} diff --git a/apps/docs/package.json b/apps/docs/package.json index fb9b0901..34cf1c9b 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,9 +1,9 @@ { - "name": "@nestri/docs", + "name": "docs", "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbo --port 3001", + "developer": "next dev --turbo --port 3001", "build": "next build", "start": "next start", "lint": "next lint" diff --git a/apps/nexus/.gitignore b/apps/nexus/.gitignore new file mode 100644 index 00000000..e319e063 --- /dev/null +++ b/apps/nexus/.gitignore @@ -0,0 +1,33 @@ +# prod +dist/ + +# dev +.yarn/ +!.yarn/releases +.vscode/* +!.vscode/launch.json +!.vscode/*.code-snippets +.idea/workspace.xml +.idea/usage.statistics.xml +.idea/shelf + +# deps +node_modules/ +.wrangler + +# env +.env +.env.production +.dev.vars + +# logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# misc +.DS_Store diff --git a/apps/nexus/README.md b/apps/nexus/README.md new file mode 100644 index 00000000..4620f9bc --- /dev/null +++ b/apps/nexus/README.md @@ -0,0 +1,12 @@ +# Nexus + +## Development + +``` +npm install +npm run dev +``` + +``` +npm run deploy +``` diff --git a/apps/nexus/assets/favicon.ico b/apps/nexus/assets/favicon.ico new file mode 100644 index 00000000..69cc4180 Binary files /dev/null and b/apps/nexus/assets/favicon.ico differ diff --git a/apps/nexus/assets/favicon.svg b/apps/nexus/assets/favicon.svg new file mode 100644 index 00000000..736b34cb --- /dev/null +++ b/apps/nexus/assets/favicon.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/apps/nexus/bun.lockb b/apps/nexus/bun.lockb new file mode 100644 index 00000000..f857839b Binary files /dev/null and b/apps/nexus/bun.lockb differ diff --git a/apps/nexus/package.json b/apps/nexus/package.json new file mode 100644 index 00000000..9bc4a03d --- /dev/null +++ b/apps/nexus/package.json @@ -0,0 +1,23 @@ +{ + "name": "@nestri/nexus", + "version": "0.0.1", + "private": true, + "description": "Nestri's core", + "scripts": { + "dev": "wrangler dev src/index.ts", + "deploy": "wrangler deploy --minify src/index.ts" + }, + "dependencies": { + "hono": "^4.5.5" + }, + "devDependencies": { + "@cf-wasm/resvg": "^0.1.21", + "@cloudflare/workers-types": "^4.20240529.0", + "@jsquash/avif": "^1.3.0", + "@jsquash/jpeg": "^1.4.0", + "@jsquash/resize": "^2.0.0", + "@nestri/cache": "workspace:*", + "tinycolor2": "^1.6.0", + "wrangler": "^3.72.2" + } +} diff --git a/apps/nexus/src/image/avatar.ts b/apps/nexus/src/image/avatar.ts new file mode 100644 index 00000000..5d95c350 --- /dev/null +++ b/apps/nexus/src/image/avatar.ts @@ -0,0 +1,38 @@ +import { Hono } from 'hono' +import { Resvg } from "@cf-wasm/resvg"; +import { generateGradient, createAvatarSvg } from '../utils'; +import { kvCaches } from "@nestri/cache" + +const cacheOptions = { + key: "nexus", + namespace: "avatar-cache" +}; + +const app = new Hono() + +const middleware = kvCaches(cacheOptions); + +app.get('/:id', middleware, async (c) => { + const id = c.req.param('id'); + const [name, fileType] = id.split('.'); + const size = parseInt(c.req.query("size") || "200"); + + const validImageTypes = ["png"] //['jpg', 'png', 'webp', 'avif']; + if (!validImageTypes.includes(fileType)) { + return c.text('Invalid image type', 400); + } + + const gradient = generateGradient(name || Math.random() + ""); + // console.log(`gradient: ${JSON.stringify(gradient)}`) + const svg = createAvatarSvg(size, gradient, fileType); + const resvg = await Resvg.create(svg.toString()); + const pngData = resvg.render() + const pngBuffer = pngData.asPng() + + return c.newResponse(pngBuffer, 200, { + "Content-Type": `image/${fileType}`, + 'Cache-Control': 'public, max-age=31536000, immutable' + }) +}) + +export default app \ No newline at end of file diff --git a/apps/nexus/src/image/banner.ts b/apps/nexus/src/image/banner.ts new file mode 100644 index 00000000..73fa0bfe --- /dev/null +++ b/apps/nexus/src/image/banner.ts @@ -0,0 +1,72 @@ +import { Hono } from 'hono' +import { kvCaches } from "@nestri/cache" +import resize, { initResize } from '@jsquash/resize'; +import decodeJpeg, { init as initDecodeJpeg } from '@jsquash/jpeg/decode'; +import encodeAvif, { init as initEncodeAvif } from '@jsquash/avif/encode.js'; +import JPEG_DEC_WASM from "@jsquash/jpeg/codec/dec/mozjpeg_dec.wasm"; +import RESIZE_WASM from "@jsquash/resize/lib/resize/pkg/squoosh_resize_bg.wasm"; +import AVIF_ENC_WASM from "@jsquash/avif/codec/enc/avif_enc.wasm"; + +const cacheOptions = { + key: "nexus", + namespace: "cover-cache" +}; + +const middleware = kvCaches(cacheOptions); + +const decodeImage = async (buffer: ArrayBuffer) => { + await initDecodeJpeg(JPEG_DEC_WASM); + return decodeJpeg(buffer); +} + +const resizeImage = async (image: { width: number; height: number }, width: number, height: number) => { + await initResize(RESIZE_WASM); + // Resize image with respect to aspect ratio + const aspectRatio = image.width / image.height; + const newWidth = width; + const newHeight = width / aspectRatio; + return resize(image, { width: newWidth, height: newHeight, fitMethod: "stretch" }); +} + +const encodeImage = async (image: { width: number; height: number }, format: string) => { + if (format === 'avif') { + await initEncodeAvif(AVIF_ENC_WASM); + return encodeAvif(image); + } + throw new Error(`Unsupported image format: ${format}`); +} + +const app = new Hono() + +app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404)) + +app.get('/:id', middleware, async (c) => { + const [gameId, imageType] = c.req.param("id").split('.'); + const width = parseInt(c.req.query("width") || "600"); + //We don't even use this, but let us keep it for future use + const height = parseInt(c.req.query("height") || "900"); + if (!gameId || !imageType) { + return c.text("Invalid image parameters", 400) + } + //Support Avif only because of it's small size + const validImageTypes = ["avif"] //['jpg', 'png', 'webp', 'avif']; + if (!validImageTypes.includes(imageType)) { + return c.text('Invalid image type', 400); + } + + const imageUrl = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${gameId}/header.jpg`; + const image = await fetch(imageUrl); + if (!image.ok) { + return c.text('Image not found', 404); + } + const imageBuffer = await image.arrayBuffer(); + const imageData = await decodeImage(imageBuffer); + const resizedImage = await resizeImage(imageData, width, height); + const resizedImageBuffer = await encodeImage(resizedImage, imageType); + return c.newResponse(resizedImageBuffer, 200, { + "Content-Type": `image/${imageType}`, + 'Cache-Control': 'public, max-age=31536000, immutable' + }) +}) + +export default app diff --git a/apps/nexus/src/image/cover.ts b/apps/nexus/src/image/cover.ts new file mode 100644 index 00000000..ef372bce --- /dev/null +++ b/apps/nexus/src/image/cover.ts @@ -0,0 +1,72 @@ +import { Hono } from 'hono' +import { kvCaches } from "@nestri/cache" +import resize, { initResize } from '@jsquash/resize'; +import decodeJpeg, { init as initDecodeJpeg } from '@jsquash/jpeg/decode'; +import encodeAvif, { init as initEncodeAvif } from '@jsquash/avif/encode.js'; +import JPEG_DEC_WASM from "@jsquash/jpeg/codec/dec/mozjpeg_dec.wasm"; +import RESIZE_WASM from "@jsquash/resize/lib/resize/pkg/squoosh_resize_bg.wasm"; +import AVIF_ENC_WASM from "@jsquash/avif/codec/enc/avif_enc.wasm"; + +const cacheOptions = { + key: "nexus", + namespace: "cover-cache" +}; + +const middleware = kvCaches(cacheOptions); + +const decodeImage = async (buffer: ArrayBuffer) => { + await initDecodeJpeg(JPEG_DEC_WASM); + return decodeJpeg(buffer); +} + +const resizeImage = async (image: { width: number; height: number }, width: number, height: number) => { + await initResize(RESIZE_WASM); + // Resize image with respect to aspect ratio + const aspectRatio = image.width / image.height; + const newWidth = width; + const newHeight = width / aspectRatio; + return resize(image, { width: newWidth, height: newHeight, fitMethod: "stretch" }); +} + +const encodeImage = async (image: { width: number; height: number }, format: string) => { + if (format === 'avif') { + await initEncodeAvif(AVIF_ENC_WASM); + return encodeAvif(image); + } + throw new Error(`Unsupported image format: ${format}`); +} + +const app = new Hono() + +app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404)) + +app.get('/:id', middleware, async (c) => { + const [gameId, imageType] = c.req.param("id").split('.'); + const width = parseInt(c.req.query("width") || "600"); + //We don't even use this, but let us keep it for future use + const height = parseInt(c.req.query("height") || "900"); + if (!gameId || !imageType) { + return c.text("Invalid image parameters", 400) + } + //Support Avif only because of it's small size + const validImageTypes = ["avif"] //['jpg', 'png', 'webp', 'avif']; + if (!validImageTypes.includes(imageType)) { + return c.text('Invalid image type', 400); + } + + const imageUrl = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${gameId}/library_600x900_2x.jpg`; + const image = await fetch(imageUrl); + if (!image.ok) { + return c.text('Image not found', 404); + } + const imageBuffer = await image.arrayBuffer(); + const imageData = await decodeImage(imageBuffer); + const resizedImage = await resizeImage(imageData, width, height); + const resizedImageBuffer = await encodeImage(resizedImage, imageType); + return c.newResponse(resizedImageBuffer, 200, { + "Content-Type": `image/${imageType}`, + 'Cache-Control': 'public, max-age=31536000, immutable' + }) +}) + +export default app diff --git a/apps/nexus/src/image/index.ts b/apps/nexus/src/image/index.ts new file mode 100644 index 00000000..93b2908d --- /dev/null +++ b/apps/nexus/src/image/index.ts @@ -0,0 +1,39 @@ +import { Hono } from 'hono' +import { cors } from 'hono/cors'; +import Cover from './cover' +import Avatar from './avatar' +import Banner from './banner' + +const app = new Hono() + +app.get('/', (c) => { + return c.text('Hello There! ๐Ÿ‘‹๐Ÿพ') +}) + +app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404)) + +app.use( + '/*', + cors({ + origin: (origin, c) => { + const allowedOriginPatterns = [ + /^https:\/\/.*\.nestri\.pages\.dev$/, + /^https:\/\/.*\.nestri\.io$/, + /^http:\/\/localhost:\d+$/ // For local development + ]; + + return allowedOriginPatterns.some(pattern => pattern.test(origin)) + ? origin + : 'https://nexus.nestri.io' + }, + }) +) + + +app.route('/cover', Cover) + +app.route('/avatar', Avatar) + +app.route("/banner", Banner) + +export default app diff --git a/apps/nexus/src/index.ts b/apps/nexus/src/index.ts new file mode 100644 index 00000000..b54acb2c --- /dev/null +++ b/apps/nexus/src/index.ts @@ -0,0 +1,18 @@ +import { Hono } from 'hono' +import Image from "./image" + +const app = new Hono() + +app.get('/', (c) => { + return c.text('Hello There! ๐Ÿ‘‹๐Ÿพ') +}) + +app.get('/favicon.ico', (c) => { + return c.newResponse(null, 302, { + Location: 'https://nestri.pages.dev/favicon.svg' + }) +}) + +app.route("/image", Image) + +export default app \ No newline at end of file diff --git a/apps/nexus/src/types/api.d.ts b/apps/nexus/src/types/api.d.ts new file mode 100644 index 00000000..b85329a1 --- /dev/null +++ b/apps/nexus/src/types/api.d.ts @@ -0,0 +1,4 @@ +declare module '*wasm' { + const content: any; + export default content; +} \ No newline at end of file diff --git a/apps/nexus/src/utils/create-avatar.tsx b/apps/nexus/src/utils/create-avatar.tsx new file mode 100644 index 00000000..a33255d3 --- /dev/null +++ b/apps/nexus/src/utils/create-avatar.tsx @@ -0,0 +1,33 @@ +export const createAvatarSvg = (size: number, gradient: { fromColor: string, toColor: string }, fileType?: string, text?: string) => ( + + + + + + + + + + {fileType === "svg" && text && ( + + {text} + + )} + + +); \ No newline at end of file diff --git a/apps/nexus/src/utils/gradient.ts b/apps/nexus/src/utils/gradient.ts new file mode 100644 index 00000000..5a2b8fa0 --- /dev/null +++ b/apps/nexus/src/utils/gradient.ts @@ -0,0 +1,24 @@ +import color from "tinycolor2"; + +function fnv1a(str: string) { + let hash = 0x811c9dc5; + for (let i = 0; i < str.length; i++) { + hash ^= str.charCodeAt(i); + hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); + } + return hash >>> 0; +} + +export function generateGradient(username: string) { + const hash = fnv1a(username); + const hue1 = hash % 360; + const hue2 = (hash >> 16) % 360; + + const c1 = color({ h: hue1, s: 0.8, l: 0.6 }); + const c2 = color({ h: hue2, s: 0.8, l: 0.5 }); + + return { + fromColor: c1.toHexString(), + toColor: c2.toHexString(), + }; +} \ No newline at end of file diff --git a/apps/nexus/src/utils/index.ts b/apps/nexus/src/utils/index.ts new file mode 100644 index 00000000..bfe71ecc --- /dev/null +++ b/apps/nexus/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './gradient' +export * from './create-avatar' \ No newline at end of file diff --git a/apps/nexus/tsconfig.json b/apps/nexus/tsconfig.json new file mode 100644 index 00000000..934f03cc --- /dev/null +++ b/apps/nexus/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "lib": [ + "ESNext" + ], + "types": [ + "@cloudflare/workers-types/2023-07-01" + ], + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx" + }, +} \ No newline at end of file diff --git a/apps/nexus/wrangler.toml b/apps/nexus/wrangler.toml new file mode 100644 index 00000000..405ebb38 --- /dev/null +++ b/apps/nexus/wrangler.toml @@ -0,0 +1,26 @@ +name = "nexus" +compatibility_date = "2024-08-14" +send_metrics = false + +[[kv_namespaces]] +binding = "nexus" +id = "e21527f9f1ed4adbaca1fa23d7f147c9" + +# [vars] +# MY_VAR = "my-variable" + +# [[kv_namespaces]] +# binding = "MY_KV_NAMESPACE" +# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +# [[r2_buckets]] +# binding = "MY_BUCKET" +# bucket_name = "my-bucket" + +# [[d1_databases]] +# binding = "DB" +# database_name = "my-database" +# database_id = "" + +# [ai] +# binding = "AI" \ No newline at end of file diff --git a/apps/www/.eslintrc.cjs b/apps/www/.eslintrc.cjs deleted file mode 100644 index 728e75f8..00000000 --- a/apps/www/.eslintrc.cjs +++ /dev/null @@ -1,11 +0,0 @@ -/** @type {import("eslint").Linter.Config} */ -module.exports = { - root: true, - extends: ["@nestri/eslint-config/qwik.js"], - parser: "@typescript-eslint/parser", - parserOptions: { - //Find some way to use the lint tsconfig - project: "./tsconfig.json", - tsconfigRootDir: __dirname, - }, -}; diff --git a/apps/www/.eslintrc.js b/apps/www/.eslintrc.js new file mode 100644 index 00000000..878c3386 --- /dev/null +++ b/apps/www/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + root: true, + extends: [ + "@nestri/eslint-config/qwik.js", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json"], + ecmaVersion: 2021, + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + } +}; \ No newline at end of file diff --git a/apps/www/package.json b/apps/www/package.json index b3c3ac68..8a88fe85 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -1,6 +1,6 @@ { - "name": "@nestri/www", - "description": "Website for Nestri", + "name": "@nestri/web", + "description": "Your games. Your rules.", "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -31,16 +31,22 @@ "devDependencies": { "@builder.io/qwik": "^1.8.0", "@builder.io/qwik-city": "^1.8.0", + "@builder.io/qwik-react": "0.5.0", "@nestri/eslint-config": "workspace:*", "@nestri/typescript-config": "workspace:*", "@nestri/ui": "workspace:*", "@types/eslint": "8.56.10", - "@types/node": "20.14.11", + "@types/node": "^22.5.1", + "@types/react": "^18.2.28", + "@types/react-dom": "^18.2.13", "@typescript-eslint/eslint-plugin": "7.16.1", "@typescript-eslint/parser": "7.16.1", "eslint": "8.57.0", "eslint-plugin-qwik": "^1.8.0", + "framer-motion": "^11.3.24", "prettier": "3.3.3", + "react": "18.2.0", + "react-dom": "18.2.0", "typescript": "5.4.5", "undici": "*", "vite": "5.3.5", diff --git a/apps/www/postcss.config.cjs b/apps/www/postcss.config.cjs index e4cb5c3d..dd1d0abe 100644 --- a/apps/www/postcss.config.cjs +++ b/apps/www/postcss.config.cjs @@ -1 +1 @@ -module.exports = require("@nestri/ui/postcss"); \ No newline at end of file +module.exports = require("@nestri/ui/postcss.config"); \ No newline at end of file diff --git a/apps/www/public/changelog/v0.0.3/game-play.png b/apps/www/public/changelog/v0.0.3/game-play.png new file mode 100644 index 00000000..8b2f1559 Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/game-play.png differ diff --git a/apps/www/public/changelog/v0.0.3/gameplay-right.png b/apps/www/public/changelog/v0.0.3/gameplay-right.png new file mode 100644 index 00000000..53c82ca1 Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/gameplay-right.png differ diff --git a/apps/www/public/changelog/v0.0.3/gameplay.avifs b/apps/www/public/changelog/v0.0.3/gameplay.avifs new file mode 100644 index 00000000..593ba813 Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/gameplay.avifs differ diff --git a/apps/www/public/changelog/v0.0.3/header.avif b/apps/www/public/changelog/v0.0.3/header.avif new file mode 100644 index 00000000..5f886639 Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/header.avif differ diff --git a/apps/www/public/changelog/v0.0.3/header.png b/apps/www/public/changelog/v0.0.3/header.png new file mode 100644 index 00000000..0835b342 Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/header.png differ diff --git a/apps/www/public/changelog/v0.0.3/nestrivalheim.mp4 b/apps/www/public/changelog/v0.0.3/nestrivalheim.mp4 new file mode 100644 index 00000000..18a59d0b Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/nestrivalheim.mp4 differ diff --git a/apps/www/public/changelog/v0.0.3/new-site.png b/apps/www/public/changelog/v0.0.3/new-site.png new file mode 100644 index 00000000..fae483e3 Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/new-site.png differ diff --git a/apps/www/public/changelog/v0.0.3/new-website-design.avif b/apps/www/public/changelog/v0.0.3/new-website-design.avif new file mode 100644 index 00000000..96ff172e Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/new-website-design.avif differ diff --git a/apps/www/public/changelog/v0.0.3/new-website-design.png b/apps/www/public/changelog/v0.0.3/new-website-design.png new file mode 100644 index 00000000..63d872aa Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/new-website-design.png differ diff --git a/apps/www/public/changelog/v0.0.3/new-website.png b/apps/www/public/changelog/v0.0.3/new-website.png new file mode 100644 index 00000000..3bf245fd Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/new-website.png differ diff --git a/apps/www/public/changelog/v0.0.3/steam-integration.png b/apps/www/public/changelog/v0.0.3/steam-integration.png new file mode 100644 index 00000000..5d1ce4a6 Binary files /dev/null and b/apps/www/public/changelog/v0.0.3/steam-integration.png differ diff --git a/apps/www/public/logo.webp b/apps/www/public/logo.webp new file mode 100644 index 00000000..65df71a6 Binary files /dev/null and b/apps/www/public/logo.webp differ diff --git a/apps/www/src/components/router-head/index.tsx b/apps/www/src/components/router-head/index.tsx index 2c32f9c5..f487a5d3 100644 --- a/apps/www/src/components/router-head/index.tsx +++ b/apps/www/src/components/router-head/index.tsx @@ -26,7 +26,7 @@ export const RouterHead = component$(() => { {/**@ts-ignore */} - + diff --git a/apps/www/src/root.tsx b/apps/www/src/root.tsx index 88c7b654..269df729 100644 --- a/apps/www/src/root.tsx +++ b/apps/www/src/root.tsx @@ -8,6 +8,7 @@ import { RouterHead } from "@/components/router-head"; import { isDev } from "@builder.io/qwik/build"; import "@nestri/ui/globals.css"; +import { Fonts } from "@nestri/ui"; export default component$(() => { /** @@ -18,21 +19,28 @@ export default component$(() => { */ return ( - - - - {!isDev && ( - - )} - - - - - {!isDev && } - - + + + + + + + {!isDev && ( + + )} + + + + + {/* {!isDev && } */} + + + + ); }); diff --git a/apps/www/src/routes/(legal)/privacy/index.tsx b/apps/www/src/routes/(legal)/privacy/index.tsx new file mode 100644 index 00000000..e7443dca --- /dev/null +++ b/apps/www/src/routes/(legal)/privacy/index.tsx @@ -0,0 +1,185 @@ +/* eslint-disable qwik/no-react-props */ +import { Title, Text} from "@nestri/ui/react"; +import { buttonVariants, cn } from "@nestri/ui/design"; +import { component$ } from "@builder.io/qwik"; +import { Link } from "@builder.io/qwik-city"; + +export default component$(() => { + + return ( +
+ {/**Gradient to hide the ending of the checkered bg at the bottom*/} + {/*
*/} + +
+ +
+ + Nestri's Privacy Policy + + + + Last updated on:  + 1st July 2024 + + + + Welcome to Nestri. Thank you for using our service. We value you and we know privacy is important to you. It's important to us, too. +
+
+ This Privacy Policy describes how we collect, use, disclose, and protect the personal data we collect through our website, products, services, and applications that link to this Privacy Policy (collectively, the "Services"). + +
+ + Information We Collect + + We may collect personal information directly from you or automatically when you use our Services. The types of personal information we may collect include: +
+ +
    +
  • + Contact Information: +  such as name and email address. +
  • +
  • + Account Credentials: +  such as usernames and passwords. +
  • +
  • + Limited Payment Processing Information: +  such as whether the transaction happened, its status, type and amount, as well as what payment scheme or operator youโ€™ve used. + We DO NOT collect information about your bank card number, bank account number, cardholderโ€™s name. +
  • +
  • + Usage Information: +  such as your IP address, browser type, operating system, and device information. +
  • +
  • + Cookies and Similar Technologies: +  to collect information about your interactions with our Services. +
  • +
  • + Additional Information: +  such as age, gender, games played, activity across our products, games "installed," crash reports, technical data about your device (including internet speed, IP, location, mobile type, hardware details), and games on Steam (from Steam). +
  • +
+ +
+ + How We Use Your Information + + We may use the personal information we collect for the following purposes: +
+ +
    +
  • + To Provide and Maintain Our Services: +  ensuring they function correctly and securely. +
  • +
  • + To Process and Fulfill Your Requests: +  such as subscription management and customer support. +
  • +
  • + To Communicate with You: +  including responding to your inquiries and providing customer support. +
  • +
  • + To Personalize Your Experience and Improve Our Services: +   based on your usage and preferences. +
  • +
  • + To Send You Marketing Communications and Promotional Offers: +  if you have opted in to receive them. +
  • +
  • + To Detect, Prevent, and Investigate Piracy and Other Illegal Activities: +  ensuring the integrity of our platform. +
  • +
  • + To Comply with Legal Obligations and Enforce Our Terms and Policies: +  including our Terms of Service. +
  • +
+ +
+ + Data Sharing and Disclosure + + We may share your personal information with third parties in the following circumstances: +
+ +
    +
  • + With Our Affiliates and Subsidiaries: +  for the purposes described in this Privacy Policy. +
  • +
  • + With Third Parties for Marketing Purposes: +   if you have consented to such sharing. +
  • +
  • + In Connection with a Merger, Acquisition, or Sale: +  of all or a portion of our assets. +
  • +
  • + With Service Providers: +  who assist us in operating our business and providing our Services. +
  • +
+
+ + Your Rights and Choices + + + You have certain rights regarding your personal information, including the right to access, correct, or delete your information. You may also have the right to object to certain processing activities and to withdraw your consent where applicable. + + + Data Transfers + + If we transfer your personal information outside of the European Economic Area (EEA), we will ensure that adequate safeguards are in place to protect your information, such as standard contractual clauses approved by the European Commission. + + Security Measures + + We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, disclosure, alteration, or destruction. + + Children's Privacy + + Our Services are not directed to children under the age of 13, and we do not knowingly collect personal information from children under this age. If you are a parent or guardian and believe that your child has provided us with personal information, please contact us, and we will take steps to delete such information. Children merit specific protection with regard to their personal data. If we decide to knowingly collect personal data from a child under 13, we will ask for consent from the holder of parental responsibility over the child. + + Changes to This Privacy Policy + + We may update this Privacy Policy from time to time to reflect changes in our practices or applicable law. We will notify you of any material changes by posting the updated Privacy Policy on our website. + + Contact Us + + + If you have any questions or concerns about our Privacy Policy or our data practices, you may contact us at + + support@nestri.io + . + + + Legal Basis for Processing (for Users in the EEA) + + If you are located in the European Economic Area (EEA), our legal basis for collecting and using the personal information described in this Privacy Policy will depend on the personal information concerned and the specific context in which we collect it. We will only process your personal information if we have a valid legal basis for doing so under applicable data protection law. + + + ๐Ÿ’– Thank you for trusting Nestri with your data and gaming experience.๐Ÿ’– +
+ We are committed to safeguarding your personal information and ensuring your privacy.
+
+
+
+ ) +}) \ No newline at end of file diff --git a/apps/www/src/routes/(legal)/terms/index.tsx b/apps/www/src/routes/(legal)/terms/index.tsx new file mode 100644 index 00000000..988067b5 --- /dev/null +++ b/apps/www/src/routes/(legal)/terms/index.tsx @@ -0,0 +1,184 @@ +/* eslint-disable qwik/no-react-props */ +import { Title, Text } from "@nestri/ui/react"; +import { buttonVariants, cn } from "@nestri/ui/design"; +import { component$ } from "@builder.io/qwik"; +import { Link } from "@builder.io/qwik-city"; + +export default component$(() => { + return ( +
+ {/**Gradient to hide the ending of the checkered bg at the bottom*/} + {/*
*/} + +
+
+ + Nestri's Terms of Service + + + + Last updated on:  + 1st July 2024 + + + + Welcome to Nestri and thank you for using our services. We are an innovative cloud gaming platform that offers both self-hosted and hosted versions for gamers without GPUs. +
+
+ By using Nestri, you agree to these Terms of Service ("Terms"). If you have any questions, feel free to contact us. +
+ + Who We Are + + 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 hosting your own gaming server. Our hosted version is perfect for those who need GPU support, providing seamless gaming experiences for everyone. + + Privacy and Security + + We take your privacy and security very seriously. We adhere to stringent data protection laws and ensure that all data, including games downloaded from Steam on behalf of the user, is encrypted. We also collect and log IP addresses to avoid abuse and ensure the security of our services. For more details, please review our + + Privacy Policy. + + + Acceptance of Terms + + By using Nestri, you agree to be bound by these Terms. If you're using Nestri on behalf of an organization, you agree to these Terms on behalf of that organization. + + Your Games + + You can run the games you own on Nestri. Please check the list of video games available on Nestri before purchasing a subscription. Sorry, but we will not provide a refund if a game you want to play is not available. +
+
+ + Check the list here. +
+ + Game Ownership and Piracy + + Nestri strictly prohibits the use of our platform for piracy. We are not liable if you are caught pirating using any of our products. Only run games that you legally own. + + Family Sharing + + Nestri allows family sharing by enabling your friends to access your Steam account from their Nestri account. Please take care of who you share your Nestri membership with. Note that no two people can play a game simultaneously as per Steam's requirements. + + Cloud Saves + + We use a custom cloud save provider to ensure your game progress is preserved for all the games you play. If you exit a game properly or close the stream for more than 15 minutes, we will automatically save your progress. However, this is limiting, and you may need to contact us to retrieve your game progress, if you plan on migrating to another service. + + Your Responsibilities + + Your use of our services must comply with these Terms and applicable Nestri policies, as well as applicable laws. You are solely responsible for the development, content, and use of the games you play on Nestri and assume all risks associated with them, including intellectual property or other legal claims. + + Prohibited Activities + + To maintain a great service, we require you to comply with certain limitations. +
+ You may not: +
+ +
    +
  • Violate any laws, regulations, ordinances, or directives.
  • +
  • Engage in any threatening, abusive, harassing, defamatory, or tortious conduct.
  • +
  • Harass or abuse Nestri personnel or representatives.
  • +
  • Use our services to support malware, phishing, spam, pirating, or similar activities.
  • +
  • Interfere with the proper functioning of our services.
  • +
  • Engage in any conduct that inhibits anyone else's use or enjoyment of our services.
  • +
  • Circumvent storage space limits or pricing.
  • +
  • Use the Nestri system inconsistently with its intended purpose.
  • +
  • Upload, transmit, or distribute any computer viruses, worms, or any software intended to damage or alter a computer system or data.
  • +
  • Send advertising, promotional materials, junk mail, spam, chain letters, pyramid schemes, or any other form of duplicative or unsolicited messages.
  • +
  • Harvest, collect, or assemble information or data regarding other users without their consent.
  • +
  • Attempt to gain unauthorized access to Nestri or other computer systems or networks connected to or used together with Nestri.
  • +
  • Use automated scripts to produce multiple accounts or to generate automated searches, requests, or queries.
  • +
+ +
+ + Our Rights + + We reserve the right to change, eliminate, or restrict access to our services at any time. We may modify, suspend, or terminate a user account if you stop paying for our service or violate any of our Terms or policies. Nestri is not liable for any damages resulting from these actions. + + Beta Services + + We may release products and features still in testing and evaluation (โ€œBeta Servicesโ€). Beta Services are confidential until official launch. By using Beta Services, you agree not to disclose any information about them without our permission. + + Other Sites and Services + + Nestri may contain links to websites, services, and advertisements that we neither own nor control. We do not endorse or assume responsibility for any third-party sites, information, materials, products, or services. + + Children + + Nestri is only for users 13 years old and older. If we become aware that a child under 13 has created an account, we will terminate that account. + + Account Creation and Access + + When you create a Nestri account, you will be required to set your account credentials, including an email address and a password. You are responsible for maintaining and safeguarding your account credentials. Nestri is under no obligation to provide you access to your account or your files if you are unable to provide the appropriate account credentials. + + Service Cancellation and Account Deletion + + You can cancel your Nestri service and delete your account at any time by signing in and deleting all your games and game progress stored on our system. If assistance is needed, please contact us. When you cancel your service, we will no longer bill you, except for past due amounts. Your canceled account information will remain accessible unless you delete your account. + + Disclaimers + + Nestri is provided "as is" without any warranties, express or implied. Except where otherwise prohibited by law, Nestri disclaims all warranties and conditions of merchantability, fitness for a particular purpose, and non-infringement. + + Limitation of Liability + + To the fullest extent allowed by law, Nestri shall not be liable for any indirect, incidental, special, consequential, or punitive damages, or any loss of profits or revenues, whether incurred directly or indirectly, or any loss of data, use, goodwill, or other intangible losses resulting from (A) your access to, use of, inability to access, or inability to use Nestri; (B) any third-party conduct or content on Nestri; or (C) any unauthorized access, use, or alteration of your content. + + Arbitration and Opt-Out + + We aim to resolve disputes fairly and quickly. If you have any issues with Nestri, please contact us, and we'll work with you in good faith to resolve the matter. If we can't solve the dispute informally, you and Nestri agree to resolve any claim through final and binding arbitration. You may opt out of the arbitration agreement by notifying us within 90 days of agreeing to these Terms. + + Class Action and Trial Waiver + + You and Nestri agree that each party may bring disputes against the other only in an individual capacity and not on behalf of any class of people. You and Nestri agree to waive the right to a trial by jury for all disputes. + + Indemnification + + You agree to indemnify, defend, and hold harmless Nestri from and against all liabilities, damages, and costs arising out of any claim by a third party against Nestri regarding (a) games and game progress stored with us by you, (b) your domains, or (c) your use of Nestri in violation of these terms. + + Governing Law and Jurisdiction + + These Terms will be governed by the laws applicable in your jurisdiction. For claims not subject to arbitration, we each agree to submit to the personal jurisdiction of the courts located in your jurisdiction. + + Entire Agreement + + These Terms and our Privacy Policy constitute the entire agreement between you and Nestri. If any provision of these Terms is found to be unenforceable, the remaining provisions will remain in full force and effect. + + No Waiver + + No waiver of any provision of these Terms shall be a further or continuing waiver of that term. Nestri's failure to assert any right or provision under these Terms does not constitute a waiver of that right or provision. + + Modification + + These Terms may be modified from time to time. The date of the most recent revisions will always be available on our website. If we make changes that we believe will substantially alter your rights, we will notify you. If you continue to use Nestri after modifications of the Terms, you agree to accept such modifications. + + Contact + + We welcome all questions, concerns, and feedback you might have about these terms. If you have suggestions for us, let us know at + + support@nestri.io + + . + + + ๐Ÿ’– Thank you for choosing Nestri for your cloud gaming needs! ๐Ÿ’– +
+
+
+ ) +}) \ No newline at end of file diff --git a/apps/www/src/routes/changelog/index.tsx b/apps/www/src/routes/changelog/index.tsx new file mode 100644 index 00000000..80efa76f --- /dev/null +++ b/apps/www/src/routes/changelog/index.tsx @@ -0,0 +1,89 @@ +import { component$ } from "@builder.io/qwik"; +import { Link } from "@builder.io/qwik-city"; +import { NavBar, Footer } from "@nestri/ui"; +import { MotionComponent, transition, TitleSection } from "@nestri/ui/react"; + +export default component$(() => { + return ( + <> + + + +
+
+
+

+ v0.0.3 +

+

August 2024

+
+
+
+
+
+

Fresh new look, Intel & AMD GPU support and we finally launched ๐Ÿฅณ

+
+
+ Nestri Logo +
+
+
+
+

Fresh new logo and branding ๐Ÿ’…๐Ÿพ

+
+
+ Nestri Logo +
+
+
+
+

Updated our Terms of Service and our Privacy Policy

+
+
+
+
+

Added support for Intel & AMD GPUs. Courtesy of{" "}@DatHorse

+
+
+
+
+ Nestri Logo +
+
+
+
+

+ Lots of quality of life improvements! ๐Ÿคž๐Ÿฝ

+
+
+
+
+
+
+
+

+ v0.0.2 +

+

June 2024

+
+
+
+
+

Nestri has been long overdue for a changelog. Welcome to our changelog!

+
+
+
+
+
+
+