Files
sky_phone-sky-systems/frontend/src/components/PhoneSetupAssistant.vue
T
2026-08-15 00:00:14 +02:00

2122 lines
55 KiB
Vue

<script setup lang="ts">
import {
BellRing,
Check,
ChevronLeft,
Cloud,
Gauge,
LockKeyhole,
Palette,
ShieldCheck,
Signal,
Sparkles,
Wifi,
} from 'lucide-vue-next'
import { computed, ref } from 'vue'
import PhonePasscode from '@/components/PhonePasscode.vue'
import { getPhoneApp, getPhoneAppLabel } from '@/config/apps'
import { useAccountStore } from '@/stores/account'
import { useAppStoreStore } from '@/stores/app-store'
import { usePhoneStore } from '@/stores/phone'
import type { BuiltinPhoneAppId } from '@/types/apps'
import { SkyButton, SkyField, SkySpinner } from '@/ui'
import { filterMailAddressInput, normalizeMailAddress } from '@/utils/mail'
import {
PHONE_SETUP_LAST_STEP,
WALLPAPER_IDS,
type AppearanceMode,
type GraphicsMode,
type WallpaperId,
} from '@/utils/preferences'
const emit = defineEmits<{ complete: [] }>()
const phone = usePhoneStore()
const account = useAccountStore()
const appStore = useAppStoreStore()
const step = ref(
Math.min(PHONE_SETUP_LAST_STEP, phone.preferences.settings.setupStep),
)
const direction = ref<'back' | 'forward'>('forward')
const accountMode = ref<'login' | 'register'>('login')
const email = ref('')
const password = ref('')
const passwordConfirm = ref('')
const accountBusy = ref(false)
const accountError = ref('')
const passcodeStage = ref<'create' | 'confirm' | null>(null)
const passcodeFirst = ref('')
const passcodeResetKey = ref(0)
const passcodeError = ref('')
const passcodeLength = ref<4 | 6>(phone.security.length === 4 ? 4 : 6)
const notificationsEnabled = ref(true)
const notificationSounds = ref(true)
const selectedApps = ref<BuiltinPhoneAppId[]>(['banking', 'garage', 'skyride'])
const setupApps = (
['banking', 'garage', 'skyride', 'citymarkt', 'picstagram', 'snake'] as const
).flatMap((id) => {
const app = getPhoneApp(id)
return app ? [app] : []
})
const wallpaperChoices = WALLPAPER_IDS
const progress = computed(() => `${((step.value + 1) / 10) * 100}%`)
const displayName = computed(() => {
const name = [phone.player.firstName, phone.player.lastName]
.map((part) => part.trim())
.filter(Boolean)
.join(' ')
return name || phone.t('Setup.ownerFallback')
})
const normalizedAccountEmail = computed(() => normalizeMailAddress(email.value))
const accountInitial = computed(() =>
(email.value.trim()[0] ?? displayName.value[0] ?? 'S').toUpperCase(),
)
const passwordStrength = computed(() => {
const value = password.value
if (!value) return 0
return [
value.length >= 6,
value.length >= 10,
/[a-z]/i.test(value) && /\d/.test(value),
/[^a-z0-9]/i.test(value),
].filter(Boolean).length
})
const currentWallpaperStyle = computed(() => ({
'--setup-wallpaper': `var(--phone-wallpaper-${phone.preferences.settings.wallpaper})`,
}))
function moveTo(nextStep: number): void {
direction.value = nextStep < step.value ? 'back' : 'forward'
step.value = Math.min(PHONE_SETUP_LAST_STEP, Math.max(0, nextStep))
phone.setSetupStep(step.value)
}
function continueSetup(): void {
if (step.value === 7) {
phone.setAllAppNotifications(
notificationsEnabled.value,
notificationSounds.value,
)
}
if (step.value === 8) {
for (const appId of selectedApps.value) appStore.claimApp(appId)
}
moveTo(step.value + 1)
}
function chooseAppearance(mode: AppearanceMode): void {
phone.setPreference('appearanceMode', mode)
}
function choosePerformance(mode: GraphicsMode): void {
phone.setPreference('graphicsMode', mode)
}
function chooseWallpaper(wallpaper: Exclude<WallpaperId, 'custom'>): void {
phone.setWallpaper(wallpaper)
}
function toggleApp(appId: BuiltinPhoneAppId): void {
selectedApps.value = selectedApps.value.includes(appId)
? selectedApps.value.filter((id) => id !== appId)
: [...selectedApps.value, appId]
}
async function submitAccount(): Promise<void> {
if (!normalizedAccountEmail.value || password.value.length < 6) {
accountError.value = phone.t('Setup.cloud.invalid')
return
}
if (
accountMode.value === 'register' &&
password.value !== passwordConfirm.value
) {
accountError.value = phone.t('Setup.cloud.passwordMismatch')
return
}
accountBusy.value = true
accountError.value = ''
const response =
accountMode.value === 'login'
? await account.login(normalizedAccountEmail.value, password.value)
: await account.register(normalizedAccountEmail.value, password.value)
accountBusy.value = false
if (!response.success) {
accountError.value = phone.t(
`Setup.cloud.errors.${response.error ?? 'request_failed'}`,
)
return
}
continueSetup()
}
function selectAccountMode(mode: 'login' | 'register'): void {
if (accountMode.value === mode) return
accountMode.value = mode
password.value = ''
passwordConfirm.value = ''
accountError.value = ''
}
function updateAccountName(event: Event): void {
const input = event.target as HTMLInputElement
const localPart = filterMailAddressInput(input.value)
.split('@')[0]
.slice(0, 32)
.toLocaleLowerCase('en-US')
input.value = localPart
email.value = localPart
}
function submitPasscode(passcode: string): void {
if (passcodeStage.value === 'create') {
passcodeFirst.value = passcode
passcodeStage.value = 'confirm'
passcodeResetKey.value += 1
return
}
if (passcode !== passcodeFirst.value) {
passcodeError.value = phone.t('Setup.security.mismatch')
passcodeStage.value = 'create'
passcodeFirst.value = ''
passcodeResetKey.value += 1
return
}
void phone.setPasscode(passcode).then((response) => {
if (!response.success) {
passcodeError.value = phone.t('Setup.security.failed')
passcodeStage.value = 'create'
passcodeResetKey.value += 1
return
}
passcodeStage.value = null
continueSetup()
})
}
function choosePasscodeLength(length: 4 | 6): void {
passcodeLength.value = length
passcodeFirst.value = ''
passcodeError.value = ''
passcodeResetKey.value += 1
}
function finish(): void {
phone.completeSetup()
emit('complete')
}
</script>
<template>
<section
class="setup-assistant"
:class="`setup-assistant--step-${step}`"
:style="currentWallpaperStyle"
:aria-label="phone.t('Setup.title')"
>
<div class="setup-assistant__aurora" aria-hidden="true"></div>
<header v-if="step > 0 && step < 9" class="setup-assistant__chrome">
<button
type="button"
class="setup-assistant__back"
:aria-label="phone.t('Common.back')"
@click="moveTo(step - 1)"
>
<ChevronLeft :size="23" :stroke-width="2.2" />
</button>
<div class="setup-assistant__progress" aria-hidden="true">
<span :style="{ width: progress }"></span>
</div>
<span class="setup-assistant__counter">{{ step + 1 }}/10</span>
</header>
<Transition
:name="direction === 'forward' ? 'setup-forward' : 'setup-back'"
mode="out-in"
>
<main :key="step" class="setup-assistant__page">
<template v-if="step === 0">
<div class="setup-welcome__hero" aria-hidden="true">
<div class="setup-welcome__greetings">
<span>{{ phone.t('Setup.welcome.hello') }}</span>
<span>{{ phone.t('Setup.welcome.hallo') }}</span>
<span>{{ phone.t('Setup.welcome.bonjour') }}</span>
</div>
<div class="setup-welcome__signature">
<span></span>
<span></span>
<span></span>
</div>
</div>
<div class="setup-welcome__copy">
<p class="setup-welcome__eyebrow">
{{ phone.t('Setup.welcome.eyebrow') }}
</p>
<h1>{{ phone.t('Setup.welcome.title') }}</h1>
<p class="setup-assistant__lead">
{{ phone.t('Setup.welcome.body', { name: displayName }) }}
</p>
</div>
<footer class="setup-welcome__footer">
<div class="setup-welcome__privacy">
<span
><ShieldCheck :size="14" />{{
phone.t('Setup.welcome.private')
}}</span
>
<i aria-hidden="true"></i>
<span
><Sparkles :size="14" />{{
phone.t('Setup.welcome.personal')
}}</span
>
</div>
<SkyButton class="setup-assistant__primary" @click="continueSetup">
{{ phone.t('Setup.getStarted') }}
</SkyButton>
<div class="setup-welcome__home-indicator" aria-hidden="true"></div>
</footer>
</template>
<template v-else-if="step === 1">
<div class="setup-assistant__icon setup-assistant__icon--signal">
<Signal :size="44" />
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.connection.eyebrow') }}
</p>
<h1>{{ phone.t('Setup.connection.title') }}</h1>
<p class="setup-assistant__lead">
{{ phone.t('Setup.connection.body') }}
</p>
<div class="setup-connectivity-card">
<div
class="setup-connectivity-card__waves"
aria-hidden="true"
></div>
<span class="setup-connectivity-card__label">Sky SIM</span>
<strong>{{
phone.device?.sim?.number ?? phone.t('Setup.connection.noSim')
}}</strong>
<span>{{
phone.device?.sim
? phone.t('Setup.connection.ready')
: phone.t('Setup.connection.offline')
}}</span>
<Wifi :size="25" />
</div>
<div class="setup-assistant__notice">
<ShieldCheck :size="19" />
<p>{{ phone.t('Setup.connection.preserved') }}</p>
</div>
<SkyButton class="setup-assistant__primary" @click="continueSetup">
{{ phone.t('Common.continue') }}
</SkyButton>
</template>
<template v-else-if="step === 2">
<div class="setup-cloud-hero" aria-hidden="true">
<span class="setup-cloud-hero__orbit"></span>
<span class="setup-cloud-hero__glow"></span>
<Cloud :size="45" :stroke-width="1.45" />
<i></i><i></i><i></i>
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.cloud.eyebrow') }}
</p>
<Transition name="setup-account-copy" mode="out-in">
<div :key="accountMode" class="setup-cloud-heading">
<h1>
{{
phone.t(
accountMode === 'login'
? 'Setup.cloud.signInTitle'
: 'Setup.cloud.createTitle',
)
}}
</h1>
<p class="setup-assistant__lead">
{{
phone.t(
accountMode === 'login'
? 'Setup.cloud.signInBody'
: 'Setup.cloud.createBody',
)
}}
</p>
</div>
</Transition>
<div v-if="!account.email" class="setup-cloud-form">
<div class="setup-assistant__selector" role="group">
<span
class="setup-assistant__selector-indicator"
:class="{
'setup-assistant__selector-indicator--register':
accountMode === 'register',
}"
aria-hidden="true"
></span>
<button
:class="{ active: accountMode === 'login' }"
:aria-pressed="accountMode === 'login'"
@click="selectAccountMode('login')"
>
{{ phone.t('Setup.cloud.signIn') }}
</button>
<button
:class="{ active: accountMode === 'register' }"
:aria-pressed="accountMode === 'register'"
@click="selectAccountMode('register')"
>
{{ phone.t('Setup.cloud.create') }}
</button>
</div>
<div class="setup-cloud-identity">
<span>{{ accountInitial }}</span>
<div>
<small>{{ phone.t('Setup.cloud.accountPreview') }}</small>
<strong
>{{ email || phone.t('Setup.cloud.addressPlaceholder')
}}<em>@ifruit.com</em></strong
>
</div>
<Check v-if="normalizedAccountEmail" :size="17" />
</div>
<div class="setup-cloud-fields">
<SkyField
id="setup-cloud-name"
:model-value="email"
:label="phone.t('Setup.cloud.accountName')"
:placeholder="phone.t('Setup.cloud.addressPlaceholder')"
autocomplete="username"
autocapitalize="none"
autocorrect="off"
:spellcheck="false"
@input="updateAccountName"
>
<template #trailing>
<span class="setup-cloud-suffix">@ifruit.com</span>
</template>
</SkyField>
<SkyField
id="setup-cloud-password"
v-model="password"
type="password"
:label="phone.t('Setup.cloud.password')"
:autocomplete="
accountMode === 'login' ? 'current-password' : 'new-password'
"
/>
<Transition name="setup-account-field">
<SkyField
v-if="accountMode === 'register'"
id="setup-cloud-password-confirm"
v-model="passwordConfirm"
type="password"
:label="phone.t('Setup.cloud.confirmPassword')"
autocomplete="new-password"
/>
</Transition>
</div>
<Transition name="setup-account-strength">
<div
v-if="accountMode === 'register'"
class="setup-cloud-strength"
>
<div>
<span
v-for="level in 4"
:key="level"
:class="{ active: level <= passwordStrength }"
></span>
</div>
<small>{{
phone.t(`Setup.cloud.strength${passwordStrength}`)
}}</small>
</div>
</Transition>
<p v-if="accountError" class="setup-assistant__error" role="alert">
{{ accountError }}
</p>
<SkyButton
class="setup-assistant__primary"
:disabled="accountBusy"
@click="submitAccount"
>
<SkySpinner v-if="accountBusy" />
<Transition v-else name="setup-account-action" mode="out-in">
<span :key="accountMode">{{
phone.t(
accountMode === 'login'
? 'Setup.cloud.signInAction'
: 'Setup.cloud.createAction',
)
}}</span>
</Transition>
</SkyButton>
<div class="setup-cloud-security-note">
<LockKeyhole :size="13" />
<span>{{ phone.t('Setup.cloud.securityNote') }}</span>
</div>
<button
type="button"
class="setup-assistant__later"
@click="continueSetup"
>
{{ phone.t('Setup.setUpLater') }}
</button>
</div>
<div v-else class="setup-cloud-connected">
<Check :size="31" />
<strong>{{ phone.t('Setup.cloud.connected') }}</strong>
<span>{{ account.email }}</span>
<SkyButton
class="setup-assistant__primary"
@click="continueSetup"
>{{ phone.t('Common.continue') }}</SkyButton
>
</div>
</template>
<template v-else-if="step === 3">
<div class="setup-assistant__icon setup-assistant__icon--security">
<LockKeyhole :size="43" />
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.security.eyebrow') }}
</p>
<h1>{{ phone.t('Setup.security.title') }}</h1>
<p class="setup-assistant__lead">
{{ phone.t('Setup.security.body') }}
</p>
<div class="setup-security-visual" aria-hidden="true">
<div>
<span v-for="dot in passcodeLength" :key="dot"></span>
</div>
<ShieldCheck :size="40" />
</div>
<div
class="setup-passcode-length"
role="group"
:aria-label="phone.t('Setup.security.lengthTitle')"
>
<button
v-for="length in [4, 6] as const"
:key="length"
type="button"
:class="{ selected: passcodeLength === length }"
@click="choosePasscodeLength(length)"
>
<span>
<b>{{ length }}</b>
<small>{{
phone.t(
`Setup.security.${length === 4 ? 'fourDigit' : 'sixDigit'}`,
)
}}</small>
</span>
<Check
v-if="passcodeLength === length"
:size="16"
:stroke-width="3"
/>
</button>
</div>
<div class="setup-assistant__notice">
<ShieldCheck :size="19" />
<p>{{ phone.t('Setup.security.local') }}</p>
</div>
<SkyButton
class="setup-assistant__primary"
@click="passcodeStage = 'create'"
>{{
phone.t('Setup.security.createSelected', {
count: String(passcodeLength),
})
}}</SkyButton
>
<button
type="button"
class="setup-assistant__later"
@click="continueSetup"
>
{{ phone.t('Setup.setUpLater') }}
</button>
</template>
<template v-else-if="step === 4">
<div class="setup-assistant__icon setup-assistant__icon--appearance">
<Palette :size="43" />
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.appearance.eyebrow') }}
</p>
<h1>{{ phone.t('Setup.appearance.title') }}</h1>
<p class="setup-assistant__lead">
{{ phone.t('Setup.appearance.body') }}
</p>
<div class="setup-choice-grid setup-choice-grid--appearance">
<button
v-for="mode in ['automatic', 'light', 'dark'] as AppearanceMode[]"
:key="mode"
:class="{
selected: phone.preferences.settings.appearanceMode === mode,
}"
@click="chooseAppearance(mode)"
>
<span
class="setup-appearance-preview"
:class="`setup-appearance-preview--${mode}`"
><i></i><i></i><i></i
></span>
<strong>{{ phone.t(`Apps.settings.${mode}`) }}</strong>
<Check
v-if="phone.preferences.settings.appearanceMode === mode"
:size="18"
/>
</button>
</div>
<SkyButton class="setup-assistant__primary" @click="continueSetup">{{
phone.t('Common.continue')
}}</SkyButton>
</template>
<template v-else-if="step === 5">
<div class="setup-assistant__icon setup-assistant__icon--performance">
<Gauge :size="44" />
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.performance.eyebrow') }}
</p>
<h1>{{ phone.t('Setup.performance.title') }}</h1>
<p class="setup-assistant__lead">
{{ phone.t('Setup.performance.body') }}
</p>
<div class="setup-mode-stack">
<button
v-for="mode in ['performance', 'ultimate'] as GraphicsMode[]"
:key="mode"
:class="{
selected: phone.preferences.settings.graphicsMode === mode,
}"
@click="choosePerformance(mode)"
>
<span
class="setup-mode-stack__orb"
:class="`setup-mode-stack__orb--${mode}`"
></span>
<span
><strong>{{ phone.t(`Apps.settings.${mode}Mode`) }}</strong
><small>{{ phone.t(`Setup.performance.${mode}`) }}</small></span
>
<span class="setup-mode-stack__check"
><Check
v-if="phone.preferences.settings.graphicsMode === mode"
:size="17"
/></span>
</button>
</div>
<div class="setup-assistant__notice">
<Gauge :size="19" />
<p>{{ phone.t('Setup.performance.changeLater') }}</p>
</div>
<SkyButton class="setup-assistant__primary" @click="continueSetup">{{
phone.t('Common.continue')
}}</SkyButton>
</template>
<template v-else-if="step === 6">
<div class="setup-assistant__icon setup-assistant__icon--wallpaper">
<Sparkles :size="43" />
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.wallpaper.eyebrow') }}
</p>
<h1>{{ phone.t('Setup.wallpaper.title') }}</h1>
<p class="setup-assistant__lead">
{{ phone.t('Setup.wallpaper.body') }}
</p>
<div class="setup-wallpapers">
<button
v-for="wallpaper in wallpaperChoices"
:key="wallpaper"
:class="[
`wallpaper--${wallpaper}`,
{
selected: phone.preferences.settings.wallpaper === wallpaper,
},
]"
:aria-label="wallpaper"
@click="chooseWallpaper(wallpaper)"
>
<Check
v-if="phone.preferences.settings.wallpaper === wallpaper"
:size="17"
/>
</button>
</div>
<SkyButton class="setup-assistant__primary" @click="continueSetup">{{
phone.t('Common.continue')
}}</SkyButton>
</template>
<template v-else-if="step === 7">
<div
class="setup-assistant__icon setup-assistant__icon--notifications"
>
<BellRing :size="43" />
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.notifications.eyebrow') }}
</p>
<h1>{{ phone.t('Setup.notifications.title') }}</h1>
<p class="setup-assistant__lead">
{{ phone.t('Setup.notifications.body') }}
</p>
<div class="setup-toggle-card">
<button @click="notificationsEnabled = !notificationsEnabled">
<span
><strong>{{ phone.t('Setup.notifications.allow') }}</strong
><small>{{
phone.t('Setup.notifications.allowBody')
}}</small></span
><i :class="{ on: notificationsEnabled }"><b></b></i>
</button>
<button
:disabled="!notificationsEnabled"
@click="notificationSounds = !notificationSounds"
>
<span
><strong>{{ phone.t('Setup.notifications.sounds') }}</strong
><small>{{
phone.t('Setup.notifications.soundsBody')
}}</small></span
><i :class="{ on: notificationsEnabled && notificationSounds }"
><b></b
></i>
</button>
</div>
<SkyButton class="setup-assistant__primary" @click="continueSetup">{{
phone.t('Common.continue')
}}</SkyButton>
</template>
<template v-else-if="step === 8">
<div class="setup-assistant__icon setup-assistant__icon--apps">
<Sparkles :size="43" />
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.apps.eyebrow') }}
</p>
<h1>{{ phone.t('Setup.apps.title') }}</h1>
<p class="setup-assistant__lead">{{ phone.t('Setup.apps.body') }}</p>
<div class="setup-app-list">
<button
v-for="app in setupApps"
:key="app.id"
:class="{
selected: selectedApps.includes(app.id as BuiltinPhoneAppId),
}"
@click="toggleApp(app.id as BuiltinPhoneAppId)"
>
<img :src="app.iconImage" alt="" />
<span
><strong>{{ getPhoneAppLabel(app, phone.t) }}</strong
><small>{{
phone.t(`Setup.apps.descriptions.${app.id}`)
}}</small></span
>
<i
><Check
v-if="selectedApps.includes(app.id as BuiltinPhoneAppId)"
:size="16"
/></i>
</button>
</div>
<SkyButton class="setup-assistant__primary" @click="continueSetup">{{
phone.t('Setup.apps.install', {
count: String(selectedApps.length),
})
}}</SkyButton>
</template>
<template v-else>
<div class="setup-ready__halo" aria-hidden="true">
<Check :size="66" />
</div>
<p class="setup-assistant__eyebrow">
{{ phone.t('Setup.ready.eyebrow') }}
</p>
<h1>
{{
phone.t('Setup.ready.title', {
name: phone.player.firstName || displayName,
})
}}
</h1>
<p class="setup-assistant__lead">{{ phone.t('Setup.ready.body') }}</p>
<div class="setup-ready__summary">
<span
><Cloud :size="18" /><b>{{
account.email || phone.t('Setup.ready.localOnly')
}}</b></span
>
<span
><Gauge :size="18" /><b>{{
phone.t(
`Apps.settings.${phone.preferences.settings.graphicsMode}Mode`,
)
}}</b></span
>
<span
><Palette :size="18" /><b>{{
phone.t(
`Apps.settings.${phone.preferences.settings.appearanceMode}`,
)
}}</b></span
>
</div>
<SkyButton class="setup-assistant__primary" @click="finish">{{
phone.t('Setup.ready.enter')
}}</SkyButton>
<button
type="button"
class="setup-assistant__later"
@click="moveTo(0)"
>
{{ phone.t('Setup.ready.review') }}
</button>
</template>
</main>
</Transition>
<PhonePasscode
v-if="passcodeStage"
:length="passcodeLength"
:reset-key="passcodeResetKey"
:error="passcodeError"
:title="
phone.t(
passcodeStage === 'create'
? 'Setup.security.enter'
: 'Setup.security.confirm',
)
"
:subtitle="
phone.t(
passcodeLength === 4
? 'Setup.security.fourDigitHint'
: 'Setup.security.sixDigitHint',
)
"
@cancel="passcodeStage = null"
@complete="submitPasscode"
/>
</section>
</template>
<style scoped>
.setup-assistant {
position: absolute;
inset: 0;
z-index: 90;
overflow: clip;
color: #f8fbff;
background: #05070d;
font-family: var(--sky-font-family);
user-select: none;
}
.setup-assistant__aurora {
position: absolute;
inset: -28%;
background:
radial-gradient(circle at 30% 20%, rgb(40 116 255/32%), transparent 28%),
radial-gradient(circle at 75% 65%, rgb(161 67 255/20%), transparent 27%),
radial-gradient(circle at 40% 92%, rgb(0 214 184/13%), transparent 25%);
filter: blur(28px);
animation: setup-aurora 12s ease-in-out infinite alternate;
}
.setup-assistant__chrome {
position: absolute;
z-index: 2;
top: 54px;
left: 18px;
right: 18px;
display: grid;
grid-template-columns: 34px 1fr 34px;
align-items: center;
gap: 12px;
}
.setup-assistant__back {
display: grid;
width: 34px;
height: 34px;
padding: 0;
place-items: center;
border: 1px solid rgb(255 255 255/10%);
border-radius: 50%;
color: white;
background: rgb(255 255 255/8%);
backdrop-filter: blur(18px);
}
.setup-assistant__progress {
height: 4px;
overflow: hidden;
border-radius: 99px;
background: rgb(255 255 255/12%);
}
.setup-assistant__progress span {
display: block;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #58aaff, #7b76ff);
transition: width 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.setup-assistant__counter {
color: rgb(255 255 255/48%);
font-size: 11px;
font-weight: 650;
text-align: right;
}
.setup-assistant__page {
position: relative;
z-index: 1;
display: flex;
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 70px 27px 25px;
flex-direction: column;
align-items: center;
overflow-y: auto;
scrollbar-width: none;
text-align: center;
}
.setup-assistant:not(.setup-assistant--step-0):not(.setup-assistant--step-9)
.setup-assistant__page {
padding-top: 103px;
}
.setup-assistant__page::-webkit-scrollbar {
display: none;
}
.setup-assistant__page h1 {
max-width: 340px;
margin: 10px 0 9px;
font-size: 34px;
font-weight: 730;
line-height: 1.02;
letter-spacing: -0.045em;
}
.setup-assistant__lead {
max-width: 325px;
margin: 0;
color: rgb(230 237 250/66%);
font-size: 14px;
line-height: 1.45;
}
.setup-assistant__eyebrow {
margin: 13px 0 0;
color: #65aaff;
font-size: 10px;
font-weight: 750;
letter-spacing: 0.13em;
text-transform: uppercase;
}
.setup-assistant__icon {
display: grid;
width: 80px;
height: 80px;
flex: none;
place-items: center;
border: 1px solid rgb(255 255 255/18%);
border-radius: 25px;
box-shadow:
inset 0 1px rgb(255 255 255/22%),
0 22px 50px rgb(0 0 0/28%);
background: linear-gradient(
145deg,
rgb(255 255 255/18%),
rgb(255 255 255/5%)
);
backdrop-filter: blur(18px);
}
.setup-assistant__icon--signal {
color: #6fafff;
}
.setup-assistant__icon--cloud {
color: #87bfff;
}
.setup-assistant__icon--security {
color: #76e3aa;
}
.setup-assistant__icon--appearance {
color: #c396ff;
}
.setup-assistant__icon--performance {
color: #ffba61;
}
.setup-assistant__icon--wallpaper {
color: #ef8cff;
}
.setup-assistant__icon--notifications {
color: #ff7b72;
}
.setup-assistant__icon--apps {
color: #79baff;
}
.setup-assistant__primary {
width: 100%;
min-height: 48px;
margin-top: auto;
border-radius: 16px !important;
font-weight: 700;
}
.setup-assistant__later {
padding: 12px;
border: 0;
color: #6eb0ff;
background: transparent;
font-size: 13px;
font-weight: 650;
}
.setup-assistant__notice {
display: flex;
width: 100%;
box-sizing: border-box;
margin: 15px 0;
padding: 13px;
gap: 10px;
align-items: flex-start;
border: 1px solid rgb(255 255 255/8%);
border-radius: 16px;
color: #87b8ff;
background: rgb(255 255 255/5%);
text-align: left;
}
.setup-assistant__notice p {
margin: 0;
color: rgb(240 245 255/62%);
font-size: 12px;
line-height: 1.38;
}
.setup-assistant__error {
margin: 4px 0;
color: #ff8f88;
font-size: 12px;
}
.setup-assistant--step-0 {
background:
radial-gradient(circle at 50% 24%, rgb(29 91 188 / 26%), transparent 31%),
linear-gradient(180deg, #02050b 0%, #050712 58%, #02060c 100%);
}
.setup-assistant--step-0 .setup-assistant__aurora {
inset: -20%;
background:
radial-gradient(circle at 21% 42%, rgb(24 106 255 / 22%), transparent 23%),
radial-gradient(circle at 83% 36%, rgb(154 70 255 / 16%), transparent 24%),
radial-gradient(circle at 50% 80%, rgb(0 211 177 / 10%), transparent 25%);
filter: blur(40px);
}
.setup-assistant--step-0 .setup-assistant__page {
padding: 64px 24px 0;
overflow: hidden;
}
.setup-welcome__hero {
position: relative;
display: grid;
width: 286px;
height: 274px;
flex: none;
place-items: center;
isolation: isolate;
}
.setup-welcome__hero::before {
position: absolute;
z-index: -1;
top: 48px;
right: 12px;
bottom: 44px;
left: 12px;
background:
linear-gradient(
90deg,
transparent,
rgb(55 122 255 / 13%) 28%,
rgb(121 91 255 / 11%) 72%,
transparent
),
linear-gradient(180deg, transparent, rgb(39 126 255 / 9%) 48%, transparent);
filter: blur(22px);
content: '';
animation: setup-welcome-breathe 5s ease-in-out infinite;
}
.setup-welcome__hero::after {
position: absolute;
top: 51px;
right: 28px;
bottom: 47px;
left: 28px;
border-top: 1px solid rgb(142 184 255 / 9%);
border-bottom: 1px solid rgb(142 184 255 / 9%);
content: '';
}
.setup-welcome__greetings {
position: relative;
width: 100%;
height: 104px;
filter: drop-shadow(0 16px 30px rgb(15 89 255 / 20%));
}
.setup-welcome__greetings span {
position: absolute;
inset: 0;
display: grid;
color: #ffffff;
font-family: 'Snell Roundhand', 'Segoe Script', 'Brush Script MT', cursive;
font-size: 68px;
font-weight: 500;
letter-spacing: -0.06em;
opacity: 0;
place-items: center;
transform: translateY(12px) scale(0.94);
animation: setup-welcome-language 9s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
.setup-welcome__greetings span:nth-child(2) {
animation-delay: 3s;
}
.setup-welcome__greetings span:nth-child(3) {
animation-delay: 6s;
}
.setup-welcome__signature {
position: absolute;
bottom: 66px;
display: flex;
align-items: center;
gap: 5px;
}
.setup-welcome__signature span {
width: 22px;
height: 2px;
background: rgb(109 170 255 / 20%);
}
.setup-welcome__signature span:nth-child(2) {
width: 42px;
background: linear-gradient(90deg, #4b9fff, #9677ff);
box-shadow: 0 0 12px rgb(73 148 255 / 34%);
}
.setup-welcome__copy {
position: relative;
z-index: 1;
margin-top: -13px;
}
.setup-welcome__copy h1 {
margin-top: 8px;
font-size: 31px;
}
.setup-welcome__eyebrow {
margin: 0;
color: #67aaff;
font-size: 10px;
font-weight: 760;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.setup-welcome__copy .setup-assistant__lead {
max-width: 300px;
margin-inline: auto;
}
.setup-welcome__footer {
display: flex;
width: calc(100% + 48px);
box-sizing: border-box;
margin: auto -24px 0;
padding: 17px 24px 13px;
flex-direction: column;
align-items: center;
border-top: 1px solid rgb(255 255 255 / 7%);
background: linear-gradient(
180deg,
rgb(7 10 20 / 30%),
rgb(4 7 13 / 94%) 28%
);
backdrop-filter: blur(24px);
}
.setup-welcome__privacy {
display: flex;
align-items: center;
justify-content: center;
gap: 9px;
color: rgb(226 235 250 / 56%);
font-size: 9px;
font-weight: 600;
}
.setup-welcome__privacy span {
display: inline-flex;
align-items: center;
gap: 5px;
}
.setup-welcome__privacy i {
width: 2px;
height: 2px;
border-radius: 50%;
background: rgb(255 255 255 / 28%);
}
.setup-welcome__footer .setup-assistant__primary {
min-height: 51px;
margin-top: 14px;
border-radius: 15px !important;
box-shadow: 0 12px 30px rgb(0 105 255 / 24%);
}
.setup-welcome__home-indicator {
width: 112px;
height: 4px;
margin-top: 12px;
border-radius: 99px;
background: rgb(255 255 255 / 92%);
}
@keyframes setup-welcome-language {
0% {
opacity: 0;
transform: translateY(12px) scale(0.94);
filter: blur(5px);
}
8%,
27% {
opacity: 1;
transform: translateY(0) scale(1);
filter: blur(0);
}
34%,
100% {
opacity: 0;
transform: translateY(-10px) scale(1.03);
filter: blur(4px);
}
}
@keyframes setup-welcome-breathe {
0%,
100% {
transform: scaleX(0.92);
opacity: 0.58;
}
50% {
transform: scaleX(1.04);
opacity: 1;
}
}
.setup-connectivity-card {
position: relative;
width: 100%;
min-height: 150px;
box-sizing: border-box;
margin-top: 22px;
padding: 20px;
overflow: hidden;
border: 1px solid rgb(255 255 255/14%);
border-radius: 25px;
background: linear-gradient(135deg, #18345f, #0a1730 68%);
box-shadow: 0 25px 55px rgb(0 0 0/25%);
text-align: left;
}
.setup-connectivity-card__waves {
position: absolute;
width: 200px;
height: 200px;
right: -90px;
top: -90px;
border: 32px solid rgb(104 171 255/8%);
border-radius: 50%;
box-shadow:
0 0 0 28px rgb(104 171 255/6%),
0 0 0 58px rgb(104 171 255/4%);
}
.setup-connectivity-card__label {
display: block;
color: #8dc0ff;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.1em;
}
.setup-connectivity-card strong {
display: block;
margin-top: 28px;
font-size: 22px;
letter-spacing: -0.03em;
}
.setup-connectivity-card > span:last-of-type {
color: rgb(255 255 255/55%);
font-size: 12px;
}
.setup-connectivity-card svg {
position: absolute;
right: 20px;
bottom: 20px;
color: #7fb7ff;
}
.setup-cloud-form,
.setup-cloud-connected {
display: flex;
width: 100%;
margin-top: 19px;
gap: 10px;
flex-direction: column;
}
.setup-assistant--step-2 .setup-assistant__page {
padding-right: 24px;
padding-bottom: 16px;
padding-left: 24px;
}
.setup-assistant--step-2 .setup-assistant__page h1 {
max-width: 315px;
margin: 7px 0 6px;
font-size: 29px;
}
.setup-cloud-heading {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
}
.setup-assistant--step-2 .setup-assistant__lead {
max-width: 310px;
font-size: 12px;
line-height: 1.35;
}
.setup-assistant--step-2 .setup-assistant__eyebrow {
margin-top: 8px;
}
.setup-cloud-hero {
position: relative;
display: grid;
width: 70px;
height: 70px;
flex: none;
margin-top: -2px;
place-items: center;
color: #b8d7ff;
}
.setup-cloud-hero__orbit {
position: absolute;
inset: 0;
border: 1px solid rgb(112 174 255 / 26%);
border-radius: 50%;
box-shadow: inset 0 0 22px rgb(50 131 255 / 12%);
animation: setup-cloud-orbit 9s linear infinite;
}
.setup-cloud-hero__orbit::before,
.setup-cloud-hero__orbit::after {
position: absolute;
inset: 7px -7px;
border: 1px solid rgb(130 104 255 / 16%);
border-radius: 50%;
content: '';
transform: rotate(58deg);
}
.setup-cloud-hero__orbit::after {
transform: rotate(-58deg);
}
.setup-cloud-hero__glow {
position: absolute;
inset: 13px;
border-radius: 50%;
background: rgb(38 125 255 / 22%);
box-shadow: 0 0 35px 10px rgb(39 115 255 / 22%);
}
.setup-cloud-hero svg {
position: relative;
filter: drop-shadow(0 6px 12px rgb(25 102 255 / 42%));
}
.setup-cloud-hero i {
position: absolute;
width: 3px;
height: 3px;
border-radius: 50%;
background: #8ac1ff;
box-shadow: 0 0 8px #479eff;
}
.setup-cloud-hero i:nth-of-type(1) {
top: 2px;
left: 23px;
}
.setup-cloud-hero i:nth-of-type(2) {
right: -1px;
bottom: 25px;
}
.setup-cloud-hero i:nth-of-type(3) {
bottom: 3px;
left: 11px;
}
.setup-cloud-form {
margin-top: 11px;
gap: 7px;
}
.setup-cloud-identity {
display: flex;
min-height: 47px;
padding: 6px 10px;
box-sizing: border-box;
align-items: center;
gap: 10px;
border: 1px solid rgb(113 170 255 / 12%);
border-radius: 16px;
background: linear-gradient(
135deg,
rgb(49 115 213 / 14%),
rgb(255 255 255 / 4%)
);
text-align: left;
}
.setup-cloud-identity > span {
display: grid;
width: 32px;
height: 32px;
flex: none;
border: 1px solid rgb(255 255 255 / 16%);
border-radius: 50%;
background: linear-gradient(145deg, #398ff6, #6d5bea);
box-shadow:
inset 0 1px rgb(255 255 255 / 28%),
0 8px 18px rgb(27 91 208 / 20%);
color: white;
font-size: 15px;
font-style: normal;
font-weight: 750;
place-items: center;
}
.setup-cloud-identity div {
min-width: 0;
flex: 1;
}
.setup-cloud-identity small,
.setup-cloud-identity strong {
display: block;
}
.setup-cloud-identity small {
color: rgb(255 255 255 / 43%);
font-size: 8px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.setup-cloud-identity strong {
margin-top: 2px;
overflow: hidden;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.setup-cloud-identity em {
color: #8dbdff;
font-style: normal;
font-weight: 600;
}
.setup-cloud-identity > svg {
color: #67d99d;
}
.setup-cloud-fields {
overflow: hidden;
border: 1px solid rgb(255 255 255 / 9%);
border-radius: 17px;
background: rgb(255 255 255 / 5%);
}
.setup-account-field-enter-active,
.setup-account-field-leave-active {
overflow: hidden;
transition:
max-height 280ms cubic-bezier(0.32, 0.72, 0, 1),
opacity 180ms ease,
transform 280ms cubic-bezier(0.32, 0.72, 0, 1);
}
.setup-account-field-enter-from,
.setup-account-field-leave-to {
max-height: 0;
opacity: 0;
transform: translateY(-8px);
}
.setup-account-field-enter-to,
.setup-account-field-leave-from {
max-height: 58px;
opacity: 1;
transform: translateY(0);
}
.setup-cloud-fields :deep(.sky-field) {
border: 0;
border-radius: 0;
background: transparent;
}
.setup-cloud-fields :deep(.sky-field__inner) {
padding: 0;
}
.setup-cloud-fields :deep(.sky-field + .sky-field) {
border-top: 1px solid rgb(255 255 255 / 8%);
}
.setup-cloud-fields :deep(.sky-field) {
padding: 3px 12px;
}
.setup-cloud-fields :deep(.sky-field__control),
.setup-cloud-fields :deep(.sky-field__input) {
min-height: 43px;
}
.setup-cloud-fields :deep(.sky-field--floating-label .sky-field__control) {
min-height: 52px;
}
.setup-cloud-fields :deep(.sky-field--floating-label .sky-field__label) {
top: 6px;
left: 12px;
}
.setup-cloud-fields :deep(.sky-field__label) {
color: rgb(255 255 255 / 52%);
}
.setup-cloud-fields :deep(.sky-field__input) {
color: #ffffff;
caret-color: #5ba9ff;
}
.setup-cloud-fields :deep(.sky-field__input::placeholder) {
color: rgb(255 255 255 / 24%);
}
.setup-cloud-suffix {
color: #82b8ff;
font-size: 11px;
font-weight: 650;
white-space: nowrap;
}
.setup-cloud-strength {
display: flex;
align-items: center;
gap: 9px;
color: rgb(255 255 255 / 42%);
font-size: 9px;
}
.setup-cloud-strength > div {
display: flex;
flex: 1;
gap: 4px;
}
.setup-cloud-strength span {
height: 3px;
flex: 1;
border-radius: 99px;
background: rgb(255 255 255 / 10%);
}
.setup-cloud-strength span.active {
background: linear-gradient(90deg, #318bff, #66c0ff);
box-shadow: 0 0 7px rgb(49 139 255 / 34%);
}
.setup-account-strength-enter-active,
.setup-account-strength-leave-active {
transition:
opacity 180ms ease,
transform 240ms cubic-bezier(0.32, 0.72, 0, 1);
}
.setup-account-strength-enter-from,
.setup-account-strength-leave-to {
opacity: 0;
transform: translateY(-5px);
}
.setup-cloud-security-note {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
color: rgb(199 215 238 / 46%);
font-size: 9px;
}
.setup-assistant--step-2 .setup-assistant__primary {
min-height: 46px;
}
.setup-assistant--step-2 .setup-assistant__later {
padding: 7px;
}
@keyframes setup-cloud-orbit {
to {
transform: rotate(360deg);
}
}
.setup-assistant__selector {
position: relative;
display: grid;
padding: 3px;
grid-template-columns: 1fr 1fr;
border-radius: 13px;
background: rgb(255 255 255/8%);
}
.setup-assistant__selector-indicator {
position: absolute;
z-index: 0;
top: 3px;
bottom: 3px;
left: 3px;
width: calc(50% - 3px);
border: 1px solid rgb(255 255 255 / 7%);
border-radius: 10px;
background: rgb(255 255 255 / 13%);
box-shadow:
0 3px 12px rgb(0 0 0 / 20%),
inset 0 1px rgb(255 255 255 / 7%);
transition: transform 320ms cubic-bezier(0.32, 0.72, 0, 1);
}
.setup-assistant__selector-indicator--register {
transform: translateX(100%);
}
.setup-assistant__selector button {
position: relative;
z-index: 1;
height: 34px;
border: 0;
border-radius: 10px;
color: rgb(255 255 255/55%);
background: transparent;
font-weight: 650;
transition: color 220ms ease;
}
.setup-assistant__selector button.active {
color: white;
}
.setup-account-copy-enter-active,
.setup-account-copy-leave-active {
transition:
opacity 170ms ease,
transform 230ms cubic-bezier(0.32, 0.72, 0, 1);
}
.setup-account-copy-enter-from {
opacity: 0;
transform: translateY(7px);
}
.setup-account-copy-leave-to {
opacity: 0;
transform: translateY(-5px);
}
.setup-account-action-enter-active,
.setup-account-action-leave-active {
transition:
opacity 140ms ease,
transform 180ms ease;
}
.setup-account-action-enter-from {
opacity: 0;
transform: translateY(5px);
}
.setup-account-action-leave-to {
opacity: 0;
transform: translateY(-5px);
}
.setup-cloud-connected {
align-items: center;
padding-top: 35px;
color: #7ee2ad;
}
.setup-cloud-connected strong {
color: white;
font-size: 18px;
}
.setup-cloud-connected span {
color: rgb(255 255 255/55%);
font-size: 13px;
}
.setup-security-visual {
position: relative;
display: grid;
width: 100%;
height: 108px;
margin-top: 17px;
place-items: center;
border: 1px solid rgb(255 255 255/9%);
border-radius: 24px;
background: linear-gradient(145deg, rgb(46 187 120/15%), rgb(255 255 255/4%));
}
.setup-security-visual > div {
display: flex;
gap: 9px;
}
.setup-security-visual span {
width: 11px;
height: 11px;
border-radius: 50%;
background: #8fe4b6;
box-shadow: 0 0 15px rgb(88 222 149/38%);
}
.setup-security-visual svg {
position: absolute;
right: 18px;
bottom: 15px;
color: rgb(126 226 173/32%);
}
.setup-passcode-length {
display: grid;
width: 100%;
margin-top: 12px;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.setup-passcode-length button {
display: flex;
min-height: 57px;
padding: 9px 11px;
align-items: center;
justify-content: space-between;
border: 1px solid rgb(255 255 255 / 9%);
border-radius: 16px;
color: #ffffff;
background: rgb(255 255 255 / 5%);
text-align: left;
transition:
border-color 160ms ease,
background 160ms ease,
transform 120ms ease;
}
.setup-passcode-length button:active {
transform: scale(0.975);
}
.setup-passcode-length button.selected {
border-color: rgb(94 172 255 / 72%);
background: linear-gradient(
145deg,
rgb(42 132 245 / 23%),
rgb(64 96 210 / 10%)
);
box-shadow:
inset 0 1px rgb(255 255 255 / 9%),
0 9px 22px rgb(23 91 193 / 12%);
}
.setup-passcode-length button > span,
.setup-passcode-length b,
.setup-passcode-length small {
display: block;
}
.setup-passcode-length b {
font-size: 18px;
line-height: 1;
}
.setup-passcode-length small {
margin-top: 4px;
color: rgb(255 255 255 / 48%);
font-size: 9px;
}
.setup-passcode-length svg {
padding: 3px;
border-radius: 50%;
background: #318cf5;
}
.setup-assistant--step-3 .setup-assistant__notice {
margin: 12px 0;
}
.setup-choice-grid {
display: grid;
width: 100%;
margin-top: 22px;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.setup-choice-grid button {
position: relative;
padding: 9px 7px 12px;
border: 1px solid rgb(255 255 255/9%);
border-radius: 19px;
color: white;
background: rgb(255 255 255/5%);
}
.setup-choice-grid button.selected {
border-color: #559eff;
background: rgb(57 125 235/15%);
}
.setup-choice-grid button > svg {
position: absolute;
top: 7px;
right: 7px;
padding: 2px;
border-radius: 50%;
background: #398cf5;
}
.setup-choice-grid strong {
display: block;
margin-top: 9px;
font-size: 11px;
}
.setup-appearance-preview {
display: flex;
height: 92px;
padding: 8px;
gap: 5px;
flex-direction: column;
border-radius: 12px;
background: #f5f6f8;
}
.setup-appearance-preview i {
display: block;
height: 14px;
border-radius: 4px;
background: #d8dbe0;
}
.setup-appearance-preview i:first-child {
width: 58%;
height: 25px;
background: #18191d;
}
.setup-appearance-preview--dark {
background: #17181c;
}
.setup-appearance-preview--dark i {
background: #31333a;
}
.setup-appearance-preview--dark i:first-child {
background: #f6f7fa;
}
.setup-appearance-preview--automatic {
background: linear-gradient(135deg, #f5f6f8 50%, #17181c 50%);
}
.setup-appearance-preview--automatic i {
background: rgb(120 122 130/40%);
}
.setup-mode-stack {
display: flex;
width: 100%;
margin-top: 22px;
gap: 10px;
flex-direction: column;
}
.setup-mode-stack button {
display: grid;
min-height: 95px;
padding: 13px;
grid-template-columns: 65px 1fr 25px;
gap: 13px;
align-items: center;
border: 1px solid rgb(255 255 255/9%);
border-radius: 21px;
color: white;
background: rgb(255 255 255/5%);
text-align: left;
}
.setup-mode-stack button.selected {
border-color: rgb(96 166 255/75%);
background: linear-gradient(100deg, rgb(41 110 204/22%), rgb(255 255 255/5%));
}
.setup-mode-stack__orb {
width: 62px;
height: 62px;
border-radius: 20px;
background: linear-gradient(145deg, #3e82e8, #172a4c);
box-shadow: inset 0 1px rgb(255 255 255/25%);
}
.setup-mode-stack__orb--ultimate {
background: radial-gradient(
circle at 28% 25%,
#f2a7ff,
#6755d5 40%,
#142544 80%
);
box-shadow:
0 8px 25px rgb(139 82 238/25%),
inset 0 1px rgb(255 255 255/45%);
}
.setup-mode-stack strong,
.setup-mode-stack small {
display: block;
}
.setup-mode-stack strong {
font-size: 15px;
}
.setup-mode-stack small {
margin-top: 4px;
color: rgb(255 255 255/48%);
font-size: 11px;
line-height: 1.3;
}
.setup-mode-stack__check {
display: grid;
width: 22px;
height: 22px;
place-items: center;
border: 1px solid rgb(255 255 255/20%);
border-radius: 50%;
}
.selected .setup-mode-stack__check {
border-color: #4b9aff;
background: #398cf5;
}
.setup-wallpapers {
display: grid;
width: 100%;
margin: 19px 0 15px;
grid-template-columns: repeat(4, 1fr);
gap: 9px;
}
.setup-wallpapers button {
position: relative;
height: 82px;
overflow: hidden;
border: 2px solid transparent;
border-radius: 17px;
box-shadow: inset 0 0 0 1px rgb(255 255 255/12%);
}
.setup-wallpapers button.selected {
border-color: white;
box-shadow: 0 0 0 2px #328aff;
}
.setup-wallpapers svg {
position: absolute;
right: 5px;
bottom: 5px;
padding: 3px;
border-radius: 50%;
background: #2585f5;
}
.setup-toggle-card {
width: 100%;
margin-top: 25px;
overflow: hidden;
border: 1px solid rgb(255 255 255/9%);
border-radius: 22px;
background: rgb(255 255 255/5%);
}
.setup-toggle-card button {
display: flex;
width: 100%;
min-height: 82px;
padding: 14px;
align-items: center;
justify-content: space-between;
border: 0;
color: white;
background: transparent;
text-align: left;
}
.setup-toggle-card button + button {
border-top: 1px solid rgb(255 255 255/8%);
}
.setup-toggle-card button:disabled {
opacity: 0.4;
}
.setup-toggle-card strong,
.setup-toggle-card small {
display: block;
}
.setup-toggle-card strong {
font-size: 14px;
}
.setup-toggle-card small {
max-width: 245px;
margin-top: 3px;
color: rgb(255 255 255/48%);
font-size: 11px;
}
.setup-toggle-card i {
position: relative;
width: 45px;
height: 27px;
flex: none;
border-radius: 99px;
background: #3b3d43;
transition: 0.2s;
}
.setup-toggle-card i b {
position: absolute;
width: 23px;
height: 23px;
left: 2px;
top: 2px;
border-radius: 50%;
background: white;
box-shadow: 0 2px 6px rgb(0 0 0/30%);
transition: 0.2s;
}
.setup-toggle-card i.on {
background: #34c759;
}
.setup-toggle-card i.on b {
transform: translateX(18px);
}
.setup-app-list {
display: grid;
width: 100%;
margin: 16px 0 14px;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.setup-app-list button {
display: grid;
min-height: 79px;
padding: 10px;
grid-template-columns: 42px 1fr;
gap: 9px;
align-items: center;
border: 1px solid rgb(255 255 255/8%);
border-radius: 17px;
color: white;
background: rgb(255 255 255/5%);
text-align: left;
}
.setup-app-list button.selected {
border-color: rgb(75 149 249/65%);
background: rgb(52 123 222/13%);
}
.setup-app-list img {
width: 42px;
height: 42px;
border-radius: 12px;
}
.setup-app-list strong,
.setup-app-list small {
display: block;
}
.setup-app-list strong {
overflow: hidden;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.setup-app-list small {
margin-top: 2px;
color: rgb(255 255 255/45%);
font-size: 9px;
line-height: 1.2;
}
.setup-app-list i {
position: absolute;
}
.setup-app-list button {
position: relative;
}
.setup-app-list button > i {
display: grid;
width: 18px;
height: 18px;
right: 6px;
top: 6px;
place-items: center;
border: 1px solid rgb(255 255 255/23%);
border-radius: 50%;
}
.setup-app-list button.selected > i {
border-color: #3d95ff;
background: #3388ee;
}
.setup-ready__halo {
display: grid;
width: 144px;
height: 144px;
margin-top: 70px;
place-items: center;
border: 1px solid rgb(255 255 255/20%);
border-radius: 50%;
color: white;
background: radial-gradient(
circle,
#418df0,
#2852b2 48%,
rgb(61 85 192/12%) 70%
);
box-shadow:
0 0 90px rgb(45 111 255/45%),
inset 0 1px rgb(255 255 255/45%);
}
.setup-ready__summary {
display: flex;
width: 100%;
margin: 28px 0 20px;
gap: 8px;
flex-direction: column;
}
.setup-ready__summary span {
display: flex;
padding: 12px 14px;
gap: 10px;
align-items: center;
border: 1px solid rgb(255 255 255/8%);
border-radius: 14px;
color: #7db7ff;
background: rgb(255 255 255/5%);
text-align: left;
}
.setup-ready__summary b {
overflow: hidden;
color: rgb(255 255 255/74%);
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
@keyframes setup-aurora {
to {
transform: translate3d(8%, -5%, 0) rotate(8deg);
}
}
.setup-forward-enter-active,
.setup-forward-leave-active,
.setup-back-enter-active,
.setup-back-leave-active {
transition:
opacity 0.22s ease,
transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.setup-forward-enter-from {
opacity: 0;
transform: translateX(24px);
}
.setup-forward-leave-to {
opacity: 0;
transform: translateX(-16px);
}
.setup-back-enter-from {
opacity: 0;
transform: translateX(-24px);
}
.setup-back-leave-to {
opacity: 0;
transform: translateX(16px);
}
@media (prefers-reduced-motion: reduce) {
.setup-assistant,
.setup-assistant *,
.setup-assistant *::before,
.setup-assistant *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
}
.setup-assistant__aurora,
.setup-welcome__hero::before,
.setup-cloud-hero__orbit {
animation: none;
}
.setup-welcome__greetings span {
opacity: 0;
animation: none;
transform: none;
}
.setup-welcome__greetings span:first-child {
opacity: 1;
}
.setup-forward-enter-active,
.setup-forward-leave-active,
.setup-back-enter-active,
.setup-back-leave-active {
transition-duration: 0.01ms;
}
.setup-forward-enter-from,
.setup-forward-leave-to,
.setup-back-enter-from,
.setup-back-leave-to,
.setup-account-field-enter-from,
.setup-account-field-leave-to,
.setup-account-strength-enter-from,
.setup-account-strength-leave-to,
.setup-account-copy-enter-from,
.setup-account-copy-leave-to,
.setup-account-action-enter-from,
.setup-account-action-leave-to,
.setup-passcode-length button:active {
transform: none;
}
}
</style>