ENH - Apps

This commit is contained in:
Eichenholz
2026-08-03 16:46:19 +02:00
parent 6addba279d
commit fcf59da357
40 changed files with 1335 additions and 219 deletions
+110 -46
View File
@@ -1,37 +1,52 @@
<script setup lang="ts">
import {
Gamepad2,
Grid2X2,
Rocket,
Search,
Sparkles,
UserRound,
} from 'lucide-vue-next'
import { computed, ref } from 'vue'
import { Search } from 'lucide-vue-next'
import { useMediaStore } from '@/stores/media'
import { usePhoneStore } from '@/stores/phone'
const phone = usePhoneStore()
const media = useMediaStore()
const tab = ref('today')
const tab = ref<'today' | 'apps' | 'games' | 'arcade' | 'search'>('today')
const query = ref('')
const tabs = ['today', 'apps', 'games', 'arcade', 'search']
const tabs = [
{ id: 'today', icon: Sparkles },
{ id: 'apps', icon: Grid2X2 },
{ id: 'games', icon: Gamepad2 },
{ id: 'arcade', icon: Rocket },
{ id: 'search', icon: Search },
] as const
const catalog = [
{
id: 'orbit',
title: 'Orbit',
subtitle: 'Apps.appStore.catalog.orbit',
gradient: 'linear-gradient(135deg,#6f5cff,#29d3c2)',
gradient: 'linear-gradient(145deg,#453bd1,#75e8ff)',
},
{
id: 'studio',
title: 'Studio',
subtitle: 'Apps.appStore.catalog.studio',
gradient: 'linear-gradient(135deg,#ff8b4a,#ff2d75)',
gradient: 'linear-gradient(145deg,#ff8b4a,#ff2d75)',
},
{
id: 'trail',
title: 'Trail',
subtitle: 'Apps.appStore.catalog.trail',
gradient: 'linear-gradient(135deg,#4bd37b,#147ec1)',
gradient: 'linear-gradient(145deg,#4bd37b,#147ec1)',
},
{
id: 'puzzle',
id: 'prism',
title: 'Prism',
subtitle: 'Apps.appStore.catalog.prism',
gradient: 'linear-gradient(135deg,#ffd84d,#7b36dc)',
gradient: 'linear-gradient(145deg,#ffd84d,#7b36dc)',
},
]
const shown = computed(() =>
@@ -39,53 +54,102 @@ const shown = computed(() =>
item.title.toLowerCase().includes(query.value.toLowerCase()),
),
)
const date = new Intl.DateTimeFormat(phone.lang, {
day: 'numeric',
month: 'long',
}).format(new Date())
</script>
<template>
<main class="native-app store-app">
<header class="app-header">
<small>{{ phone.t('Apps.appStore.eyebrow') }}</small>
<h1>{{ phone.t(`Apps.appStore.tabs.${tab}`) }}</h1>
</header>
<div v-if="tab === 'search'" class="app-search">
<Search :size="17" /><input
v-model="query"
:placeholder="phone.t('Apps.appStore.searchPlaceholder')"
/>
</div>
<section class="store-feature">
<p>{{ phone.t('Apps.appStore.featured') }}</p>
<h2>{{ phone.t('Apps.appStore.heroTitle') }}</h2>
<span>{{ phone.t('Apps.appStore.heroBody') }}</span>
</section>
<section class="store-list">
<article v-for="item in shown" :key="item.id">
<div class="store-icon" :style="{ background: item.gradient }">
{{ item.title[0] }}
</div>
<main class="native-app reference-store">
<section class="store-scroll">
<header class="store-title">
<div>
<strong>{{ item.title }}</strong
><small>{{ phone.t(item.subtitle) }}</small>
<h1>{{ phone.t(`Apps.appStore.tabs.${tab}`) }}</h1>
<strong v-if="tab === 'today'">{{ date }}</strong>
</div>
<button type="button" @click="media.claimApp(item.id)">
{{
phone.t(
media.claimedApps.includes(item.id)
? 'Apps.appStore.open'
: 'Apps.appStore.get',
)
}}
</button>
</article>
<span><UserRound :size="22" /></span>
</header>
<div v-if="tab === 'search'" class="app-search">
<Search :size="17" /><input
v-model="query"
:placeholder="phone.t('Apps.appStore.searchPlaceholder')"
/>
</div>
<template v-if="tab === 'today'">
<article class="editorial-card editorial-card--sky">
<small>{{ phone.t('Apps.appStore.card.oneEyebrow') }}</small>
<h2>{{ phone.t('Apps.appStore.card.oneTitle') }}</h2>
<p>{{ phone.t('Apps.appStore.card.oneBody') }}</p>
<div class="editorial-orbit"></div>
</article>
<article class="editorial-card editorial-card--music">
<small>{{ phone.t('Apps.appStore.card.twoEyebrow') }}</small>
<h2>{{ phone.t('Apps.appStore.card.twoTitle') }}</h2>
<p>{{ phone.t('Apps.appStore.card.twoBody') }}</p>
<div class="editorial-wave" />
</article>
<div class="store-section-title">
<h2>{{ phone.t('Apps.appStore.communityTitle') }}</h2>
<p>{{ phone.t('Apps.appStore.communityBody') }}</p>
</div>
<article class="editorial-card editorial-card--play">
<small>{{ phone.t('Apps.appStore.card.threeEyebrow') }}</small>
<h2>{{ phone.t('Apps.appStore.card.threeTitle') }}</h2>
<p>{{ phone.t('Apps.appStore.card.threeBody') }}</p>
</article>
</template>
<template v-else>
<article v-if="tab !== 'search'" class="store-feature">
<p>{{ phone.t('Apps.appStore.featured') }}</p>
<h2>{{ phone.t('Apps.appStore.heroTitle') }}</h2>
<span>{{ phone.t('Apps.appStore.heroBody') }}</span>
</article>
<div class="store-section-title">
<h2>
{{
phone.t(
tab === 'games'
? 'Apps.appStore.playing'
: 'Apps.appStore.recommended',
)
}}
</h2>
<p>{{ phone.t('Apps.appStore.selected') }}</p>
</div>
<section class="store-list">
<article v-for="item in shown" :key="item.id">
<div class="store-icon" :style="{ background: item.gradient }">
{{ item.title[0] }}
</div>
<div>
<strong>{{ item.title }}</strong
><small>{{ phone.t(item.subtitle) }}</small>
</div>
<button type="button" @click="media.claimApp(item.id)">
{{
phone.t(
media.claimedApps.includes(item.id)
? 'Apps.appStore.open'
: 'Apps.appStore.get',
)
}}
</button>
</article>
</section>
</template>
</section>
<nav class="app-tabs">
<nav class="reference-tabbar">
<button
v-for="item in tabs"
:key="item"
:class="{ active: tab === item }"
:key="item.id"
:class="{ active: tab === item.id }"
type="button"
@click="tab = item"
@click="tab = item.id"
>
{{ phone.t(`Apps.appStore.tabs.${item}`) }}
<component :is="item.icon" :size="21" /><span>{{
phone.t(`Apps.appStore.tabs.${item.id}`)
}}</span>
</button>
</nav>
</main>