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
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

+712
View File
@@ -66,6 +66,7 @@ button {
}
.phone-screen {
position: relative;
container-type: size;
width: 100%;
height: 100%;
overflow: hidden;
@@ -207,11 +208,19 @@ button {
border-radius: 13px;
display: grid;
place-items: center;
overflow: hidden;
color: white;
box-shadow:
0 4px 13px #0004,
inset 0 1px 1px #ffffff55;
}
.app-icon > img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
pointer-events: none;
}
.app-icon-label {
width: 74px;
overflow: hidden;
@@ -246,6 +255,11 @@ button {
.app-icon--settings {
background: linear-gradient(145deg, #aeb3bd, #50535b);
}
.app-icon--image {
background: transparent;
box-shadow: none;
outline: none;
}
.app-dock {
position: absolute;
z-index: 8;
@@ -978,6 +992,704 @@ button {
.wallpaper-picker button.active {
border-color: #0a84ff;
}
/* Reference-accurate app surfaces. These remain scoped to the phone resource. */
.calculator-app {
padding: 54px 16px 27px;
}
.calculator-display {
height: 226px;
padding: 0 5px 4px;
font-size: 78px;
letter-spacing: -5px;
}
.calculator-pad {
gap: 11px 14px;
}
.calculator-pad button {
font-size: 28px;
font-weight: 400;
}
.reference-camera {
display: grid;
grid-template-rows: 83px minmax(0, 1fr) 190px;
padding: 0 0 24px;
background: #020202;
}
.camera-topbar {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: end;
padding: 45px 15px 11px;
}
.camera-topbar > div {
display: flex;
gap: 17px;
}
.camera-topbar > svg {
justify-self: end;
}
.camera-topbar button {
border: 0;
background: transparent;
color: inherit;
}
.camera-topbar .camera-chevron {
width: 34px;
height: 26px;
display: grid;
place-items: center;
border-radius: 20px;
background: #262629;
}
.reference-viewfinder {
position: relative;
min-height: 0;
overflow: hidden;
background:
radial-gradient(circle at 66% 21%, #ffd88baa, transparent 15%),
radial-gradient(circle at 45% 52%, #6d8ca5 0 6%, transparent 7%),
linear-gradient(150deg, #173542, #5b3e5b 56%, #13191c);
}
.reference-viewfinder::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
115deg,
transparent 45%,
#ffffff0f 46%,
transparent 48%
);
}
.focus-corner {
position: absolute;
z-index: 2;
width: 24px;
height: 24px;
border-color: white;
}
.focus-corner--tl {
top: 1px;
left: 1px;
border-top: 1px solid;
border-left: 1px solid;
}
.focus-corner--tr {
top: 1px;
right: 1px;
border-top: 1px solid;
border-right: 1px solid;
}
.focus-corner--bl {
bottom: 1px;
left: 1px;
border-bottom: 1px solid;
border-left: 1px solid;
}
.focus-corner--br {
right: 1px;
bottom: 1px;
border-right: 1px solid;
border-bottom: 1px solid;
}
.reference-viewfinder .camera-zoom {
z-index: 3;
bottom: 13px;
}
.reference-viewfinder .camera-zoom button {
display: inline-flex;
align-items: center;
justify-content: center;
background: #0008;
font-weight: 700;
}
.reference-viewfinder .camera-zoom small {
margin-left: 1px;
font-size: 8px;
}
.reference-camera-footer {
display: grid;
grid-template-rows: 43px 1fr;
background: #020202;
}
.camera-mode-strip {
display: flex;
align-items: center;
gap: 21px;
padding: 0 148px;
overflow: hidden;
}
.camera-mode-strip button {
flex: none;
padding: 0;
border: 0;
background: transparent;
color: white;
font-size: 10px;
font-weight: 650;
text-transform: uppercase;
white-space: nowrap;
}
.camera-mode-strip button.active {
color: #ffd60a;
}
.camera-shutter-row {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
padding: 0 31px 12px;
}
.camera-thumbnail {
width: 44px;
height: 44px;
border-radius: 4px;
}
.reference-shutter {
justify-self: center;
width: 62px;
height: 62px;
display: grid;
place-items: center;
border: 3px solid white;
border-radius: 50%;
background: white;
color: #111;
box-shadow: inset 0 0 0 3px #020202;
}
.reference-flip {
justify-self: end;
width: 42px;
height: 42px;
display: grid;
place-items: center;
border: 0;
border-radius: 50%;
background: #1c1c1e;
color: white;
}
.reference-tabbar {
position: absolute;
z-index: 6;
right: 0;
bottom: 21px;
left: 0;
height: 58px;
display: flex;
align-items: flex-start;
justify-content: space-around;
padding: 7px 5px 0;
border-top: 1px solid #ffffff18;
background: #101012dd;
backdrop-filter: blur(22px);
-webkit-backdrop-filter: blur(22px);
}
.reference-tabbar button {
min-width: 49px;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 0;
border: 0;
background: transparent;
color: #77777c;
font-size: 9px;
}
.reference-tabbar button.active {
color: #0a84ff;
}
.clock-app .app-tabs button.active {
color: #d99900;
}
.clock-app {
padding-top: 0;
background: #020202;
}
.reference-clock-header {
padding: 47px 16px 0;
}
.reference-clock-header nav {
display: flex;
justify-content: space-between;
color: #d99900;
}
.reference-clock-header nav button {
padding: 0;
border: 0;
background: transparent;
color: inherit;
font-size: 15px;
}
.reference-clock-header h1 {
margin: 14px 0 0;
padding-bottom: 11px;
border-bottom: 1px solid #ffffff1c;
font-size: 35px;
line-height: 1;
}
.clock-app .clock-content {
height: calc(100% - 155px);
padding: 0 16px 72px;
}
.world-clock-list > div {
min-height: 95px;
display: flex;
align-items: center;
border-bottom: 1px solid #ffffff1c;
}
.world-clock-list > div > div {
flex: 1;
display: flex;
flex-direction: column;
}
.world-clock-list small {
color: #8e8e93;
font-size: 11px;
}
.world-clock-list strong {
font-size: min(25px, 7cqw);
font-weight: 400;
}
.world-clock-list time {
font-size: min(42px, 11cqw);
font-weight: 200;
letter-spacing: -2px;
}
.reference-clock-tabs button.active {
color: #d99900;
}
.reference-photos {
padding: 0 0 79px;
background: #020202;
}
.photos-library,
.photos-scroll {
height: 100%;
overflow-y: auto;
}
.photos-floating-header {
position: sticky;
z-index: 3;
top: 0;
height: 95px;
display: flex;
align-items: flex-end;
gap: 8px;
padding: 47px 17px 14px;
background: linear-gradient(#000 55%, transparent);
}
.photos-floating-header > div {
display: flex;
flex: 1;
flex-direction: column;
}
.photos-floating-header strong {
font-size: 17px;
}
.photos-floating-header span {
font-size: 12px;
font-weight: 600;
}
.photos-floating-header button {
height: 27px;
padding: 0 12px;
border: 0;
border-radius: 18px;
background: #ffffff1d;
color: white;
font-size: 11px;
font-weight: 650;
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
}
.photos-floating-header button:last-child {
width: 28px;
padding: 0;
border-radius: 50%;
}
.reference-photo-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 2px;
}
.reference-photo-grid article {
aspect-ratio: 1;
}
.photos-count {
padding: 15px;
text-align: center;
font-size: 11px;
}
.photos-period {
position: sticky;
bottom: 5px;
width: max-content;
margin: 0 auto;
display: flex;
padding: 3px;
border-radius: 22px;
background: #303033aa;
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
}
.photos-period button {
padding: 5px 12px;
border: 0;
border-radius: 18px;
background: transparent;
color: #ddd;
font-size: 10px;
font-weight: 650;
}
.photos-period button.active {
background: #ffffff25;
color: white;
}
.photos-scroll {
padding: 82px 16px 20px;
}
.photos-scroll > h1 {
margin: 0 0 28px;
font-size: 34px;
}
.photos-section-title {
margin-top: 22px;
padding-top: 13px;
display: flex;
align-items: end;
border-top: 1px solid #ffffff1b;
}
.photos-section-title h2 {
flex: 1;
margin: 0;
font-size: 22px;
}
.photos-section-title button {
border: 0;
background: transparent;
color: #0a84ff;
}
.memory-card {
position: relative;
height: 410px;
margin-top: 12px;
padding: 16px;
display: flex;
flex-direction: column;
border-radius: 17px;
box-shadow: inset 0 -160px 100px #0008;
}
.memory-card > svg {
margin-left: auto;
}
.memory-card > div {
margin-top: auto;
display: flex;
flex-direction: column;
}
.memory-card strong {
font-size: 26px;
}
.memory-card span {
font-size: 11px;
letter-spacing: 2px;
text-transform: uppercase;
}
.featured-row {
display: flex;
gap: 9px;
margin-top: 12px;
overflow-x: auto;
}
.featured-row article {
width: 88%;
flex: none;
display: flex;
flex-direction: column;
}
.featured-row article > div {
aspect-ratio: 1;
border-radius: 10px;
}
.featured-row article span {
color: #777;
font-size: 12px;
}
.reference-photos .photo-grid {
height: auto;
margin-top: 13px;
padding-bottom: 10px;
}
.reference-store {
padding: 0 0 79px;
background: #0e0e0e;
}
.store-scroll {
height: 100%;
padding: 52px 19px 26px;
overflow-y: auto;
}
.store-title {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.store-title > div {
flex: 1;
display: flex;
align-items: baseline;
gap: 11px;
}
.store-title h1 {
margin: 0;
font-size: 34px;
}
.store-title strong {
color: #777;
font-size: 18px;
}
.store-title > span {
width: 37px;
height: 37px;
display: grid;
place-items: center;
border-radius: 50%;
background: linear-gradient(145deg, #3a3c46, #1b1c22);
}
.editorial-card {
position: relative;
height: 390px;
margin-bottom: 18px;
padding: 20px;
overflow: hidden;
border-radius: 22px;
box-shadow: 0 10px 28px #0007;
}
.editorial-card small {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
}
.editorial-card h2 {
width: 80%;
margin: 7px 0;
font-size: 28px;
line-height: 1.05;
}
.editorial-card p {
width: 80%;
margin: 0;
color: #ffffffbf;
font-size: 14px;
}
.editorial-card--sky {
background:
radial-gradient(
circle at 68% 63%,
#fff 0 4%,
#4de1fb 5% 9%,
transparent 10%
),
radial-gradient(circle at 65% 60%, #c653ff99, transparent 31%),
linear-gradient(145deg, #10144d, #4630a6 58%, #1bc7e2);
}
.editorial-orbit {
position: absolute;
right: -10px;
bottom: 30px;
width: 230px;
height: 230px;
display: grid;
place-items: center;
border: 2px solid #ffffff99;
border-radius: 50%;
font-size: 74px;
transform: rotate(-18deg);
}
.editorial-card--music {
background:
radial-gradient(circle at 60% 70%, #ffdd45, transparent 22%),
linear-gradient(155deg, #531849, #ca2c72 60%, #f48c46);
}
.editorial-wave {
position: absolute;
right: 16px;
bottom: 28px;
left: 16px;
height: 110px;
background: repeating-linear-gradient(
90deg,
#ffffffaa 0 4px,
transparent 4px 12px
);
clip-path: polygon(
0 45%,
10% 10%,
20% 82%,
30% 25%,
40% 70%,
50% 5%,
60% 85%,
70% 28%,
80% 68%,
90% 13%,
100% 55%,
100% 70%,
0 70%
);
}
.editorial-card--play {
height: 330px;
background:
radial-gradient(circle at 80% 70%, #64ffbe88, transparent 30%),
linear-gradient(145deg, #0d4137, #18236c);
}
.store-section-title {
padding: 14px 0 12px;
border-top: 1px solid #ffffff1c;
}
.store-section-title h2 {
margin: 0;
font-size: 19px;
}
.store-section-title p {
margin: 3px 0 0;
color: #777;
font-size: 13px;
}
.reference-store .store-feature {
margin: 0 0 20px;
}
.reference-store .store-list {
padding: 0;
}
.reference-settings {
padding: 0;
background: #0e0e0e;
}
.settings-scroll {
height: 100%;
padding: 69px 16px 38px;
overflow-y: auto;
}
.settings-scroll > h1 {
margin: 0 0 17px;
font-size: 35px;
line-height: 1;
}
.settings-search {
height: 36px;
display: flex;
align-items: center;
gap: 7px;
padding: 0 9px;
border-radius: 9px;
background: #1c1c1e;
color: #777;
}
.settings-search input {
width: 100%;
border: 0;
outline: 0;
background: transparent;
color: white;
font-size: 16px;
}
.settings-account {
margin: 17px 0;
padding: 10px;
display: flex;
align-items: center;
gap: 12px;
border-radius: 12px;
background: #1c1c1e;
}
.settings-account > span {
width: 58px;
height: 58px;
display: grid;
place-items: center;
border-radius: 50%;
background: linear-gradient(145deg, #596172, #22252c);
}
.settings-account > div {
flex: 1;
display: flex;
flex-direction: column;
}
.settings-account strong {
font-size: 17px;
}
.settings-account small {
color: #ccc;
font-size: 11px;
}
.reference-settings-group {
margin-bottom: 17px;
padding: 0 11px;
overflow: hidden;
border-radius: 12px;
background: #1c1c1e;
}
.reference-settings-group article {
min-height: 44px;
display: flex;
align-items: center;
gap: 12px;
}
.reference-settings-group article:not(:last-child) > div {
border-bottom: 1px solid #ffffff18;
}
.reference-settings-group article > div {
min-width: 0;
flex: 1;
align-self: stretch;
display: flex;
align-items: center;
justify-content: space-between;
}
.reference-settings-group article > div strong {
font-size: 14px;
font-weight: 400;
}
.reference-settings-group article > div small {
max-width: 100px;
overflow: hidden;
color: #999;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.reference-settings-group .settings-symbol {
width: 27px;
height: 27px;
flex: none;
border-radius: 6px;
}
.settings-chevron {
color: #555;
}
.reference-settings .wallpaper-picker button {
height: 105px;
}
* {
scrollbar-width: none;
}
*::-webkit-scrollbar {
display: none;
}
.camera-mode-strip {
padding: 0;
}
.camera-mode-strip button:first-child {
margin-left: -205px;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
+20 -2
View File
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { usePhoneStore } from '@/stores/phone'
@@ -18,6 +19,7 @@ const props = withDefaults(
const phone = usePhoneStore()
const router = useRouter()
const iconFailed = ref(false)
function launch(event: MouseEvent): void {
const button = event.currentTarget as HTMLElement
@@ -49,8 +51,24 @@ function launch(event: MouseEvent): void {
:aria-label="phone.t(app.labelKey)"
@click="launch"
>
<span class="app-icon" :class="app.iconClass" aria-hidden="true">
<component :is="app.icon" :size="compact ? 18 : 28" :stroke-width="2" />
<span
class="app-icon"
:class="[app.iconClass, { 'app-icon--image': !iconFailed }]"
aria-hidden="true"
>
<img
v-if="!iconFailed"
:src="app.iconImage"
alt=""
draggable="false"
@error="iconFailed = true"
/>
<component
:is="app.icon"
v-else
:size="compact ? 18 : 28"
:stroke-width="2"
/>
</span>
<span v-if="showLabel" class="app-icon-label">{{
phone.t(app.labelKey)
+3
View File
@@ -8,6 +8,9 @@ describe('app registry', () => {
expect(PHONE_APPS.every((app) => app.route === `/apps/${app.id}`)).toBe(
true,
)
expect(PHONE_APPS.every((app) => app.iconImage.endsWith('.webp'))).toBe(
true,
)
expect(
PHONE_APPS.filter((app) => app.dockOrder !== null)
.sort((a, b) => (a.dockOrder ?? 0) - (b.dockOrder ?? 0))
+12
View File
@@ -8,6 +8,12 @@ import {
} from 'lucide-vue-next'
import { defineAsyncComponent, markRaw } from 'vue'
import appStoreIcon from '@/assets/img/app-icons/apps.webp'
import calculatorIcon from '@/assets/img/app-icons/calculator.webp'
import cameraIcon from '@/assets/img/app-icons/camera.webp'
import clockIcon from '@/assets/img/app-icons/clock.webp'
import photosIcon from '@/assets/img/app-icons/gallery.webp'
import settingsIcon from '@/assets/img/app-icons/settings.webp'
import type { PhoneAppDefinition, PhoneAppId } from '@/types/apps'
export const PHONE_APPS: PhoneAppDefinition[] = [
@@ -19,6 +25,7 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
gridOrder: 0,
icon: markRaw(Calculator),
iconClass: 'app-icon--calculator',
iconImage: calculatorIcon,
id: 'calculator',
labelKey: 'Apps.calculator.name',
route: '/apps/calculator',
@@ -31,6 +38,7 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
gridOrder: 1,
icon: markRaw(Camera),
iconClass: 'app-icon--camera',
iconImage: cameraIcon,
id: 'camera',
labelKey: 'Apps.camera.name',
route: '/apps/camera',
@@ -43,6 +51,7 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
gridOrder: 2,
icon: markRaw(Clock3),
iconClass: 'app-icon--clock',
iconImage: clockIcon,
id: 'clock',
labelKey: 'Apps.clock.name',
route: '/apps/clock',
@@ -55,6 +64,7 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
gridOrder: 3,
icon: markRaw(Images),
iconClass: 'app-icon--photos',
iconImage: photosIcon,
id: 'photos',
labelKey: 'Apps.photos.name',
route: '/apps/photos',
@@ -67,6 +77,7 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
gridOrder: 4,
icon: markRaw(ShoppingBag),
iconClass: 'app-icon--store',
iconImage: appStoreIcon,
id: 'app-store',
labelKey: 'Apps.appStore.name',
route: '/apps/app-store',
@@ -79,6 +90,7 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
gridOrder: 5,
icon: markRaw(Settings),
iconClass: 'app-icon--settings',
iconImage: settingsIcon,
id: 'settings',
labelKey: 'Apps.settings.name',
route: '/apps/settings',
+62 -1
View File
@@ -26,6 +26,11 @@ const defaultLocales: LocaleTree = {
get: 'GET',
open: 'OPEN',
searchPlaceholder: 'Games, Apps, Stories and More',
communityTitle: 'Black Voices and Creators',
communityBody: 'Apps and games from the community',
playing: "What We're Playing",
recommended: 'Recommended for You',
selected: 'Great apps selected by our editors',
tabs: {
today: 'Today',
apps: 'Apps',
@@ -39,18 +44,42 @@ const defaultLocales: LocaleTree = {
trail: 'Explore nearby',
prism: 'A colorful puzzle',
},
card: {
oneEyebrow: 'App of the Day',
oneTitle: 'A universe in your pocket',
oneBody: 'Explore something extraordinary today.',
twoEyebrow: 'Now Trending',
twoTitle: 'Turn up your afternoon',
twoBody: 'Fresh sounds and stories picked for you.',
threeEyebrow: "Editors' Choice",
threeTitle: 'Play without limits',
threeBody: 'A new world is waiting.',
},
},
calculator: { name: 'Calculator' },
camera: {
name: 'Camera',
shutter: 'Take photo',
flip: 'Flip camera',
modes: { video: 'Video', photo: 'Photo', portrait: 'Portrait' },
flash: 'Toggle flash',
controls: 'Camera controls',
modes: {
timelapse: 'Timelapse',
slowMo: 'Slow-Mo',
cinematic: 'Cinematic',
video: 'Video',
photo: 'Photo',
portrait: 'Portrait',
pano: 'Pano',
},
},
clock: {
name: 'Clock',
lap: 'Lap',
minutes: 'Minutes',
add: 'Add clock',
today: 'Today',
offset: '+0HRS',
tabs: {
world: 'World Clock',
alarm: 'Alarm',
@@ -73,6 +102,19 @@ const defaultLocales: LocaleTree = {
items: 'items',
memories: 'Memories',
featured: 'City colors',
dateRange: '19 Apr7 May 2024',
place: 'Los Santos & more',
select: 'Select',
count: '3,042 Photos, 125 Videos',
years: 'Years',
months: 'Months',
days: 'Days',
allPhotos: 'All Photos',
seeAll: 'See All',
onThisDay: 'On This Day',
trip: 'MAR 2024 TRIP',
featuredPhotos: 'Featured Photos',
featuredDate: '30 Mar 2024',
tabs: {
library: 'Library',
forYou: 'For You',
@@ -94,6 +136,24 @@ const defaultLocales: LocaleTree = {
bluetooth: 'Bluetooth',
airplaneMode: 'Airplane Mode',
wallpaper: 'Wallpaper',
on: 'On',
accountName: 'Sky Citizen',
accountDetail: 'Apple ID, iCloud, Media & Purchases',
mobileServices: 'Mobile Services',
personalHotspot: 'Personal Hotspot',
vpn: 'VPN',
notifications: 'Notifications',
sounds: 'Sounds & Haptics',
focus: 'Focus',
screenTime: 'Screen Time',
general: 'General',
display: 'Display & Brightness',
homeScreen: 'Home Screen & App Library',
accessibility: 'Accessibility',
battery: 'Battery',
privacy: 'Privacy & Security',
passwords: 'Passwords',
wallpaperPicker: 'Built-in Wallpapers',
toggle: {
wifi: 'Toggle Wi-Fi',
bluetooth: 'Toggle Bluetooth',
@@ -109,6 +169,7 @@ const defaultLocales: LocaleTree = {
Common: {
cancel: 'Cancel',
close: 'Close',
edit: 'Edit',
home: 'Home',
pause: 'Pause',
phone: 'Phone',
+1
View File
@@ -23,6 +23,7 @@ export type PhoneAppDefinition = {
gridOrder: number
icon: Component
iconClass: string
iconImage: string
id: PhoneAppId
labelKey: string
route: `/apps/${PhoneAppId}`
+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>
+99 -41
View File
@@ -1,68 +1,126 @@
<script setup lang="ts">
import { Camera, RotateCcw } from 'lucide-vue-next'
import {
Aperture,
Camera,
ChevronUp,
RotateCcw,
Zap,
ZapOff,
} from 'lucide-vue-next'
import { ref } from 'vue'
import { useMediaStore } from '@/stores/media'
import { usePhoneStore } from '@/stores/phone'
const media = useMediaStore()
const phone = usePhoneStore()
const flash = ref(false)
const modes = ['video', 'photo', 'portrait'] as const
const flashEnabled = ref(false)
const modes = [
'timelapse',
'slowMo',
'cinematic',
'video',
'photo',
'portrait',
'pano',
] as const
function capture(): void {
flash.value = true
media.capture()
setTimeout(() => {
flash.value = false
}, 120)
window.setTimeout(() => (flash.value = false), 120)
}
</script>
<template>
<main class="native-app camera-app">
<div
class="camera-viewfinder"
<main class="native-app reference-camera">
<header class="camera-topbar">
<div>
<button
type="button"
:aria-label="phone.t('Apps.camera.flash')"
@click="flashEnabled = !flashEnabled"
>
<Zap v-if="flashEnabled" :size="22" />
<ZapOff v-else :size="22" />
</button>
<Aperture :size="22" />
</div>
<button
class="camera-chevron"
type="button"
:aria-label="phone.t('Apps.camera.controls')"
>
<ChevronUp :size="20" />
</button>
<Aperture :size="22" />
</header>
<section
class="reference-viewfinder"
:class="`camera-facing--${media.cameraFacing}`"
>
<div class="camera-flash" :class="{ active: flash }" />
<div class="camera-grid" />
<i
v-for="corner in ['tl', 'tr', 'bl', 'br']"
:key="corner"
:class="`focus-corner focus-corner--${corner}`"
/>
<div class="camera-zoom">
<button
v-for="zoom in [0.5, 1, 2]"
v-for="zoom in [0.5, 1]"
:key="zoom"
:class="{ active: media.cameraZoom === zoom }"
type="button"
@click="media.cameraZoom = zoom"
>
{{ zoom }}×
{{ zoom }}<small v-if="zoom === 1">x</small>
</button>
</div>
</div>
<div class="camera-modes">
<button
v-for="mode in modes"
:key="mode"
:class="{ active: media.cameraMode === mode }"
type="button"
@click="media.cameraMode = mode"
>
{{ phone.t(`Apps.camera.modes.${mode}`) }}
</button>
</div>
<div class="camera-controls">
<span /><button
class="shutter"
type="button"
:aria-label="phone.t('Apps.camera.shutter')"
@click="capture"
>
<Camera :size="26" /></button
><button
type="button"
:aria-label="phone.t('Apps.camera.flip')"
@click="
media.cameraFacing = media.cameraFacing === 'rear' ? 'front' : 'rear'
"
>
<RotateCcw :size="23" />
</button>
</div>
</section>
<footer class="reference-camera-footer">
<nav class="camera-mode-strip">
<button
v-for="mode in modes"
:key="mode"
:class="{ active: media.cameraMode === mode }"
type="button"
@click="
mode === 'photo' || mode === 'portrait' || mode === 'video'
? (media.cameraMode = mode)
: undefined
"
>
{{ phone.t(`Apps.camera.modes.${mode}`) }}
</button>
</nav>
<div class="camera-shutter-row">
<div
class="camera-thumbnail"
:style="{ background: media.photos[0]?.gradient }"
/>
<button
class="reference-shutter"
type="button"
:aria-label="phone.t('Apps.camera.shutter')"
@click="capture"
>
<Camera :size="23" />
</button>
<button
class="reference-flip"
type="button"
:aria-label="phone.t('Apps.camera.flip')"
@click="
media.cameraFacing =
media.cameraFacing === 'rear' ? 'front' : 'rear'
"
>
<RotateCcw :size="22" />
</button>
</div>
</footer>
</main>
</template>
+30 -11
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { AlarmClock, Clock3, Plus, Timer, TimerReset } from 'lucide-vue-next'
import { useClockStore } from '@/stores/clock'
import { usePhoneStore } from '@/stores/phone'
@@ -35,7 +36,12 @@ const cities = [
{ key: 'london', zone: 'Europe/London' },
{ key: 'tokyo', zone: 'Asia/Tokyo' },
]
const tabs = ['world', 'alarm', 'stopwatch', 'timer'] as const
const tabs = [
{ id: 'world', icon: Clock3 },
{ id: 'alarm', icon: AlarmClock },
{ id: 'stopwatch', icon: Timer },
{ id: 'timer', icon: TimerReset },
] as const
function cityTime(zone: string): string {
return new Intl.DateTimeFormat(phone.lang, {
hour: '2-digit',
@@ -53,14 +59,25 @@ onBeforeUnmount(() => clearInterval(ticker))
<template>
<main class="native-app clock-app">
<header class="app-header">
<header class="reference-clock-header">
<nav>
<button type="button">{{ phone.t('Common.edit') }}</button
><button type="button" :aria-label="phone.t('Apps.clock.add')">
<Plus :size="25" />
</button>
</nav>
<h1>{{ phone.t(`Apps.clock.tabs.${tab}`) }}</h1>
</header>
<section class="clock-content">
<div v-if="tab === 'world'" class="settings-list">
<div v-for="city in cities" :key="city.key" class="settings-row">
<span>{{ phone.t(`Apps.clock.cities.${city.key}`) }}</span
><strong>{{ cityTime(city.zone) }}</strong>
<div v-if="tab === 'world'" class="world-clock-list">
<div v-for="city in cities" :key="city.key">
<div>
<small
>{{ phone.t('Apps.clock.today') }},
{{ phone.t('Apps.clock.offset') }}</small
><strong>{{ phone.t(`Apps.clock.cities.${city.key}`) }}</strong>
</div>
<time>{{ cityTime(city.zone) }}</time>
</div>
</div>
<div v-else-if="tab === 'alarm'" class="settings-list">
@@ -162,15 +179,17 @@ onBeforeUnmount(() => clearInterval(ticker))
</div>
</div>
</section>
<nav class="app-tabs">
<nav class="reference-tabbar reference-clock-tabs">
<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.clock.tabs.${item}`) }}
<component :is="item.icon" :size="22" /><span>{{
phone.t(`Apps.clock.tabs.${item.id}`)
}}</span>
</button>
</nav>
</main>
+109 -46
View File
@@ -1,71 +1,134 @@
<script setup lang="ts">
import { Heart, Images, Library, Search } 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 media = useMediaStore()
const phone = usePhoneStore()
const tab = ref('library')
const tab = ref<'library' | 'forYou' | 'albums' | 'search'>('library')
const query = ref('')
const tabs = ['library', 'forYou', 'albums', 'search']
const photos = computed(() =>
const tabs = [
{ id: 'library', icon: Library },
{ id: 'forYou', icon: Heart },
{ id: 'albums', icon: Images },
{ id: 'search', icon: Search },
] as const
const gallery = computed(() =>
Array.from(
{ length: 54 },
(_, index) => media.photos[index % media.photos.length],
),
)
const filtered = computed(() =>
media.photos.filter((photo) =>
phone.t(photo.titleKey).toLowerCase().includes(query.value.toLowerCase()),
),
)
</script>
<template>
<main class="native-app photos-app">
<header class="app-header">
<h1>{{ phone.t(`Apps.photos.tabs.${tab}`) }}</h1>
</header>
<div v-if="tab === 'search'" class="app-search">
<Search :size="17" /><input
v-model="query"
:placeholder="phone.t('Apps.photos.searchPlaceholder')"
/>
</div>
<section v-if="tab === 'albums'" class="album-cards">
<article>
<div
class="photo-tile"
:style="{ background: media.photos[0]?.gradient }"
<main class="native-app reference-photos">
<section v-if="tab === 'library'" class="photos-library">
<header class="photos-floating-header">
<div>
<strong>{{ phone.t('Apps.photos.dateRange') }}</strong
><span>{{ phone.t('Apps.photos.place') }}</span>
</div>
<button type="button">{{ phone.t('Apps.photos.select') }}</button
><button type="button"></button>
</header>
<div class="reference-photo-grid">
<article
v-for="(photo, index) in gallery"
:key="`${photo.id}-${index}`"
:style="{ background: photo.gradient }"
/>
<strong>{{ phone.t('Apps.photos.recents') }}</strong
><small
>{{ media.photos.length }} {{ phone.t('Apps.photos.items') }}</small
>
</article>
<article>
<div class="photo-tile favorites" />
<strong>{{ phone.t('Apps.photos.favorites') }}</strong
><small>2 {{ phone.t('Apps.photos.items') }}</small>
</article>
</section>
<section v-else-if="tab === 'forYou'" class="featured-photo">
<p>{{ phone.t('Apps.photos.memories') }}</p>
<div :style="{ background: media.photos[1]?.gradient }">
<span>{{ phone.t('Apps.photos.featured') }}</span>
</div>
<p class="photos-count">{{ phone.t('Apps.photos.count') }}</p>
<div class="photos-period">
<button>{{ phone.t('Apps.photos.years') }}</button
><button>{{ phone.t('Apps.photos.months') }}</button
><button>{{ phone.t('Apps.photos.days') }}</button
><button class="active">{{ phone.t('Apps.photos.allPhotos') }}</button>
</div>
</section>
<section v-else class="photo-grid">
<section v-else-if="tab === 'forYou'" class="photos-scroll">
<h1>{{ phone.t('Apps.photos.tabs.forYou') }}</h1>
<div class="photos-section-title">
<h2>{{ phone.t('Apps.photos.memories') }}</h2>
<button>{{ phone.t('Apps.photos.seeAll') }}</button>
</div>
<article
v-for="photo in photos"
:key="photo.id"
class="photo-tile"
:style="{ background: photo.gradient }"
:aria-label="phone.t(photo.titleKey)"
/>
class="memory-card"
:style="{ background: media.photos[1]?.gradient }"
>
<Heart :size="25" />
<div>
<strong>{{ phone.t('Apps.photos.onThisDay') }}</strong
><span>{{ phone.t('Apps.photos.trip') }}</span>
</div>
</article>
<div class="photos-section-title">
<h2>{{ phone.t('Apps.photos.featuredPhotos') }}</h2>
</div>
<div class="featured-row">
<article v-for="photo in media.photos.slice(0, 2)" :key="photo.id">
<div :style="{ background: photo.gradient }" />
<strong>{{ phone.t(photo.titleKey) }}</strong
><span>{{ phone.t('Apps.photos.featuredDate') }}</span>
</article>
</div>
</section>
<nav class="app-tabs">
<section v-else-if="tab === 'albums'" class="photos-scroll">
<h1>{{ phone.t('Apps.photos.tabs.albums') }}</h1>
<div class="album-cards">
<article>
<div
class="photo-tile"
:style="{ background: media.photos[0]?.gradient }"
/>
<strong>{{ phone.t('Apps.photos.recents') }}</strong
><small
>{{ media.photos.length }} {{ phone.t('Apps.photos.items') }}</small
>
</article>
<article>
<div class="photo-tile favorites" />
<strong>{{ phone.t('Apps.photos.favorites') }}</strong
><small>2 {{ phone.t('Apps.photos.items') }}</small>
</article>
</div>
</section>
<section v-else class="photos-scroll">
<h1>{{ phone.t('Apps.photos.tabs.search') }}</h1>
<div class="app-search">
<Search :size="17" /><input
v-model="query"
:placeholder="phone.t('Apps.photos.searchPlaceholder')"
/>
</div>
<div class="photo-grid">
<article
v-for="photo in filtered"
:key="photo.id"
class="photo-tile"
:style="{ background: photo.gradient }"
/>
</div>
</section>
<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.photos.tabs.${item}`) }}
<component :is="item.icon" :size="22" /><span>{{
phone.t(`Apps.photos.tabs.${item.id}`)
}}</span>
</button>
</nav>
</main>
+144 -54
View File
@@ -1,68 +1,158 @@
<script setup lang="ts">
import {
Accessibility,
BatteryMedium,
BellRing,
Bluetooth,
ChevronRight,
Clock3,
Focus,
Globe2,
Grid2X2,
KeyRound,
LockKeyhole,
Monitor,
Plane,
Radio,
Search,
Settings,
Smartphone,
Sun,
UserRound,
Volume2,
Wifi,
} from 'lucide-vue-next'
import { computed, ref } from 'vue'
import { Bluetooth, Search, Wifi, Radio } from 'lucide-vue-next'
import { usePhoneStore } from '@/stores/phone'
import { WALLPAPER_IDS } from '@/utils/preferences'
const phone = usePhoneStore()
const query = ref('')
const rows = [
{ key: 'wifi', icon: Wifi, color: '#1677ff' },
{ key: 'bluetooth', icon: Bluetooth, color: '#1677ff' },
{ key: 'airplaneMode', icon: Radio, color: '#ff9500' },
] as const
const shown = computed(() =>
rows.filter((row) =>
phone
.t(`Apps.settings.${row.key}`)
.toLowerCase()
.includes(query.value.toLowerCase()),
),
const groups = [
[
{ key: 'airplaneMode', icon: Plane, color: '#ff9500', toggle: true },
{
key: 'wifi',
icon: Wifi,
color: '#0a84ff',
value: 'SkyNet 5G',
toggle: true,
},
{
key: 'bluetooth',
icon: Bluetooth,
color: '#0a84ff',
valueKey: 'Apps.settings.on',
toggle: true,
},
{ key: 'mobileServices', icon: Radio, color: '#30d158' },
{ key: 'personalHotspot', icon: Smartphone, color: '#30d158' },
{ key: 'vpn', icon: Globe2, color: '#0a84ff' },
],
[
{ key: 'notifications', icon: BellRing, color: '#ff453a' },
{ key: 'sounds', icon: Volume2, color: '#ff2d55' },
{ key: 'focus', icon: Focus, color: '#5e5ce6' },
{ key: 'screenTime', icon: Clock3, color: '#5e5ce6' },
],
[
{ key: 'general', icon: Settings, color: '#8e8e93' },
{ key: 'display', icon: Sun, color: '#0a84ff' },
{ key: 'homeScreen', icon: Grid2X2, color: '#5e5ce6' },
{ key: 'accessibility', icon: Accessibility, color: '#0a84ff' },
{ key: 'wallpaper', icon: Monitor, color: '#64d2ff' },
{ key: 'battery', icon: BatteryMedium, color: '#30d158' },
{ key: 'privacy', icon: LockKeyhole, color: '#0a84ff' },
],
[{ key: 'passwords', icon: KeyRound, color: '#636366' }],
]
const filteredGroups = computed(() =>
groups
.map((group) =>
group.filter((row) =>
phone
.t(`Apps.settings.${row.key}`)
.toLowerCase()
.includes(query.value.toLowerCase()),
),
)
.filter((group) => group.length),
)
</script>
<template>
<main class="native-app settings-app">
<header class="app-header">
<main class="native-app reference-settings">
<section class="settings-scroll">
<h1>{{ phone.t('Apps.settings.name') }}</h1>
</header>
<div class="app-search">
<Search :size="17" /><input
v-model="query"
:placeholder="phone.t('Apps.settings.searchPlaceholder')"
/>
</div>
<section class="settings-list">
<div v-for="row in shown" :key="row.key" class="settings-row">
<span class="settings-symbol" :style="{ background: row.color }"
><component :is="row.icon" :size="16" /></span
><span>{{ phone.t(`Apps.settings.${row.key}`) }}</span
><button
class="ios-switch"
:class="{ active: phone.preferences.settings[row.key] }"
type="button"
:aria-label="phone.t(`Apps.settings.toggle.${row.key}`)"
@click="
phone.setSetting(row.key, !phone.preferences.settings[row.key])
"
>
<span />
</button>
<div class="settings-search">
<Search :size="17" /><input
v-model="query"
:placeholder="phone.t('Apps.settings.searchPlaceholder')"
/>
</div>
</section>
<h2 class="settings-section-title">
{{ phone.t('Apps.settings.wallpaper') }}
</h2>
<section class="wallpaper-picker">
<button
v-for="wallpaper in WALLPAPER_IDS"
:key="wallpaper"
type="button"
:class="[
`wallpaper--${wallpaper}`,
{ active: phone.preferences.settings.wallpaper === wallpaper },
]"
:aria-label="phone.t(`Apps.settings.wallpapers.${wallpaper}`)"
@click="phone.setWallpaper(wallpaper)"
/>
<article class="settings-account">
<span><UserRound :size="34" /></span>
<div>
<strong>{{ phone.t('Apps.settings.accountName') }}</strong
><small>{{ phone.t('Apps.settings.accountDetail') }}</small>
</div>
<ChevronRight :size="19" />
</article>
<section
v-for="(group, groupIndex) in filteredGroups"
:key="groupIndex"
class="reference-settings-group"
>
<article v-for="row in group" :key="row.key">
<span class="settings-symbol" :style="{ background: row.color }"
><component :is="row.icon" :size="16"
/></span>
<div>
<strong>{{ phone.t(`Apps.settings.${row.key}`) }}</strong
><small v-if="row.value">{{ row.value }}</small
><small v-else-if="row.valueKey">{{ phone.t(row.valueKey) }}</small>
</div>
<button
v-if="row.toggle"
class="ios-switch"
:class="{
active:
phone.preferences.settings[
row.key as 'airplaneMode' | 'wifi' | 'bluetooth'
],
}"
type="button"
:aria-label="phone.t(`Apps.settings.toggle.${row.key}`)"
@click="
phone.setSetting(
row.key as 'airplaneMode' | 'wifi' | 'bluetooth',
!phone.preferences.settings[
row.key as 'airplaneMode' | 'wifi' | 'bluetooth'
],
)
"
>
<span /></button
><ChevronRight v-else :size="18" class="settings-chevron" />
</article>
</section>
<h2 class="settings-section-title">
{{ phone.t('Apps.settings.wallpaperPicker') }}
</h2>
<section class="wallpaper-picker">
<button
v-for="wallpaper in WALLPAPER_IDS"
:key="wallpaper"
type="button"
:class="[
`wallpaper--${wallpaper}`,
{ active: phone.preferences.settings.wallpaper === wallpaper },
]"
:aria-label="phone.t(`Apps.settings.wallpapers.${wallpaper}`)"
@click="phone.setWallpaper(wallpaper)"
/>
</section>
</section>
</main>
</template>
+19 -5
View File
@@ -2,7 +2,7 @@ Locales["en"] = {
CommandDescription = "Open your phone.",
Nui = {
Common = {
cancel = "Cancel", close = "Close", home = "Home", pause = "Pause",
cancel = "Cancel", close = "Close", edit = "Edit", home = "Home", pause = "Pause",
phone = "Phone", phoneStatus = "Phone status", reset = "Reset",
search = "Search", start = "Start", stop = "Stop",
},
@@ -21,11 +21,11 @@ Locales["en"] = {
Apps = {
calculator = { name = "Calculator" },
camera = {
name = "Camera", shutter = "Take photo", flip = "Flip camera",
modes = { video = "Video", photo = "Photo", portrait = "Portrait" },
name = "Camera", shutter = "Take photo", flip = "Flip camera", flash = "Toggle flash", controls = "Camera controls",
modes = { timelapse = "Timelapse", slowMo = "Slow-Mo", cinematic = "Cinematic", video = "Video", photo = "Photo", portrait = "Portrait", pano = "Pano" },
},
clock = {
name = "Clock", lap = "Lap", minutes = "Minutes",
name = "Clock", lap = "Lap", minutes = "Minutes", add = "Add clock", today = "Today", offset = "+0HRS",
tabs = { world = "World Clock", alarm = "Alarm", stopwatch = "Stopwatch", timer = "Timer" },
cities = { cupertino = "Cupertino", newYork = "New York", london = "London", tokyo = "Tokyo" },
alarm = { weekday = "Weekdays", weekend = "Weekend" },
@@ -33,6 +33,9 @@ Locales["en"] = {
photos = {
name = "Photos", searchPlaceholder = "Photos, people, places...", recents = "Recents",
favorites = "Favorites", items = "items", memories = "Memories", featured = "City colors",
dateRange = "19 Apr7 May 2024", place = "Los Santos & more", select = "Select", count = "3,042 Photos, 125 Videos",
years = "Years", months = "Months", days = "Days", allPhotos = "All Photos", seeAll = "See All",
onThisDay = "On This Day", trip = "MAR 2024 TRIP", featuredPhotos = "Featured Photos", featuredDate = "30 Mar 2024",
tabs = { library = "Library", forYou = "For You", albums = "Albums", search = "Search" },
samples = { sunset = "Sunset drive", ocean = "Ocean air", city = "City lights", desert = "Desert road", capture = "Camera capture" },
},
@@ -40,12 +43,23 @@ Locales["en"] = {
name = "App Store", eyebrow = "Discover", featured = "Featured", heroTitle = "Apps for every day",
heroBody = "Fresh ideas, built for your life in the city.", get = "GET", open = "OPEN",
searchPlaceholder = "Games, Apps, Stories and More",
communityTitle = "Black Voices and Creators", communityBody = "Apps and games from the community",
playing = "What We're Playing", recommended = "Recommended for You", selected = "Great apps selected by our editors",
tabs = { today = "Today", apps = "Apps", games = "Games", arcade = "Arcade", search = "Search" },
catalog = { orbit = "Plan your day", studio = "Create something new", trail = "Explore nearby", prism = "A colorful puzzle" },
card = {
oneEyebrow = "App of the Day", oneTitle = "A universe in your pocket", oneBody = "Explore something extraordinary today.",
twoEyebrow = "Now Trending", twoTitle = "Turn up your afternoon", twoBody = "Fresh sounds and stories picked for you.",
threeEyebrow = "Editors' Choice", threeTitle = "Play without limits", threeBody = "A new world is waiting.",
},
},
settings = {
name = "Settings", searchPlaceholder = "Search", wifi = "Wi-Fi", bluetooth = "Bluetooth",
airplaneMode = "Airplane Mode", wallpaper = "Wallpaper",
airplaneMode = "Airplane Mode", wallpaper = "Wallpaper", on = "On", accountName = "Sky Citizen",
accountDetail = "Apple ID, iCloud, Media & Purchases", mobileServices = "Mobile Services", personalHotspot = "Personal Hotspot",
vpn = "VPN", notifications = "Notifications", sounds = "Sounds & Haptics", focus = "Focus", screenTime = "Screen Time",
general = "General", display = "Display & Brightness", homeScreen = "Home Screen & App Library", accessibility = "Accessibility",
battery = "Battery", privacy = "Privacy & Security", passwords = "Passwords", wallpaperPicker = "Built-in Wallpapers",
toggle = { wifi = "Toggle Wi-Fi", bluetooth = "Toggle Bluetooth", airplaneMode = "Toggle Airplane Mode" },
wallpapers = { midnight = "Midnight wallpaper", aurora = "Aurora wallpaper", ember = "Ember wallpaper" },
},
@@ -1 +0,0 @@
import{a as v,u as A,o as l,c as p,b as e,t as s,e as a,g as m,S as y,w as k,v as C,m as w,F as u,r as h,p as z,n as B,h as g,l as V}from"./sky-index-DrcmepK6.js";import{u as $}from"./sky-media-DNqFydRb.js";const x={class:"native-app store-app"},L={class:"app-header"},N={key:0,class:"app-search"},P=["placeholder"],T={class:"store-feature"},D={class:"store-list"},F=["onClick"],M={class:"app-tabs"},q=["onClick"],j=v({__name:"AppStoreApp",setup(E){const o=A(),i=$(),r=g("today"),n=g(""),b=["today","apps","games","arcade","search"],_=[{id:"orbit",title:"Orbit",subtitle:"Apps.appStore.catalog.orbit",gradient:"linear-gradient(135deg,#6f5cff,#29d3c2)"},{id:"studio",title:"Studio",subtitle:"Apps.appStore.catalog.studio",gradient:"linear-gradient(135deg,#ff8b4a,#ff2d75)"},{id:"trail",title:"Trail",subtitle:"Apps.appStore.catalog.trail",gradient:"linear-gradient(135deg,#4bd37b,#147ec1)"},{id:"puzzle",title:"Prism",subtitle:"Apps.appStore.catalog.prism",gradient:"linear-gradient(135deg,#ffd84d,#7b36dc)"}],S=V(()=>_.filter(d=>d.title.toLowerCase().includes(n.value.toLowerCase())));return(d,c)=>(l(),p("main",x,[e("header",L,[e("small",null,s(a(o).t("Apps.appStore.eyebrow")),1),e("h1",null,s(a(o).t(`Apps.appStore.tabs.${r.value}`)),1)]),r.value==="search"?(l(),p("div",N,[m(a(y),{size:17}),k(e("input",{"onUpdate:modelValue":c[0]||(c[0]=t=>n.value=t),placeholder:a(o).t("Apps.appStore.searchPlaceholder")},null,8,P),[[C,n.value]])])):w("",!0),e("section",T,[e("p",null,s(a(o).t("Apps.appStore.featured")),1),e("h2",null,s(a(o).t("Apps.appStore.heroTitle")),1),e("span",null,s(a(o).t("Apps.appStore.heroBody")),1)]),e("section",D,[(l(!0),p(u,null,h(S.value,t=>(l(),p("article",{key:t.id},[e("div",{class:"store-icon",style:z({background:t.gradient})},s(t.title[0]),5),e("div",null,[e("strong",null,s(t.title),1),e("small",null,s(a(o).t(t.subtitle)),1)]),e("button",{type:"button",onClick:f=>a(i).claimApp(t.id)},s(a(o).t(a(i).claimedApps.includes(t.id)?"Apps.appStore.open":"Apps.appStore.get")),9,F)]))),128))]),e("nav",M,[(l(),p(u,null,h(b,t=>e("button",{key:t,class:B({active:r.value===t}),type:"button",onClick:f=>r.value=t},s(a(o).t(`Apps.appStore.tabs.${t}`)),11,q)),64))])]))}});export{j as default};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{d as b,a as g,u as y,o as s,c as u,b as t,t as p,e as d,F as m,r as O,n as h}from"./sky-index-DrcmepK6.js";const _={accumulator:null,display:"0",error:!1,pendingOperator:null,waitingForOperand:!1};function c(r){if(!Number.isFinite(r))return"Error";const n=Number(r.toPrecision(12)),a=String(n);return a.length<=12?a:n.toExponential(6)}function f(r,n,a){return a==="add"?r+n:a==="subtract"?r-n:a==="multiply"?r*n:n===0?null:r/n}function l(){return{..._}}function k(r,n){return/^\d$/.test(n)?r.error||r.waitingForOperand?{...r,display:n,error:!1,waitingForOperand:!1}:r.display==="0"?{...r,display:n}:r.display.replace("-","").replace(".","").length>=10?r:{...r,display:`${r.display}${n}`}:r}function C(r){return r.error||r.waitingForOperand?{...r,display:"0.",error:!1,waitingForOperand:!1}:r.display.includes(".")?r:{...r,display:`${r.display}.`}}function F(r){return r.error||r.display==="0"?r:{...r,display:r.display.startsWith("-")?r.display.slice(1):`-${r.display}`}}function $(r){return r.error?r:{...r,display:c(Number(r.display)/100)}}function S(r,n){if(r.error)return l();const a=Number(r.display);let e=r.accumulator;if(e!==null&&r.pendingOperator&&!r.waitingForOperand){const i=f(e,a,r.pendingOperator);if(i===null)return{...l(),display:"Error",error:!0};e=i}else e===null&&(e=a);return{accumulator:e,display:c(e),error:!1,pendingOperator:n,waitingForOperand:!0}}function w(r){if(r.error||r.accumulator===null||!r.pendingOperator)return r;const n=f(r.accumulator,Number(r.display),r.pendingOperator);return n===null?{...l(),display:"Error",error:!0}:{accumulator:null,display:c(n),error:!1,pendingOperator:null,waitingForOperand:!0}}const N=b("calculator",{state:()=>l(),actions:{chooseOperator(r){Object.assign(this,S(this.$state,r))},clear(){Object.assign(this,l())},decimal(){Object.assign(this,C(this.$state))},digit(r){Object.assign(this,k(this.$state,r))},equals(){Object.assign(this,w(this.$state))},percent(){Object.assign(this,$(this.$state))},toggleSign(){Object.assign(this,F(this.$state))}}}),j=["aria-label"],A={class:"calculator-display","aria-live":"polite"},E={class:"calculator-pad"},L=["onClick"],B=g({__name:"CalculatorApp",setup(r){const n=N(),a=y(),e=[{label:"AC",action:n.clear,kind:"utility"},{label:"+/",action:n.toggleSign,kind:"utility"},{label:"%",action:n.percent,kind:"utility"},{label:"÷",action:()=>n.chooseOperator("divide"),kind:"operator"},...["7","8","9"].map(i=>({label:i,action:()=>n.digit(i)})),{label:"×",action:()=>n.chooseOperator("multiply"),kind:"operator"},...["4","5","6"].map(i=>({label:i,action:()=>n.digit(i)})),{label:"",action:()=>n.chooseOperator("subtract"),kind:"operator"},...["1","2","3"].map(i=>({label:i,action:()=>n.digit(i)})),{label:"+",action:()=>n.chooseOperator("add"),kind:"operator"},{label:"0",action:()=>n.digit("0"),kind:"zero"},{label:".",action:n.decimal},{label:"=",action:n.equals,kind:"operator"}];return(i,T)=>(s(),u("main",{class:"native-app calculator-app","aria-label":d(a).t("Apps.calculator.name")},[t("div",A,p(d(n).display),1),t("div",E,[(s(),u(m,null,O(e,o=>t("button",{key:o.label,type:"button",class:h(o.kind),onClick:o.action},p(o.label),11,L)),64))])],8,j))}});export{B as default};
import{d as b,a as g,u as y,o as s,c as u,b as t,t as p,e as d,F as m,r as O,n as h}from"./sky-index-CHPJPv4D.js";const _={accumulator:null,display:"0",error:!1,pendingOperator:null,waitingForOperand:!1};function c(r){if(!Number.isFinite(r))return"Error";const n=Number(r.toPrecision(12)),a=String(n);return a.length<=12?a:n.toExponential(6)}function f(r,n,a){return a==="add"?r+n:a==="subtract"?r-n:a==="multiply"?r*n:n===0?null:r/n}function l(){return{..._}}function k(r,n){return/^\d$/.test(n)?r.error||r.waitingForOperand?{...r,display:n,error:!1,waitingForOperand:!1}:r.display==="0"?{...r,display:n}:r.display.replace("-","").replace(".","").length>=10?r:{...r,display:`${r.display}${n}`}:r}function C(r){return r.error||r.waitingForOperand?{...r,display:"0.",error:!1,waitingForOperand:!1}:r.display.includes(".")?r:{...r,display:`${r.display}.`}}function F(r){return r.error||r.display==="0"?r:{...r,display:r.display.startsWith("-")?r.display.slice(1):`-${r.display}`}}function $(r){return r.error?r:{...r,display:c(Number(r.display)/100)}}function S(r,n){if(r.error)return l();const a=Number(r.display);let e=r.accumulator;if(e!==null&&r.pendingOperator&&!r.waitingForOperand){const i=f(e,a,r.pendingOperator);if(i===null)return{...l(),display:"Error",error:!0};e=i}else e===null&&(e=a);return{accumulator:e,display:c(e),error:!1,pendingOperator:n,waitingForOperand:!0}}function w(r){if(r.error||r.accumulator===null||!r.pendingOperator)return r;const n=f(r.accumulator,Number(r.display),r.pendingOperator);return n===null?{...l(),display:"Error",error:!0}:{accumulator:null,display:c(n),error:!1,pendingOperator:null,waitingForOperand:!0}}const N=b("calculator",{state:()=>l(),actions:{chooseOperator(r){Object.assign(this,S(this.$state,r))},clear(){Object.assign(this,l())},decimal(){Object.assign(this,C(this.$state))},digit(r){Object.assign(this,k(this.$state,r))},equals(){Object.assign(this,w(this.$state))},percent(){Object.assign(this,$(this.$state))},toggleSign(){Object.assign(this,F(this.$state))}}}),j=["aria-label"],A={class:"calculator-display","aria-live":"polite"},E={class:"calculator-pad"},L=["onClick"],B=g({__name:"CalculatorApp",setup(r){const n=N(),a=y(),e=[{label:"AC",action:n.clear,kind:"utility"},{label:"+/",action:n.toggleSign,kind:"utility"},{label:"%",action:n.percent,kind:"utility"},{label:"÷",action:()=>n.chooseOperator("divide"),kind:"operator"},...["7","8","9"].map(i=>({label:i,action:()=>n.digit(i)})),{label:"×",action:()=>n.chooseOperator("multiply"),kind:"operator"},...["4","5","6"].map(i=>({label:i,action:()=>n.digit(i)})),{label:"",action:()=>n.chooseOperator("subtract"),kind:"operator"},...["1","2","3"].map(i=>({label:i,action:()=>n.digit(i)})),{label:"+",action:()=>n.chooseOperator("add"),kind:"operator"},{label:"0",action:()=>n.digit("0"),kind:"zero"},{label:".",action:n.decimal},{label:"=",action:n.equals,kind:"operator"}];return(i,T)=>(s(),u("main",{class:"native-app calculator-app","aria-label":d(a).t("Apps.calculator.name")},[t("div",A,p(d(n).display),1),t("div",E,[(s(),u(m,null,O(e,o=>t("button",{key:o.label,type:"button",class:h(o.kind),onClick:o.action},p(o.label),11,L)),64))])],8,j))}});export{B as default};
@@ -1 +0,0 @@
import{u as h}from"./sky-media-DNqFydRb.js";import{f as b,a as k,u as C,o as i,c as l,b as e,n,F as u,r as p,e as a,t as m,g as d,C as y,h as g}from"./sky-index-DrcmepK6.js";const F=b("rotate-ccw",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]]),M={class:"native-app camera-app"},$={class:"camera-zoom"},A=["onClick"],w={class:"camera-modes"},x=["onClick"],B={class:"camera-controls"},L=["aria-label"],S=["aria-label"],z=k({__name:"CameraApp",setup(N){const s=h(),r=C(),c=g(!1),v=["video","photo","portrait"];function _(){c.value=!0,s.capture(),setTimeout(()=>{c.value=!1},120)}return(V,o)=>(i(),l("main",M,[e("div",{class:n(["camera-viewfinder",`camera-facing--${a(s).cameraFacing}`])},[e("div",{class:n(["camera-flash",{active:c.value}])},null,2),o[1]||(o[1]=e("div",{class:"camera-grid"},null,-1)),e("div",$,[(i(),l(u,null,p([.5,1,2],t=>e("button",{key:t,class:n({active:a(s).cameraZoom===t}),type:"button",onClick:f=>a(s).cameraZoom=t},m(t)+"× ",11,A)),64))])],2),e("div",w,[(i(),l(u,null,p(v,t=>e("button",{key:t,class:n({active:a(s).cameraMode===t}),type:"button",onClick:f=>a(s).cameraMode=t},m(a(r).t(`Apps.camera.modes.${t}`)),11,x)),64))]),e("div",B,[o[2]||(o[2]=e("span",null,null,-1)),e("button",{class:"shutter",type:"button","aria-label":a(r).t("Apps.camera.shutter"),onClick:_},[d(a(y),{size:26})],8,L),e("button",{type:"button","aria-label":a(r).t("Apps.camera.flip"),onClick:o[0]||(o[0]=t=>a(s).cameraFacing=a(s).cameraFacing==="rear"?"front":"rear")},[d(a(F),{size:23})],8,S)])]))}});export{z as default};
@@ -0,0 +1 @@
import{u as M}from"./sky-media-hPsqn053.js";import{f as i,a as g,u as w,o as r,c,b as t,e,g as f,h as n,n as l,F as h,r as m,i as x,t as y,j as z,k as A,C as $,l as k}from"./sky-index-CHPJPv4D.js";const b=i("aperture",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m14.31 8 5.74 9.94",key:"1y6ab4"}],["path",{d:"M9.69 8h11.48",key:"1wxppr"}],["path",{d:"m7.38 12 5.74-9.94",key:"1grp0k"}],["path",{d:"M9.69 16 3.95 6.06",key:"libnyf"}],["path",{d:"M14.31 16H2.83",key:"x5fava"}],["path",{d:"m16.62 12-5.74 9.94",key:"1vwawt"}]]);const F=i("chevron-up",[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]]);const B=i("rotate-ccw",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]]);const N=i("zap-off",[["path",{d:"M10.513 4.856 13.12 2.17a.5.5 0 0 1 .86.46l-1.377 4.317",key:"193nxd"}],["path",{d:"M15.656 10H20a1 1 0 0 1 .78 1.63l-1.72 1.773",key:"27a7lr"}],["path",{d:"M16.273 16.273 10.88 21.83a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14H4a1 1 0 0 1-.78-1.63l4.507-4.643",key:"1e0qe9"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]]);const S=i("zap",[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]]),V={class:"native-app reference-camera"},Z={class:"camera-topbar"},q=["aria-label"],H=["aria-label"],L={class:"camera-zoom"},E=["onClick"],T={key:0},j={class:"reference-camera-footer"},D={class:"camera-mode-strip"},I=["onClick"],O={class:"camera-shutter-row"},P=["aria-label"],R=["aria-label"],Q=g({__name:"CameraApp",setup(U){const s=M(),o=w(),u=k(!1),d=k(!1),v=["timelapse","slowMo","cinematic","video","photo","portrait","pano"];function _(){u.value=!0,s.capture(),window.setTimeout(()=>u.value=!1,120)}return(G,p)=>(r(),c("main",V,[t("header",Z,[t("div",null,[t("button",{type:"button","aria-label":e(o).t("Apps.camera.flash"),onClick:p[0]||(p[0]=a=>d.value=!d.value)},[d.value?(r(),f(e(S),{key:0,size:22})):(r(),f(e(N),{key:1,size:22}))],8,q),n(e(b),{size:22})]),t("button",{class:"camera-chevron",type:"button","aria-label":e(o).t("Apps.camera.controls")},[n(e(F),{size:20})],8,H),n(e(b),{size:22})]),t("section",{class:l(["reference-viewfinder",`camera-facing--${e(s).cameraFacing}`])},[t("div",{class:l(["camera-flash",{active:u.value}])},null,2),(r(),c(h,null,m(["tl","tr","bl","br"],a=>t("i",{key:a,class:l(`focus-corner focus-corner--${a}`)},null,2)),64)),t("div",L,[(r(),c(h,null,m([.5,1],a=>t("button",{key:a,class:l({active:e(s).cameraZoom===a}),type:"button",onClick:C=>e(s).cameraZoom=a},[x(y(a),1),a===1?(r(),c("small",T,"x")):z("",!0)],10,E)),64))])],2),t("footer",j,[t("nav",D,[(r(),c(h,null,m(v,a=>t("button",{key:a,class:l({active:e(s).cameraMode===a}),type:"button",onClick:C=>a==="photo"||a==="portrait"||a==="video"?e(s).cameraMode=a:void 0},y(e(o).t(`Apps.camera.modes.${a}`)),11,I)),64))]),t("div",O,[t("div",{class:"camera-thumbnail",style:A({background:e(s).photos[0]?.gradient})},null,4),t("button",{class:"reference-shutter",type:"button","aria-label":e(o).t("Apps.camera.shutter"),onClick:_},[n(e($),{size:23})],8,P),t("button",{class:"reference-flip",type:"button","aria-label":e(o).t("Apps.camera.flip"),onClick:p[1]||(p[1]=a=>e(s).cameraFacing=e(s).cameraFacing==="rear"?"front":"rear")},[n(e(B),{size:22})],8,R)])])]))}});export{Q as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{f as k,a as S,u as w,o as a,c as n,b as t,t as e,e as s,F as u,r as h,k as c,h as v,S as b,w as z,x as D,n as L,g as P,q as x,I as B,l as g,v as m}from"./sky-index-CHPJPv4D.js";import{u as $}from"./sky-media-hPsqn053.js";const A=k("heart",[["path",{d:"M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z",key:"c3ymky"}]]);const q=k("library",[["path",{d:"m16 6 4 14",key:"ji33uf"}],["path",{d:"M12 6v14",key:"1n7gus"}],["path",{d:"M8 8v12",key:"1gg7y9"}],["path",{d:"M4 4v16",key:"6qkkli"}]]),I={class:"native-app reference-photos"},V={key:0,class:"photos-library"},Y={class:"photos-floating-header"},F={type:"button"},K={class:"reference-photo-grid"},N={class:"photos-count"},T={class:"photos-period"},j={class:"active"},E={key:1,class:"photos-scroll"},H={class:"photos-section-title"},R={class:"photos-section-title"},U={class:"featured-row"},Z={key:2,class:"photos-scroll"},G={class:"album-cards"},J={key:3,class:"photos-scroll"},O={class:"app-search"},Q=["placeholder"],W={class:"photo-grid"},X={class:"reference-tabbar"},tt=["onClick"],lt=S({__name:"PhotosApp",setup(st){const r=$(),o=w(),i=g("library"),d=g(""),f=[{id:"library",icon:q},{id:"forYou",icon:A},{id:"albums",icon:B},{id:"search",icon:b}],C=m(()=>Array.from({length:54},(_,p)=>r.photos[p%r.photos.length])),M=m(()=>r.photos.filter(_=>o.t(_.titleKey).toLowerCase().includes(d.value.toLowerCase())));return(_,p)=>(a(),n("main",I,[i.value==="library"?(a(),n("section",V,[t("header",Y,[t("div",null,[t("strong",null,e(s(o).t("Apps.photos.dateRange")),1),t("span",null,e(s(o).t("Apps.photos.place")),1)]),t("button",F,e(s(o).t("Apps.photos.select")),1),p[1]||(p[1]=t("button",{type:"button"},"•••",-1))]),t("div",K,[(a(!0),n(u,null,h(C.value,(l,y)=>(a(),n("article",{key:`${l.id}-${y}`,style:c({background:l.gradient})},null,4))),128))]),t("p",N,e(s(o).t("Apps.photos.count")),1),t("div",T,[t("button",null,e(s(o).t("Apps.photos.years")),1),t("button",null,e(s(o).t("Apps.photos.months")),1),t("button",null,e(s(o).t("Apps.photos.days")),1),t("button",j,e(s(o).t("Apps.photos.allPhotos")),1)])])):i.value==="forYou"?(a(),n("section",E,[t("h1",null,e(s(o).t("Apps.photos.tabs.forYou")),1),t("div",H,[t("h2",null,e(s(o).t("Apps.photos.memories")),1),t("button",null,e(s(o).t("Apps.photos.seeAll")),1)]),t("article",{class:"memory-card",style:c({background:s(r).photos[1]?.gradient})},[v(s(A),{size:25}),t("div",null,[t("strong",null,e(s(o).t("Apps.photos.onThisDay")),1),t("span",null,e(s(o).t("Apps.photos.trip")),1)])],4),t("div",R,[t("h2",null,e(s(o).t("Apps.photos.featuredPhotos")),1)]),t("div",U,[(a(!0),n(u,null,h(s(r).photos.slice(0,2),l=>(a(),n("article",{key:l.id},[t("div",{style:c({background:l.gradient})},null,4),t("strong",null,e(s(o).t(l.titleKey)),1),t("span",null,e(s(o).t("Apps.photos.featuredDate")),1)]))),128))])])):i.value==="albums"?(a(),n("section",Z,[t("h1",null,e(s(o).t("Apps.photos.tabs.albums")),1),t("div",G,[t("article",null,[t("div",{class:"photo-tile",style:c({background:s(r).photos[0]?.gradient})},null,4),t("strong",null,e(s(o).t("Apps.photos.recents")),1),t("small",null,e(s(r).photos.length)+" "+e(s(o).t("Apps.photos.items")),1)]),t("article",null,[p[2]||(p[2]=t("div",{class:"photo-tile favorites"},null,-1)),t("strong",null,e(s(o).t("Apps.photos.favorites")),1),t("small",null,"2 "+e(s(o).t("Apps.photos.items")),1)])])])):(a(),n("section",J,[t("h1",null,e(s(o).t("Apps.photos.tabs.search")),1),t("div",O,[v(s(b),{size:17}),z(t("input",{"onUpdate:modelValue":p[0]||(p[0]=l=>d.value=l),placeholder:s(o).t("Apps.photos.searchPlaceholder")},null,8,Q),[[D,d.value]])]),t("div",W,[(a(!0),n(u,null,h(M.value,l=>(a(),n("article",{key:l.id,class:"photo-tile",style:c({background:l.gradient})},null,4))),128))])])),t("nav",X,[(a(),n(u,null,h(f,l=>t("button",{key:l.id,class:L({active:i.value===l.id}),type:"button",onClick:y=>i.value=l.id},[(a(),P(x(l.icon),{size:22})),t("span",null,e(s(o).t(`Apps.photos.tabs.${l.id}`)),1)],10,tt)),64))])]))}});export{lt as default};
@@ -1 +0,0 @@
import{a as y,u as f,o as l,c as n,b as s,t as a,e,g,S as k,w as A,v as C,m as S,p as u,F as h,r as m,n as w,h as _,l as V}from"./sky-index-DrcmepK6.js";import{u as x}from"./sky-media-DNqFydRb.js";const z={class:"native-app photos-app"},B={class:"app-header"},L={key:0,class:"app-search"},N=["placeholder"],P={key:1,class:"album-cards"},$={key:2,class:"featured-photo"},D={key:3,class:"photo-grid"},F=["aria-label"],K={class:"app-tabs"},M=["onClick"],U=y({__name:"PhotosApp",setup(Y){const p=x(),t=f(),r=_("library"),c=_(""),v=["library","forYou","albums","search"],b=V(()=>p.photos.filter(d=>t.t(d.titleKey).toLowerCase().includes(c.value.toLowerCase())));return(d,i)=>(l(),n("main",z,[s("header",B,[s("h1",null,a(e(t).t(`Apps.photos.tabs.${r.value}`)),1)]),r.value==="search"?(l(),n("div",L,[g(e(k),{size:17}),A(s("input",{"onUpdate:modelValue":i[0]||(i[0]=o=>c.value=o),placeholder:e(t).t("Apps.photos.searchPlaceholder")},null,8,N),[[C,c.value]])])):S("",!0),r.value==="albums"?(l(),n("section",P,[s("article",null,[s("div",{class:"photo-tile",style:u({background:e(p).photos[0]?.gradient})},null,4),s("strong",null,a(e(t).t("Apps.photos.recents")),1),s("small",null,a(e(p).photos.length)+" "+a(e(t).t("Apps.photos.items")),1)]),s("article",null,[i[1]||(i[1]=s("div",{class:"photo-tile favorites"},null,-1)),s("strong",null,a(e(t).t("Apps.photos.favorites")),1),s("small",null,"2 "+a(e(t).t("Apps.photos.items")),1)])])):r.value==="forYou"?(l(),n("section",$,[s("p",null,a(e(t).t("Apps.photos.memories")),1),s("div",{style:u({background:e(p).photos[1]?.gradient})},[s("span",null,a(e(t).t("Apps.photos.featured")),1)],4)])):(l(),n("section",D,[(l(!0),n(h,null,m(b.value,o=>(l(),n("article",{key:o.id,class:"photo-tile",style:u({background:o.gradient}),"aria-label":e(t).t(o.titleKey)},null,12,F))),128))])),s("nav",K,[(l(),n(h,null,m(v,o=>s("button",{key:o,class:w({active:r.value===o}),type:"button",onClick:q=>r.value=o},a(e(t).t(`Apps.photos.tabs.${o}`)),11,M)),64))])]))}});export{U as default};
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{f as h,a as _,u as f,o as n,c as o,b as a,t as c,e as s,g as m,S as v,w as b,v as A,F as p,r as d,p as C,q as S,s as L,n as u,W as $,h as q,l as M,x}from"./sky-index-DrcmepK6.js";const B=h("bluetooth",[["path",{d:"m7 7 10 10-5 5V2l5 5L7 17",key:"1q5490"}]]);const w=h("radio",[["path",{d:"M16.247 7.761a6 6 0 0 1 0 8.478",key:"1fwjs5"}],["path",{d:"M19.075 4.933a10 10 0 0 1 0 14.134",key:"ehdyv1"}],["path",{d:"M4.925 19.067a10 10 0 0 1 0-14.134",key:"1q22gi"}],["path",{d:"M7.753 16.239a6 6 0 0 1 0-8.478",key:"r2q7qm"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]]),z={class:"native-app settings-app"},D={class:"app-header"},P={class:"app-search"},V=["placeholder"],W={class:"settings-list"},E=["aria-label","onClick"],F={class:"settings-section-title"},I={class:"wallpaper-picker"},N=["aria-label","onClick"],T=_({__name:"SettingsApp",setup(R){const t=f(),i=q(""),y=[{key:"wifi",icon:x,color:"#1677ff"},{key:"bluetooth",icon:B,color:"#1677ff"},{key:"airplaneMode",icon:w,color:"#ff9500"}],k=M(()=>y.filter(r=>t.t(`Apps.settings.${r.key}`).toLowerCase().includes(i.value.toLowerCase())));return(r,l)=>(n(),o("main",z,[a("header",D,[a("h1",null,c(s(t).t("Apps.settings.name")),1)]),a("div",P,[m(s(v),{size:17}),b(a("input",{"onUpdate:modelValue":l[0]||(l[0]=e=>i.value=e),placeholder:s(t).t("Apps.settings.searchPlaceholder")},null,8,V),[[A,i.value]])]),a("section",W,[(n(!0),o(p,null,d(k.value,e=>(n(),o("div",{key:e.key,class:"settings-row"},[a("span",{class:"settings-symbol",style:C({background:e.color})},[(n(),S(L(e.icon),{size:16}))],4),a("span",null,c(s(t).t(`Apps.settings.${e.key}`)),1),a("button",{class:u(["ios-switch",{active:s(t).preferences.settings[e.key]}]),type:"button","aria-label":s(t).t(`Apps.settings.toggle.${e.key}`),onClick:g=>s(t).setSetting(e.key,!s(t).preferences.settings[e.key])},[...l[1]||(l[1]=[a("span",null,null,-1)])],10,E)]))),128))]),a("h2",F,c(s(t).t("Apps.settings.wallpaper")),1),a("section",I,[(n(!0),o(p,null,d(s($),e=>(n(),o("button",{key:e,type:"button",class:u([`wallpaper--${e}`,{active:s(t).preferences.settings.wallpaper===e}]),"aria-label":s(t).t(`Apps.settings.wallpapers.${e}`),onClick:g=>s(t).setWallpaper(e)},null,10,N))),128))])]))}});export{T as default};
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{d as a}from"./sky-index-DrcmepK6.js";const s=[{capturedAt:Date.now()-864e5,gradient:"linear-gradient(145deg, #ff9a62, #5f2c82 58%, #141e30)",id:"sunset-drive",titleKey:"Apps.photos.samples.sunset"},{capturedAt:Date.now()-1728e5,gradient:"linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)",id:"ocean-air",titleKey:"Apps.photos.samples.ocean"},{capturedAt:Date.now()-2592e5,gradient:"linear-gradient(135deg, #fbc2eb, #a6c1ee 48%, #302b63)",id:"city-lights",titleKey:"Apps.photos.samples.city"},{capturedAt:Date.now()-3456e5,gradient:"linear-gradient(150deg, #f6d365, #fda085 45%, #512b58)",id:"desert-road",titleKey:"Apps.photos.samples.desert"}],r=a("media",{state:()=>({cameraFacing:"rear",cameraMode:"photo",cameraZoom:1,captures:[],claimedApps:[]}),getters:{photos:e=>[...e.captures,...s]},actions:{capture(){const e=["linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)","linear-gradient(150deg, #00c9a7, #4d8076 46%, #1f3a5f)","linear-gradient(135deg, #ffc75f, #f96d80 48%, #4b4453)"],t=this.captures.length;this.captures.unshift({capturedAt:Date.now(),gradient:e[t%e.length],id:`capture-${Date.now()}`,titleKey:"Apps.photos.samples.capture"})},claimApp(e){this.claimedApps.includes(e)||this.claimedApps.push(e)}}});export{r as u};
import{d as a}from"./sky-index-CHPJPv4D.js";const s=[{capturedAt:Date.now()-864e5,gradient:"linear-gradient(145deg, #ff9a62, #5f2c82 58%, #141e30)",id:"sunset-drive",titleKey:"Apps.photos.samples.sunset"},{capturedAt:Date.now()-1728e5,gradient:"linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)",id:"ocean-air",titleKey:"Apps.photos.samples.ocean"},{capturedAt:Date.now()-2592e5,gradient:"linear-gradient(135deg, #fbc2eb, #a6c1ee 48%, #302b63)",id:"city-lights",titleKey:"Apps.photos.samples.city"},{capturedAt:Date.now()-3456e5,gradient:"linear-gradient(150deg, #f6d365, #fda085 45%, #512b58)",id:"desert-road",titleKey:"Apps.photos.samples.desert"}],r=a("media",{state:()=>({cameraFacing:"rear",cameraMode:"photo",cameraZoom:1,captures:[],claimedApps:[]}),getters:{photos:e=>[...e.captures,...s]},actions:{capture(){const e=["linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)","linear-gradient(150deg, #00c9a7, #4d8076 46%, #1f3a5f)","linear-gradient(135deg, #ffc75f, #f96d80 48%, #4b4453)"],t=this.captures.length;this.captures.unshift({capturedAt:Date.now(),gradient:e[t%e.length],id:`capture-${Date.now()}`,titleKey:"Apps.photos.samples.capture"})},claimApp(e){this.claimedApps.includes(e)||this.claimedApps.push(e)}}});export{r as u};
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

@@ -0,0 +1 @@
import{f as e}from"./sky-index-CHPJPv4D.js";const c=e("grid-2x2",[["path",{d:"M12 3v18",key:"108xh3"}],["path",{d:"M3 12h18",key:"1i2n21"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2",key:"h1oib"}]]);const h=e("user-round",[["circle",{cx:"12",cy:"8",r:"5",key:"1hypcn"}],["path",{d:"M20 21a8 8 0 0 0-16 0",key:"rfgkzh"}]]);export{c as G,h as U};
+2 -2
View File
@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sky Phone</title>
<script type="module" crossorigin src="./assets/sky-index-DrcmepK6.js"></script>
<link rel="stylesheet" crossorigin href="./assets/sky-index-BQ2KvS-_.css">
<script type="module" crossorigin src="./assets/sky-index-CHPJPv4D.js"></script>
<link rel="stylesheet" crossorigin href="./assets/sky-index-DOLQ1Bue.css">
</head>
<body>
<div id="app"></div>