Initial Commit

This commit is contained in:
Simeon Radivoev 2026-05-06 22:36:32 +03:00
commit 6aa786e326
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
46 changed files with 1697 additions and 0 deletions

BIN
src/assets/256x256.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/favicon.ico (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/icon.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/3d screenshot.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/3nhuKCK6E3.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/4MtAe7Wkev.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/6wz3gW8c2h.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/CpBLzTNM6N.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/EWPHmIBEE5.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/GL7SkQbHIY.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/MMeJxl4IXr.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/Pkazk0RufB.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/iunZbvYEGp-ezgif.com-optimize.gif (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/mockup-1777308293568.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/rBY2mgTLy0.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/xNj7scPEDQ.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/yObFD2LySH.jpg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/zEQxtzhPGx.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src/assets/screenshots/zl8Dj4xnEw.png (Stored with Git LFS) Normal file

Binary file not shown.

6
src/assets/style.css Normal file
View file

@ -0,0 +1,6 @@
@import "tailwindcss";
@plugin "daisyui";
body {
font-family: 'Alan Sans', sans-serif;
}

View file

@ -0,0 +1,13 @@
---
const { title, description, icon } = Astro.props;
---
<div class="flex gap-4 justify-center m-4 p-4 ring-2 ring-accent/5 rounded-2xl">
<slot name="icon" />
<div class="flex flex-col">
<h1>{title}</h1>
<small class="text-base-content/60">
{description}
</small>
</div>
</div>

View file

@ -0,0 +1,79 @@
---
import {
PcCase,
Gamepad2,
Binoculars,
CloudSync,
PackageOpen,
SunMoon,
Store,
RefreshCcw,
Joystick,
} from "@lucide/astro";
import Feature from "./Feature.astro";
---
<div class="bg-base-300 grid sm:grid-cols-1 md:grid-cols-2 lg:md:grid-cols-3">
<Feature
title="Controller-First UI"
description="Switch-inspired navigation, fully operable with D-pad, joysticks, and
shoulder buttons. Designed from the ground up for handheld play."
>
<Gamepad2 size={64} slot="icon" />
</Feature>
<Feature
title="Play In-App"
description="EmulatorJS with RetroArch cores built right in. Launch and play
hundreds of platforms right in app."
>
<Joystick size={64} slot="icon" />
</Feature>
<Feature
title="Native Emulators"
description="Download, update and manage emulators within the app. Use the latest standalone emulators for maximum performance and compatibility"
>
<PackageOpen size={64} slot="icon" />
</Feature>
<Feature
title="Auto Downloads"
description="Connect to ROMM and let Gameflow Deck automatically download, sync,
and update your ROM library and platform metadata."
>
<RefreshCcw size={64} slot="icon" />
</Feature>
<Feature
title="Cloud Save Sync"
description="RClone integration lets you sync saves to any cloud provider or
between devices. Resume on your deck, continue on your PC."
>
<CloudSync size={64} slot="icon" />
</Feature>
<Feature
title="Curated Free Store"
description="Browse homebrew titles and download emulators from inside the app.
Community-driven, zero paywalls, no dark patterns."
>
<Store size={64} slot="icon" />
</Feature>
<Feature
title="ES-DE Discovery"
description="Auto-detect installed emulators from your existing ES-DE / EmuDeck
config. Bring your setup and Gameflow Deck adapts."
>
<Binoculars size={64} slot="icon" />
</Feature>
<Feature
title="Windows Games on Linux"
description="MU Launcher integration lets you run Windows store games on Linux
without Steam. Seamless Proton-powered compatibility."
>
<PcCase size={64} slot="icon" />
</Feature>
<Feature
title="Dark & Light Themes"
description="Full dark and light mode. Whether gaming at midnight or on a sunny
commute, it always looks sharp."
>
<SunMoon size={64} slot="icon" />
</Feature>
</div>

View file

@ -0,0 +1,36 @@
---
import { Github, Youtube } from "simple-icons-astro";
import { HandCoins } from "@lucide/astro";
import pkg from "../../package.json";
---
<footer class="footer sm:footer-horizontal bg-base-300 text-base-content p-10">
<nav>
<h6 class="footer-title">Source</h6>
<a class="link link-hover" href={pkg.socials.issues}>Issues</a>
<a class="link link-hover" href={pkg.downloads}>Downloads</a>
<a class="link link-hover" href={pkg.socials.mirror}>Mirror</a>
<a class="link link-hover" href={pkg.socials.license}>License</a>
</nav>
<nav>
<h6 class="footer-title">Others</h6>
<a class="link link-hover" href={pkg.socials.aboutus}>About us</a>
<a class="link link-hover" href={pkg.socials.packages}>Packages</a>
<a class="link link-hover" href={pkg.socials.store}>Store</a>
<a class="link link-hover" href={pkg.sponsor.url}>Sponsor</a>
</nav>
<nav>
<h6 class="footer-title">Social</h6>
<div class="grid grid-flow-col gap-4">
<a title="Github" href={pkg.socials.github}>
<Github />
</a>
<a title="Youtube" href={pkg.socials.youtube}>
<Youtube />
</a>
<a title="Sponsor" href={pkg.sponsor.url}>
<HandCoins />
</a>
</div>
</nav>
</footer>

View file

@ -0,0 +1,31 @@
---
import { Github } from "simple-icons-astro";
import { HandCoins } from "@lucide/astro";
import Icon from "../assets/icon.svg";
import pkg from "../../package.json";
import { releaseData } from "../scripts/getters";
---
<div class="sticky flex top-0 navbar z-100 bg-base-200 justify-between">
<div class="flex gap-2 items-center">
<img class="size-12" src={Icon.src} />
<div class="text-primary font-bold text-lg uppercase">
{pkg.displayName}
</div>
<div>Deck</div>
<div class="bg-base-300 p-1 px-3 rounded-full">{releaseData.tag_name}</div>
</div>
<div class="flex gap-2">
<a
class="flex gap-2 bg-base-300 cursor-pointer rounded-full p-1 px-3 text-xl hover:bg-accent hover:text-accent-content"
href={pkg.sponsor.url}
><HandCoins />
</a>
<a
title="Sponsor"
class="hidden sm:hidden md:flex gap-2 bg-base-300 cursor-pointer rounded-full p-1 px-3 text-xl hover:bg-accent hover:text-accent-content"
href={pkg.socials.github}
><Github />GitHub
</a>
</div>
</div>

51
src/components/Hero.astro Normal file
View file

@ -0,0 +1,51 @@
---
import gameflowLogo from "../assets/icon.svg";
import screenshot from "../assets/screenshots/3d screenshot.png";
import { Dot, Download } from "@lucide/astro";
import { Github } from "simple-icons-astro";
import pkg from "../../package.json";
---
<div class="hero bg-base-100 py-16 md:p-0 md:min-h-[calc(100vh-19rem)]">
<div class="hero-content flex-col lg:flex-row gap-8 md:p-16 rounded-4xl">
<img src={screenshot.src} class="md:max-w-sm rounded-2xl shadow-2xl" />
<div>
<small
class="flex text-base-content/40 gap-2 justify-center md:justify-start items-center mb-2"
>
<Github size={16} /> Open Source <Dot />
{pkg.license}
<div class="badge badge-ghost">beta</div>
</small>
<h1
class="flex flex-wrap gap-4 text-5xl font-bold text-primary justify-center md:justify-start"
>
<div class="uppercase">{pkg.displayName}</div>
<span class="text-base-content">Deck</span>
</h1>
<p class="py-6">
A Cross-Platform open source Retro gaming frontend designed for handheld
and controllers. Focused on building a simple user experience and
intuitive UI as a curated community driven experience.
</p>
<div class="flex gap-2 justify-center md:justify-start">
<a
class="bg-primary rounded-full text-primary-content p-2 px-4 hover:bg-accent hover:text-accent-content cursor-pointer flex gap-2"
href={pkg.downloads}
target="_blank"
>
<Download />
Download
</a>
<a
class="bg-primary rounded-full text-primary-content p-2 px-4 hover:bg-accent hover:text-accent-content cursor-pointer flex gap-2"
href={pkg.socials.github}
target="_blank"
>
<Github />
View On Github
</a>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,39 @@
---
import { Puzzle } from "@lucide/astro";
import { emulators } from "../scripts/getters";
---
<div class="divider sm:my-8 md:my-16"><Puzzle size={48} />Integrations</div>
<div class="grid sm:grid-cols-1 md:grid-cols-2 gap-8 p-8">
<ul
class="grid sm:grid-cols-2 md:lg:grid-cols-3 lg:grid-cols-4 gap-2 sm:border-b-3 sm:border-b-base-content/10 sm:pb-8 md:border-r-3 md:border-r-base-content/10 md:pr-8 items-start"
>
<li class="bg-base-300 rounded-full p-2 flex items-center justify-center">
ROMM
</li>
<li class="bg-base-300 rounded-full p-2 flex items-center justify-center">
Emulator JS
</li>
<li class="bg-base-300 rounded-full p-2 flex items-center justify-center">
RClone
</li>
<li class="bg-base-300 rounded-full p-2 flex items-center justify-center">
UMU
</li>
</ul>
<ul class="grid sm:grid-cols-1 md:lg:grid-cols-2 lg:grid-cols-3 gap-2">
{
emulators.map((e) => (
<a
class="bg-base-300 rounded-full p-2 flex items-center justify-center gap-2 hover:bg-base-100"
href={e.homepage}
target="_blank"
>
<img class="size-8" src={e.logo} />
<div>{e.name}</div>
</a>
))
}
</ul>
</div>

View file

@ -0,0 +1,7 @@
---
import pkg from "../../package.json";
---
<ul class="flex gap-2 flex-wrap justify-center p-8">
{pkg.keywords.map((k) => <li class="badge badge-ghost">{k}</li>)}
</ul>

View file

@ -0,0 +1,30 @@
---
import { PcCase } from "@lucide/astro";
import { Linux, Steamdeck } from "simple-icons-astro";
---
<div
class="flex flex-wrap justify-center items-center min-h-24 gap-2 bg-base-200 px-8 py-8"
>
<div class="font-semibold">Works On:</div>
<ul class="flex flex-wrap justify-center gap-2">
<li
class="flex gap-2 text-success font-semibold p-2 px-4 rounded-full border-dashed border-2"
>
<PcCase />
Windows
</li>
<li
class="flex gap-2 text-success font-semibold p-2 px-4 rounded-full border-dashed border-2"
>
<Linux />
Linux
</li>
<li
class="flex gap-2 text-accent font-semibold p-2 px-4 rounded-full border-dashed border-2"
>
<Steamdeck />
Steam Deck
</li>
</ul>
</div>

View file

@ -0,0 +1,24 @@
---
import { Image } from "@lucide/astro";
const images = import.meta.glob("../assets/screenshots/*.{png,jpg}", {
eager: true,
});
---
<div class="divider sm:my-8 md:my-16"><Image size={48} />Screenshots</div>
<div
class="columns-1 sm:columns-2 md:columns-3 lg:columns-4 gap-4 sm:p-8 md:p-16"
>
{
Object.values(images).map((img: any, i) => (
<a href={img.default.src} target="_blank">
<img
class="mb-4 w-full rounded-xl"
alt={`Screenshot ${i}`}
src={img.default.src}
loading="lazy"
/>
</a>
))
}
</div>

View file

@ -0,0 +1,86 @@
---
import {
Download,
Joystick,
Star,
GitCommitVertical,
UserPen,
} from "@lucide/astro";
import {
repoData,
emulators,
appContributorsData,
storeContributorsData,
} from "../scripts/getters";
---
<div
class="flex flex-wrap gap-8 bg-base-300 justify-center items-center min-h-48 py-8 px-4"
>
<div class="flex gap-4">
<Star class="text-primary" size={48} />
<div class="flex flex-col">
<span id="stars" class="text-2xl font-semibold"
>{repoData.stargazers_count}+</span
>
<div class="text-base-content/60">GitHub Stars</div>
</div>
</div>
<div class="flex gap-4">
<Download class="text-primary" size={48} />
<div class="flex flex-col">
<span id="downloads" class="text-2xl font-semibold">10+</span>
<div class="text-base-content/60">Downloads</div>
</div>
</div>
<div class="flex gap-4">
<Joystick class="text-primary" size={48} />
<div class="flex flex-col">
<span id="downloads" class="text-2xl font-semibold"
>{emulators.length}</span
>
<div class="text-base-content/60">Built In Emulators</div>
</div>
</div>
<div class="flex gap-4">
<GitCommitVertical class="text-primary" size={48} />
<div class="flex flex-col">
<span id="downloads" class="text-2xl font-semibold"
>{
appContributorsData.reduce(
(sum: number, user: any) => sum + user.contributions,
0,
)
}</span
>
<div class="text-base-content/60">Commits</div>
</div>
</div>
<div class="flex gap-4">
<UserPen class="text-primary" size={48} />
<div class="flex flex-col">
<span id="downloads" class="text-2xl font-semibold"
>{
new Set(
appContributorsData
.map((c: any) => c.login)
.concat(storeContributorsData.map((c: any) => c.login)),
).size
}</span
>
<div class="text-base-content/60">Contributors</div>
</div>
</div>
</div>
<script lang="ts">
const dl = document.getElementById("downloads");
fetch(
"https://api.npmjs.org/downloads/point/last-year/@simeonradivoev/gameflow-store",
)
.then((res) => res.json())
.then((data) => {
dl.textContent = `${data.downloads}+`;
});
</script>

View file

@ -0,0 +1,34 @@
---
import Hero from "./Hero.astro";
import Stats from "./Stats.astro";
import Platforms from "./Platforms.astro";
import Screenshots from "./Screenshots.astro";
import Header from "./Header.astro";
import Integrations from "./Integrations.astro";
import Keywords from "./Keywords.astro";
import Footer from "./Footer.astro";
import Features from "./Features.astro";
---
<Header />
<Hero />
<Platforms />
<Stats />
<div class="bg-base-200">
<div class="hero min-h-[50vh]">
<div class="hero-content flex-col text-center">
<div class="text-accent/60 text-lg">What it Does</div>
<div class="text-4xl font-semibold">Everything you need.</div>
<div class="text-4xl text-accent font-semibold">
Without ever leaving the app.
</div>
</div>
</div>
<Features />
<Integrations />
<div>
<Screenshots />
</div>
</div>
<Keywords />
<Footer />

24
src/layouts/Layout.astro Normal file
View file

@ -0,0 +1,24 @@
---
import "../assets/style.css";
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<link
href="https://fonts.googleapis.com/css2?family=Alan+Sans:wght@300..900&display=swap"
rel="stylesheet"
/>
<title>Gameflow Deck</title>
</head>
<body class="absolute left-0 top-0 right-0 bottom-0 border-2 border-red-400">
<slot />
</body>
</html>

11
src/pages/index.astro Normal file
View file

@ -0,0 +1,11 @@
---
import Welcome from "../components/Welcome.astro";
import Layout from "../layouts/Layout.astro";
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
---
<Layout>
<Welcome />
</Layout>

41
src/scripts/getters.ts Normal file
View file

@ -0,0 +1,41 @@
export const repoData = await fetch(
"https://api.github.com/repos/simeonradivoev/gameflow-deck",
{
headers: {
Authorization: `Bearer ${import.meta.env.GITHUB_TOKEN}`,
},
},
).then((res) => res.json());
export const releaseData = await fetch(
"https://api.github.com/repos/simeonradivoev/gameflow-deck/releases/latest",
{
headers: {
Authorization: `Bearer ${import.meta.env.GITHUB_TOKEN}`,
},
},
).then((res) => res.json());
export const appContributorsData = await fetch(
"https://api.github.com/repos/simeonradivoev/gameflow-deck/contributors",
{
headers: {
Authorization: `Bearer ${import.meta.env.GITHUB_TOKEN}`,
},
},
).then((res) => res.json());
export const storeContributorsData = await fetch(
"https://api.github.com/repos/simeonradivoev/gameflow-store/contributors",
{
headers: {
Authorization: `Bearer ${import.meta.env.GITHUB_TOKEN}`,
},
},
).then((res) => res.json());
export const emulators = await fetch(
"https://cdn.jsdelivr.net/npm/@simeonradivoev/gameflow-store@latest/manifests/emulators.json",
)
.then((res) => res.json())
.then((d) => d.emulators as any[]);