new StoresIndexPage
This commit is contained in:
@@ -199,55 +199,40 @@ export default {
|
||||
subtitle: 'Which engines build for which platforms.',
|
||||
},
|
||||
stores: {
|
||||
title: 'WarpEngine Client',
|
||||
subtitle: 'Our games on your own machine, kept up to date — as an app, a command line, or your console\'s own library.',
|
||||
lead: 'Our whole catalog on your own machine, kept up to date. The quickest way in is the app: install it, and games land in your application menu like anything else you have installed. If a window is the wrong shape — a server, a script, or a console with its own library — there is a command line for desktops, and integrations that put our games into Batocera\'s and RetroArch\'s own libraries. All of it downloads straight from this site with box art, and all of it uses nothing but the Python 3 standard library.',
|
||||
clientTitle: 'Get WarpEngine Client',
|
||||
clientDesc: 'A small desktop application: the catalog as a grid of cards, one click to install a game into your own application menu, one to play it, one to remove it. Nothing to set up first — it downloads the store itself on first run — and on Windows it is the way in, because `curl … | sh` does not exist there.',
|
||||
clientPlatforms: 'Linux, macOS and Windows',
|
||||
title: 'Stores',
|
||||
subtitle: 'Our games on your own machine, kept up to date.',
|
||||
viewClient: 'WarpEngine Client',
|
||||
viewOthers: 'Others',
|
||||
|
||||
clientDesc: 'The app: the catalog as a grid of cards, one click to install a game into your application menu, one to play it. It sets the store up itself on first run, and on Windows it is the way in.',
|
||||
clientDownload: 'Download',
|
||||
clientPlatforms: 'Linux, macOS and Windows · needs Python 3',
|
||||
clientShotAlt: 'The WarpEngine Client window: the store picker and category filters on the left, and the catalog as a grid of game cards with install and play buttons.',
|
||||
clientPointSetup: 'Sets the store up itself on first run — nothing to install beforehand.',
|
||||
clientPointBrowse: 'Filter by platform, by what is installed, or by what has an update.',
|
||||
clientPointSame: 'The same store underneath, so the command line keeps working on the same games.',
|
||||
clientDownload: 'Download the app',
|
||||
clientNote: 'Python 3 has to be on the machine, as it does for the command line — the store itself is a Python program and the window drives it. The macOS build is signed but not notarised, so macOS asks before opening it the first time; the download page says how.',
|
||||
chooseTitle: 'Other ways in',
|
||||
chooseDesc: 'The same catalog and the same engine underneath, for the machines where a window is the wrong shape: a command line for scripts and servers, and integrations that put our games into a console\'s own library.',
|
||||
repo: 'Git repository',
|
||||
|
||||
docs: 'Documentation',
|
||||
repo: 'Git repository',
|
||||
installTitle: 'Install',
|
||||
installThen: 'Then',
|
||||
useTitle: 'Use it',
|
||||
uninstallTitle: 'Remove it',
|
||||
uninstallDesc: 'One line takes it all back out: the games, the metadata, the store itself.',
|
||||
uninstallNote: 'Your own games are never touched. Anything you put in the store\'s folder keeps its place, your play counts and favourites survive, and the backups the store made of files it found are listed rather than deleted.',
|
||||
platformsTitle: 'What they install',
|
||||
platformsDesc: 'A catalog platform is installable when its release file boots directly in an emulator — the same cartridges either way. Our other platforms ship web and desktop builds, which no emulator loads; on a Batocera box those can be installed as Ports instead.',
|
||||
engineTitle: 'Run your own store',
|
||||
engineDesc: 'Nothing here is specific to us. Each store is just a config file on top of an open engine that works with any WarpEngine-based site: point it at your own catalog, give it a name and a folder of its own, and your games land on a device the same way. The two engines share warpstore, which does the catalog side for both, so a third kind of device is an adapter and a config rather than a new program. Several stores can live side by side on one machine without touching each other\'s games.',
|
||||
platformsTitle: 'What gets installed',
|
||||
engineLine: 'Every store here is a config file on top of an open engine — point one at your own catalog:',
|
||||
copy: 'Copy',
|
||||
|
||||
desktop: {
|
||||
name: 'Desktop CLI',
|
||||
site: 'Teletype Games',
|
||||
tagline: 'For scripts and servers',
|
||||
installDesc: 'Run this on the computer you want to play on. It downloads the games, installs them under your own user account and adds them to your application menu — no administrator rights, nothing installed system-wide.',
|
||||
installNote: 'The native builds are tens of megabytes each, so the first run takes a while. Games we have no native build for get an entry that opens the browser version instead, and those need the network to play. This is the same store the app above installs, so the two are interchangeable on one machine.',
|
||||
useDesc: 'The store has a small CLI, and `paths` shows where things went on your machine:',
|
||||
},
|
||||
batocera: {
|
||||
name: 'Batocera',
|
||||
site: 'Batocera project',
|
||||
tagline: 'The plug-and-play retro distribution',
|
||||
installDesc: 'Run this over SSH on the Batocera box. It installs the engine, writes the config, creates the Ports entry and runs the first sync.',
|
||||
installNote: 'Then restart EmulationStation so the games show up:',
|
||||
useDesc: 'On the device: Ports ▸ "Teletype Games Store". Over SSH the store has a small CLI:',
|
||||
},
|
||||
retroarch: {
|
||||
name: 'RetroArch',
|
||||
site: 'RetroArch project',
|
||||
tagline: 'Desktop, Android, Steam Deck, handhelds',
|
||||
installDesc: 'Run this on the machine RetroArch is installed on. It reads your retroarch.cfg to find where your playlists, thumbnails and cores live, then writes one playlist per platform with box art.',
|
||||
installNote: 'Restart RetroArch afterwards — it builds its menu at startup. If the VICE x64 and TIC-80 cores are missing it still works, and RetroArch asks which core to use the first time.',
|
||||
useDesc: 'The store has a small CLI. Close RetroArch before syncing: it writes its playlists back when it exits, so a sync underneath a running instance can be undone.',
|
||||
site: 'RetroArch',
|
||||
tagline: 'Anything that runs RetroArch',
|
||||
},
|
||||
},
|
||||
code: {
|
||||
|
||||
@@ -199,55 +199,40 @@ export default {
|
||||
subtitle: 'Melyik engine melyik platformra fordít.',
|
||||
},
|
||||
stores: {
|
||||
title: 'WarpEngine Client',
|
||||
subtitle: 'A játékaink a saját gépeden, mindig frissen — alkalmazásként, parancssorból vagy a konzolod saját könyvtárában.',
|
||||
lead: 'A teljes katalógusunk a saját gépeden, mindig frissen. A legrövidebb út az alkalmazás: telepítsd, és a játékok ugyanúgy megjelennek az alkalmazásmenüdben, mint bármi más. Ahol az ablak a rossz forma — szerveren, szkriptből, vagy egy konzolon, aminek saját könyvtára van —, ott van parancssor asztali gépekre, és integráció, ami a Batocera, illetve a RetroArch saját könyvtárába teszi a játékainkat. Mindegyik közvetlenül erről az oldalról töltődik le, borítóképpel, és mindegyikhez elég a Python 3 alapkészlete.',
|
||||
clientTitle: 'Töltsd le a WarpEngine Clientet',
|
||||
clientDesc: 'Egy kis asztali alkalmazás: a katalógus kártyákban, egy kattintás a telepítés a saját alkalmazásmenüdbe, egy az indítás, egy az eltávolítás. Előre semmit nem kell beállítani — első indításkor magát a store-t is letölti —, Windowson pedig ez az út, mert ott a `curl … | sh` nem létezik.',
|
||||
clientPlatforms: 'Linux, macOS és Windows',
|
||||
title: 'Store-ok',
|
||||
subtitle: 'A játékaink a saját gépeden, mindig frissen.',
|
||||
viewClient: 'WarpEngine Client',
|
||||
viewOthers: 'Egyéb',
|
||||
|
||||
clientDesc: 'Az alkalmazás: a katalógus kártyákban, egy kattintás a telepítés az alkalmazásmenübe, egy az indítás. Első indításkor magát a store-t is beállítja, Windowson pedig ez az út.',
|
||||
clientDownload: 'Letöltés',
|
||||
clientPlatforms: 'Linux, macOS és Windows · Python 3 kell hozzá',
|
||||
clientShotAlt: 'A WarpEngine Client ablaka: balra a store-választó és a kategóriaszűrők, jobbra a katalógus játékkártyákban, telepítés és indítás gombokkal.',
|
||||
clientPointSetup: 'Első indításkor magát a store-t is beállítja — előre semmit nem kell telepíteni.',
|
||||
clientPointBrowse: 'Szűrhetsz platformra, a telepítettekre vagy arra, amihez frissítés van.',
|
||||
clientPointSame: 'Alatta ugyanaz a store, tehát a parancssor ugyanazokon a játékokon dolgozik tovább.',
|
||||
clientDownload: 'Alkalmazás letöltése',
|
||||
clientNote: 'Python 3 kell a gépre, ugyanúgy, mint a parancssorhoz — a store maga egy Python program, az ablak csak vezérli. A macOS-csomag aláírt, de nem notarizált, ezért a rendszer az első megnyitás előtt rákérdez; a letöltési oldal leírja, hogyan.',
|
||||
chooseTitle: 'További utak',
|
||||
chooseDesc: 'Ugyanaz a katalógus és ugyanaz a motor alatta, azokra a gépekre, ahol az ablak a rossz forma: parancssor szkriptekhez és szerverekhez, meg integrációk, amik a konzol saját könyvtárába teszik a játékainkat.',
|
||||
repo: 'Git tároló',
|
||||
|
||||
docs: 'Dokumentáció',
|
||||
repo: 'Git repó',
|
||||
installTitle: 'Telepítés',
|
||||
installThen: 'Utána',
|
||||
useTitle: 'Használat',
|
||||
uninstallTitle: 'Eltávolítás',
|
||||
uninstallDesc: 'Egy sor mindent visszabont: a játékokat, a metaadatokat és magát a store-t.',
|
||||
uninstallNote: 'A saját játékaidhoz nem nyúl. Ami a store mappájába tőled került, a helyén marad, a játékszámláló és a kedvencek megmaradnak, a store által készített biztonsági másolatokat pedig felsorolja, nem törli.',
|
||||
platformsTitle: 'Mit telepítenek',
|
||||
platformsDesc: 'Egy katalógus-platform akkor telepíthető, ha a kiadás fájlja közvetlenül elindul egy emulátorban — mindkét úton ugyanezek. A többi platformunk webes és asztali buildet ad, amit emulátor nem tölt be; Batocera gépen azok Ports-ként telepíthetők.',
|
||||
engineTitle: 'Csinálj saját store-t',
|
||||
engineDesc: 'Itt semmi sem ránk van szabva. Mindkét store csak egy konfigurációs fájl egy nyílt motor fölött, ami bármilyen WarpEngine alapú oldallal működik: állítsd a saját katalógusodra, adj neki nevet és saját mappát, és a játékaid ugyanígy kerülnek fel egy gépre. A két motor a warpstore-on osztozik, ami mindkettőnek a katalógus-oldalt viszi, tehát egy harmadik fajta gép már csak egy adapter és egy konfiguráció, nem új program. Egy gépen több store is megfér egymás mellett anélkül, hogy egymás játékaihoz nyúlnának.',
|
||||
platformsTitle: 'Mi kerül fel',
|
||||
engineLine: 'Minden store egy config egy nyílt motor fölött — állítsd a sajátodra:',
|
||||
copy: 'Másolás',
|
||||
|
||||
desktop: {
|
||||
name: 'Desktop CLI',
|
||||
site: 'Teletype Games',
|
||||
tagline: 'Szkriptekhez és szerverekhez',
|
||||
installDesc: 'Futtasd azon a gépen, amin játszani szeretnél. Letölti a játékokat, a saját felhasználód alá telepíti őket, és felveszi az alkalmazás-menüdbe — nem kell rendszergazdai jog, és semmi nem kerül a rendszerbe.',
|
||||
installNote: 'A natív buildek egyenként több tíz megabájtosak, ezért az első futás eltart egy ideig. Amihez nincs natív buildünk, ahhoz a böngészős változatot megnyitó bejegyzés kerül a menübe — azokhoz internet kell. Ez ugyanaz a store, amit a fenti alkalmazás is telepít, tehát a kettő felváltva használható egy gépen.',
|
||||
useDesc: 'A store-nak van egy egyszerű CLI-je, a `paths` pedig megmutatja, hova került minden a gépeden:',
|
||||
},
|
||||
batocera: {
|
||||
name: 'Batocera',
|
||||
site: 'Batocera projekt',
|
||||
tagline: 'A plug-and-play retró disztribúció',
|
||||
installDesc: 'Futtasd ezt SSH-n a Batocera gépen. Telepíti a motort, kiírja a konfigurációt, létrehozza a Ports bejegyzést és lefuttatja az első szinkront.',
|
||||
installNote: 'Utána indítsd újra az EmulationStationt, hogy megjelenjenek a játékok:',
|
||||
useDesc: 'A gépen: Ports ▸ „Teletype Games Store”. SSH-n keresztül egy egyszerű CLI áll rendelkezésre:',
|
||||
tagline: 'A dugd-be-és-megy retro disztró',
|
||||
},
|
||||
retroarch: {
|
||||
name: 'RetroArch',
|
||||
site: 'RetroArch projekt',
|
||||
tagline: 'Asztali gép, Android, Steam Deck, kézikonzol',
|
||||
installDesc: 'Futtasd azon a gépen, amin a RetroArch van. A retroarch.cfg-ből olvassa ki, hol vannak a playlistjeid, a borítóképek és a magok, majd platformonként egy playlistet ír borítóképpel.',
|
||||
installNote: 'Utána indítsd újra a RetroArch-ot — a menüt indulásnál építi. Ha a VICE x64 és a TIC-80 mag nincs telepítve, akkor is működik: a RetroArch az első indításnál rákérdez, melyik maggal futtassa.',
|
||||
useDesc: 'A store-nak van egy egyszerű CLI-je. Szinkron előtt zárd be a RetroArch-ot: kilépéskor visszaírja a playlistjeit, tehát a futó példány alatti szinkron elveszhet.',
|
||||
site: 'RetroArch',
|
||||
tagline: 'Bármi, amin fut a RetroArch',
|
||||
},
|
||||
},
|
||||
code: {
|
||||
|
||||
@@ -1,43 +1,46 @@
|
||||
<template>
|
||||
<header class="hero-section-gradient from-emerald-700 to-teal-800 py-16">
|
||||
<header class="hero-section-gradient from-emerald-700 to-teal-800 py-10">
|
||||
<div class="hero-container">
|
||||
<h1 class="hero-title">{{ t('stores.title') }}</h1>
|
||||
<p class="hero-subtitle text-emerald-50">{{ t('stores.subtitle') }}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main-container py-12 px-4 mt-0">
|
||||
<div class="st-panel">
|
||||
<p class="st-lead">{{ t('stores.lead') }}</p>
|
||||
</div>
|
||||
<main class="main-container py-8 px-4 mt-0">
|
||||
<!-- Two ways in, and almost everybody wants the first one. A thin switcher rather
|
||||
than a page of prose: whoever needs the detail follows a link to the wiki. -->
|
||||
<nav class="st-views" role="tablist" :aria-label="t('stores.title')">
|
||||
<button
|
||||
class="st-view" :class="{ 'st-view-active': view === 'client' }"
|
||||
role="tab" :aria-selected="view === 'client'" @click="selectView('client')"
|
||||
>
|
||||
<i class="fa-solid fa-window-maximize"></i> {{ t('stores.viewClient') }}
|
||||
</button>
|
||||
<button
|
||||
class="st-view" :class="{ 'st-view-active': view === 'others' }"
|
||||
role="tab" :aria-selected="view === 'others'" @click="selectView('others')"
|
||||
>
|
||||
<i class="fa-solid fa-terminal"></i> {{ t('stores.viewOthers') }}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- The app first, and not behind a tab: it is what most visitors want, it is the
|
||||
only way in on Windows (there is no `curl … | sh` there), and it drives any
|
||||
store the site's registry offers. The integrations below are for the machines
|
||||
where a window is the wrong shape. -->
|
||||
<section class="st-section st-app">
|
||||
<h2 class="st-app-title">
|
||||
<i class="fa-solid fa-window-maximize st-app-icon"></i> {{ t('stores.clientTitle') }}
|
||||
</h2>
|
||||
<section v-if="view === 'client'" class="st-section st-app">
|
||||
<div class="st-app-grid">
|
||||
<div>
|
||||
<p class="st-app-lead">{{ t('stores.clientDesc') }}</p>
|
||||
<ul class="st-client-points">
|
||||
<li><i class="fa-solid fa-check"></i> {{ t('stores.clientPointSetup') }}</li>
|
||||
<li><i class="fa-solid fa-check"></i> {{ t('stores.clientPointBrowse') }}</li>
|
||||
<li><i class="fa-solid fa-check"></i> {{ t('stores.clientPointSame') }}</li>
|
||||
</ul>
|
||||
<div class="st-links">
|
||||
<a :href="client.releasesUrl" target="_blank" rel="noopener noreferrer"
|
||||
class="st-link st-link-cta">
|
||||
<a
|
||||
:href="client.releasesUrl" target="_blank" rel="noopener noreferrer"
|
||||
class="st-link st-link-cta"
|
||||
>
|
||||
<i class="fa-solid fa-download"></i> {{ t('stores.clientDownload') }}
|
||||
</a>
|
||||
<a :href="client.repoUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-dark">
|
||||
<i class="fa-solid fa-code-branch"></i> {{ t('stores.repo') }}
|
||||
</a>
|
||||
<a :href="client.wikiUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-indigo">
|
||||
<i class="fa-solid fa-book"></i> {{ t('stores.docs') }}
|
||||
</a>
|
||||
<a :href="client.repoUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-dark">
|
||||
<i class="fa-solid fa-code-branch"></i> {{ t('stores.repo') }}
|
||||
</a>
|
||||
</div>
|
||||
<p class="st-app-platforms">
|
||||
<i class="fa-brands fa-linux"></i> <i class="fa-brands fa-apple"></i>
|
||||
@@ -45,22 +48,16 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- The window itself, because a store is a thing you look at. Eager, with its
|
||||
dimensions given: it sits in the first screen, so lazy-loading it would only
|
||||
buy a reflow. -->
|
||||
<!-- Eager, with its dimensions given: it sits in the first screen, so lazy
|
||||
loading it would only buy a reflow. -->
|
||||
<figure class="st-app-shot">
|
||||
<img :src="clientScreenshot" :alt="t('stores.clientShotAlt')" width="2304" height="1664">
|
||||
</figure>
|
||||
</div>
|
||||
<p class="st-note">{{ t('stores.clientNote') }}</p>
|
||||
</section>
|
||||
|
||||
<!-- The other ways in. Same catalog, same engine underneath; what differs is the
|
||||
host, so where you are installing is the only thing to choose. -->
|
||||
<section class="st-section">
|
||||
<h2 class="st-section-title">{{ t('stores.chooseTitle') }}</h2>
|
||||
<p class="st-section-desc">{{ t('stores.chooseDesc') }}</p>
|
||||
<div class="st-devices" role="tablist">
|
||||
<template v-else>
|
||||
<div class="st-devices" role="tablist" :aria-label="t('stores.viewOthers')">
|
||||
<button
|
||||
v-for="d in devices"
|
||||
:key="d.id"
|
||||
@@ -76,54 +73,38 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="st-links">
|
||||
<a :href="current.repoUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-dark">
|
||||
<i class="fa-solid fa-code-branch"></i> {{ t('stores.repo') }}
|
||||
</a>
|
||||
<a :href="current.wikiUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-indigo">
|
||||
<i class="fa-solid fa-book"></i> {{ t('stores.docs') }}
|
||||
</a>
|
||||
<a :href="current.projectUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-emerald">
|
||||
<i :class="current.icon"></i> {{ t(`stores.${device}.site`) }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="st-section">
|
||||
<p class="st-cmd-label">{{ t('stores.installTitle') }}</p>
|
||||
<CommandBlock :command="current.installCmd" />
|
||||
<template v-if="current.afterInstallCmd">
|
||||
<p class="st-cmd-label">{{ t('stores.installThen') }}</p>
|
||||
<CommandBlock :command="current.afterInstallCmd" />
|
||||
</template>
|
||||
|
||||
<section class="st-section">
|
||||
<h2 class="st-section-title">
|
||||
<span class="st-step">1</span> {{ t('stores.installTitle') }}
|
||||
</h2>
|
||||
<p class="st-section-desc">{{ t(`stores.${device}.installDesc`) }}</p>
|
||||
<CommandBlock :command="current.installCmd" />
|
||||
<template v-if="current.afterInstallCmd">
|
||||
<p class="st-note">{{ t(`stores.${device}.installNote`) }}</p>
|
||||
<CommandBlock :command="current.afterInstallCmd" />
|
||||
</template>
|
||||
<p v-else class="st-note">{{ t(`stores.${device}.installNote`) }}</p>
|
||||
</section>
|
||||
<p class="st-cmd-label">{{ t('stores.useTitle') }}</p>
|
||||
<CommandBlock :command="current.cliSnippet" />
|
||||
|
||||
<section class="st-section">
|
||||
<h2 class="st-section-title">
|
||||
<span class="st-step">2</span> {{ t('stores.useTitle') }}
|
||||
</h2>
|
||||
<p class="st-section-desc">{{ t(`stores.${device}.useDesc`) }}</p>
|
||||
<CommandBlock :command="current.cliSnippet" />
|
||||
</section>
|
||||
<p class="st-cmd-label">{{ t('stores.uninstallTitle') }}</p>
|
||||
<CommandBlock :command="current.uninstallCmd" />
|
||||
|
||||
<section class="st-section">
|
||||
<h2 class="st-section-title">
|
||||
<span class="st-step">3</span> {{ t('stores.uninstallTitle') }}
|
||||
</h2>
|
||||
<p class="st-section-desc">{{ t('stores.uninstallDesc') }}</p>
|
||||
<CommandBlock :command="current.uninstallCmd" />
|
||||
<p class="st-note">{{ t('stores.uninstallNote') }}</p>
|
||||
</section>
|
||||
<div class="st-links">
|
||||
<a :href="current.wikiUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-indigo">
|
||||
<i class="fa-solid fa-book"></i> {{ t('stores.docs') }}
|
||||
</a>
|
||||
<a :href="current.repoUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-dark">
|
||||
<i class="fa-solid fa-code-branch"></i> {{ t('stores.repo') }}
|
||||
</a>
|
||||
<a :href="current.projectUrl" target="_blank" rel="noopener noreferrer" class="st-link st-link-emerald">
|
||||
<i :class="current.icon"></i> {{ t(`stores.${device}.site`) }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- Below here nothing depends on the device: the cartridges, and the
|
||||
engines both stores are built from, are the same either way. -->
|
||||
<!-- The same either way: which catalog platforms end up installed. A list, not a
|
||||
paragraph. -->
|
||||
<section class="st-section">
|
||||
<h2 class="st-section-title">{{ t('stores.platformsTitle') }}</h2>
|
||||
<p class="st-section-desc">{{ t('stores.platformsDesc') }}</p>
|
||||
<ul class="st-platforms">
|
||||
<li v-for="p in platforms" :key="p.platform" class="st-platform">
|
||||
<i :class="p.icon" class="st-platform-icon"></i>
|
||||
@@ -133,20 +114,13 @@
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="st-section st-engine">
|
||||
<h2 class="st-section-title">
|
||||
<i class="fa-solid fa-cubes st-engine-icon"></i> {{ t('stores.engineTitle') }}
|
||||
</h2>
|
||||
<p class="st-section-desc">{{ t('stores.engineDesc') }}</p>
|
||||
<div class="st-links">
|
||||
<a
|
||||
v-for="e in engines" :key="e.name" :href="e.url" target="_blank" rel="noopener noreferrer"
|
||||
class="st-link st-link-slate"
|
||||
>
|
||||
<i class="fa-solid fa-code-branch"></i> {{ e.name }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<p class="st-engine-line">
|
||||
{{ t('stores.engineLine') }}
|
||||
<a
|
||||
v-for="e in engines" :key="e.name" :href="e.url" target="_blank" rel="noopener noreferrer"
|
||||
class="st-engine-link"
|
||||
>{{ e.name }}</a>
|
||||
</p>
|
||||
|
||||
<div class="st-back">
|
||||
<RouterLink to="/catalog" class="st-back-link">{{ t('catalogShow.back') }}</RouterLink>
|
||||
@@ -236,12 +210,23 @@ const devices = [
|
||||
},
|
||||
]
|
||||
|
||||
// `?device=` keeps a link to one of the two shareable, and lets /batocera
|
||||
// redirect here without losing which store the visitor came for.
|
||||
type ViewId = 'client' | 'others'
|
||||
|
||||
// `?view=` and `?device=` keep a link shareable, and let /batocera redirect here without
|
||||
// losing which store the visitor came for. Query only: the router does not scroll for a
|
||||
// query-only change, so switching does not throw the page back to the top.
|
||||
const initial = devices.some((d) => d.id === route.query.device)
|
||||
? (route.query.device as DeviceId)
|
||||
: 'desktop'
|
||||
const device = ref<DeviceId>(initial)
|
||||
// The client view is what most visitors want, so it is the default — except when the URL
|
||||
// names a device, which is how /batocera and /retroarch redirect here. Somebody arriving
|
||||
// from those wants the integrations, not the app.
|
||||
const view = ref<ViewId>(
|
||||
route.query.view === 'others' || (route.query.view === undefined && route.query.device !== undefined)
|
||||
? 'others'
|
||||
: 'client',
|
||||
)
|
||||
const current = computed(() => devices.find((d) => d.id === device.value) ?? devices[0])
|
||||
// The app is not tied to a device tab: it drives whichever store the site's registry
|
||||
// offers, so it stands above the chooser rather than inside it.
|
||||
@@ -249,7 +234,12 @@ const client = CLIENT
|
||||
|
||||
function select(id: DeviceId) {
|
||||
device.value = id
|
||||
router.replace({ query: { ...route.query, device: id } })
|
||||
void router.replace({ query: { ...route.query, device: id } })
|
||||
}
|
||||
|
||||
function selectView(id: ViewId) {
|
||||
view.value = id
|
||||
void router.replace({ query: { ...route.query, view: id } })
|
||||
}
|
||||
|
||||
const platforms = [
|
||||
@@ -265,12 +255,6 @@ const engines = [
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.st-panel {
|
||||
@apply bg-white rounded-2xl shadow-xl border border-gray-100 p-6 md:p-8;
|
||||
}
|
||||
.st-lead {
|
||||
@apply text-gray-600 leading-relaxed;
|
||||
}
|
||||
.st-links {
|
||||
@apply flex flex-wrap gap-3;
|
||||
}
|
||||
@@ -278,7 +262,6 @@ const engines = [
|
||||
@apply inline-flex items-center gap-2 font-bold py-2.5 px-5 rounded-xl text-sm text-white transition-all active:scale-95;
|
||||
}
|
||||
.st-link-dark { @apply bg-slate-800 hover:bg-slate-900 shadow-lg shadow-slate-800/20; }
|
||||
.st-link-slate { @apply bg-slate-600 hover:bg-slate-700 shadow-lg shadow-slate-600/20; }
|
||||
.st-link-indigo { @apply bg-indigo-600 hover:bg-indigo-700 shadow-lg shadow-indigo-600/20; }
|
||||
.st-link-emerald { @apply bg-emerald-600 hover:bg-emerald-700 shadow-lg shadow-emerald-600/20; }
|
||||
|
||||
@@ -288,15 +271,6 @@ const engines = [
|
||||
.st-section-title {
|
||||
@apply flex items-center gap-3 text-xl font-bold text-gray-900 mb-2;
|
||||
}
|
||||
.st-step {
|
||||
@apply inline-flex items-center justify-center w-7 h-7 rounded-full bg-emerald-100 text-emerald-700 text-sm font-bold;
|
||||
}
|
||||
.st-section-desc {
|
||||
@apply text-gray-600 mb-4;
|
||||
}
|
||||
.st-note {
|
||||
@apply text-gray-600 mt-4 mb-3 text-sm;
|
||||
}
|
||||
|
||||
.st-devices {
|
||||
@apply grid grid-cols-1 sm:grid-cols-3 gap-3 mb-6;
|
||||
@@ -320,20 +294,33 @@ const engines = [
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
|
||||
.st-engine {
|
||||
@apply bg-gray-50;
|
||||
/* The switcher is meant to be almost nothing: two words and a line under the active one. */
|
||||
.st-views {
|
||||
@apply flex gap-1 border-b border-gray-200 mb-6;
|
||||
}
|
||||
.st-view {
|
||||
@apply flex items-center gap-2 px-4 py-2 -mb-px text-sm font-semibold text-gray-500 border-b-2 border-transparent transition-colors hover:text-gray-800;
|
||||
}
|
||||
.st-view-active {
|
||||
@apply text-emerald-700 border-emerald-500;
|
||||
}
|
||||
.st-cmd-label {
|
||||
@apply text-xs font-semibold uppercase tracking-wide text-gray-400 mt-4 mb-1;
|
||||
}
|
||||
.st-cmd-label:first-child {
|
||||
@apply mt-0;
|
||||
}
|
||||
.st-engine-line {
|
||||
@apply text-sm text-gray-500 mb-6 flex flex-wrap items-center gap-x-3 gap-y-1;
|
||||
}
|
||||
.st-engine-link {
|
||||
@apply text-indigo-600 hover:underline font-mono text-xs;
|
||||
}
|
||||
/* The app leads the page, so it is the one section that looks like an offer rather
|
||||
than a paragraph. */
|
||||
.st-app {
|
||||
@apply bg-gradient-to-br from-emerald-50 to-teal-50 border-emerald-200;
|
||||
}
|
||||
.st-app-title {
|
||||
@apply text-2xl font-bold text-gray-900 mb-2 flex items-center gap-3;
|
||||
}
|
||||
.st-app-icon {
|
||||
@apply text-emerald-500 text-xl;
|
||||
}
|
||||
.st-app-lead {
|
||||
@apply text-gray-700 text-lg leading-relaxed mb-4;
|
||||
}
|
||||
@@ -352,18 +339,6 @@ const engines = [
|
||||
.st-link-cta {
|
||||
@apply bg-emerald-600 text-white hover:bg-emerald-700 text-base px-5 py-2.5 font-semibold;
|
||||
}
|
||||
.st-client-points {
|
||||
@apply space-y-2 mb-5;
|
||||
}
|
||||
.st-client-points li {
|
||||
@apply flex items-start gap-2 text-gray-700;
|
||||
}
|
||||
.st-client-points i {
|
||||
@apply text-emerald-500 mt-1 text-sm;
|
||||
}
|
||||
.st-engine-icon {
|
||||
@apply text-gray-400 text-lg;
|
||||
}
|
||||
.st-platforms {
|
||||
@apply grid grid-cols-1 sm:grid-cols-2 gap-3;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,13 @@ export const router = createRouter({
|
||||
...buildsRouter,
|
||||
...storesRouter,
|
||||
],
|
||||
scrollBehavior() {
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
// Back and forward land where the visitor was.
|
||||
if (savedPosition) return savedPosition
|
||||
// A query-only change is not a new page. The stores page writes its switcher into the
|
||||
// URL so a link stays shareable, and throwing the reader back to the top for that
|
||||
// felt like a reload.
|
||||
if (to.path === from.path) return false
|
||||
return { top: 0 }
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user