2026-10-04 16:18:56 +03:30
|
|
|
|
<!--
|
|
|
|
|
|
تنظیمات ظاهری — the colour theme and the "fast & light" motion switch.
|
|
|
|
|
|
Reached from the dashboard; open to signed-out users too (these are device
|
|
|
|
|
|
preferences, not account data). State lives in stores/appearanceStore.ts,
|
|
|
|
|
|
which applies it to <html> immediately — nothing to save or confirm.
|
|
|
|
|
|
|
|
|
|
|
|
The theme previews are drawn with FIXED colours, not theme tokens: each one
|
|
|
|
|
|
has to show what that theme looks like, whichever theme is active now.
|
|
|
|
|
|
|
|
|
|
|
|
STRUCTURE IS SHARED, LOOK IS NOT: template and script are identical in
|
|
|
|
|
|
user-app and user-app-v2; only the "Look" block differs, per app.
|
|
|
|
|
|
-->
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="ap">
|
|
|
|
|
|
<PageBar title="تنظیمات ظاهری" back-to="userPage::hasSubRoute" />
|
|
|
|
|
|
|
|
|
|
|
|
<container class="ap__body">
|
|
|
|
|
|
<!-- ── Theme ───────────────────────────────────────────── -->
|
|
|
|
|
|
<section class="ap__section">
|
|
|
|
|
|
<header class="ap__head">
|
|
|
|
|
|
<h2 id="ap-theme" class="ap__title">پوسته برنامه</h2>
|
|
|
|
|
|
<p class="ap__sub">ظاهر روشن یا تیره، یا هماهنگ با تنظیمات گوشی شما</p>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="ap__themes" role="radiogroup" aria-labelledby="ap-theme">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="opt in THEMES"
|
|
|
|
|
|
:key="opt.value"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="radio"
|
|
|
|
|
|
class="ap__theme"
|
|
|
|
|
|
:class="{ 'is-on': appearance.theme === opt.value }"
|
|
|
|
|
|
:aria-checked="appearance.theme === opt.value"
|
|
|
|
|
|
@click="appearance.setTheme(opt.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="ap__preview" :class="`ap__preview--${opt.value}`" aria-hidden="true">
|
|
|
|
|
|
<span class="ap__pvBar"></span>
|
|
|
|
|
|
<span class="ap__pvCard"></span>
|
|
|
|
|
|
<span class="ap__pvLine"></span>
|
|
|
|
|
|
<span class="ap__pvLine ap__pvLine--short"></span>
|
|
|
|
|
|
<span class="ap__pvBtn"></span>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="ap__themeLabel">
|
|
|
|
|
|
<Icon :icon="opt.icon" size="15" />
|
|
|
|
|
|
{{ opt.label }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="ap__check" aria-hidden="true">
|
|
|
|
|
|
<Icon icon="vuesax-linear:tick-circle" size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="appearance.theme === 'system'" class="ap__note">
|
|
|
|
|
|
الان {{ appearance.resolvedTheme === 'dark' ? 'تیره' : 'روشن' }} است، مطابق تنظیمات گوشی.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ── Motion ──────────────────────────────────────────── -->
|
|
|
|
|
|
<section class="ap__section">
|
|
|
|
|
|
<header class="ap__head">
|
|
|
|
|
|
<h2 class="ap__title">انیمیشنها</h2>
|
|
|
|
|
|
<p class="ap__sub">برای سرعت بیشتر روی گوشیهای ضعیفتر</p>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- The whole row is the switch: one tap target, one announcement. -->
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="switch"
|
|
|
|
|
|
class="ap__row"
|
|
|
|
|
|
:aria-checked="appearance.reduceMotion"
|
|
|
|
|
|
@click="appearance.setReduceMotion(!appearance.reduceMotion)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="ap__rowIcon" aria-hidden="true">
|
|
|
|
|
|
<Icon icon="vuesax-linear:flash" size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="ap__rowText">
|
|
|
|
|
|
<b>حالت سبک و سریع</b>
|
|
|
|
|
|
<small>
|
|
|
|
|
|
حرکتهای سنگین، افکتهای شیشهای و اسلاید خودکار خاموش میشوند؛ بازخورد کوتاه لمسها و جابهجاییهای سریع
|
|
|
|
|
|
باقی میمانند تا برنامه خشک و بیروح نشود.
|
|
|
|
|
|
</small>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<ToggleSwitch :on="appearance.reduceMotion" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</container>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import Icon from '../../components/icon/Icon.vue'
|
|
|
|
|
|
import PageBar from '../../components/ui/PageBar.vue'
|
|
|
|
|
|
import ToggleSwitch from '../../components/ui/ToggleSwitch.vue'
|
|
|
|
|
|
import { useAppearanceStore, type ThemeChoice } from '../../stores/appearanceStore'
|
|
|
|
|
|
|
|
|
|
|
|
const appearance = useAppearanceStore()
|
|
|
|
|
|
|
|
|
|
|
|
const THEMES: { value: ThemeChoice; label: string; icon: string }[] = [
|
|
|
|
|
|
{ value: 'light', label: 'روشن', icon: 'vuesax-linear:sun-1' },
|
|
|
|
|
|
{ value: 'dark', label: 'تیره', icon: 'vuesax-linear:moon' },
|
|
|
|
|
|
{ value: 'system', label: 'مطابق سیستم', icon: 'vuesax-linear:monitor-mobbile' },
|
|
|
|
|
|
]
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
|
/* ── Structure (same in both apps) ──────────────────────────── */
|
|
|
|
|
|
.ap {
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__body {
|
|
|
|
|
|
padding-top: 76px;
|
|
|
|
|
|
padding-bottom: 90px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 26px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__title,
|
|
|
|
|
|
.ap__sub,
|
|
|
|
|
|
.ap__note {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__themes {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__theme {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
-webkit-tap-highlight-color: transparent;
|
|
|
|
|
|
transition: transform 0.15s ease, border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__theme:active {
|
|
|
|
|
|
transform: scale(0.97);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__themeLabel {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__check {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 8px;
|
|
|
|
|
|
inset-inline-end: 8px;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: scale(0.6);
|
|
|
|
|
|
transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__theme.is-on .ap__check {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Mini screen, fixed colours on purpose (see the header).
|
|
|
|
|
|
.ap__preview {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
aspect-ratio: 3 / 4;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 6%;
|
|
|
|
|
|
padding: 10% 12%;
|
|
|
|
|
|
--pv-bg: #ffffff;
|
|
|
|
|
|
--pv-bar: #f4f5f6;
|
|
|
|
|
|
--pv-card: #ecedef;
|
|
|
|
|
|
--pv-line: #dadce0;
|
|
|
|
|
|
--pv-btn: #14161a;
|
|
|
|
|
|
background: var(--pv-bg);
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__preview--dark {
|
|
|
|
|
|
--pv-bg: #16171a;
|
|
|
|
|
|
--pv-bar: #1e2024;
|
|
|
|
|
|
--pv-card: #2b2e34;
|
|
|
|
|
|
--pv-line: #3a3e45;
|
|
|
|
|
|
--pv-btn: #f1f2f4;
|
|
|
|
|
|
}
|
|
|
|
|
|
// System: the light screen with its lower-left half in dark.
|
|
|
|
|
|
.ap__preview--system::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
background: #16171a;
|
|
|
|
|
|
clip-path: polygon(100% 0, 100% 100%, 0 100%);
|
|
|
|
|
|
opacity: 0.94;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__pvBar {
|
|
|
|
|
|
height: 9%;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
background: var(--pv-bar);
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__pvCard {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
border-radius: 7px;
|
|
|
|
|
|
background: var(--pv-card);
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__pvLine {
|
|
|
|
|
|
height: 6%;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
background: var(--pv-line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__pvLine--short {
|
|
|
|
|
|
width: 60%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__pvBtn {
|
|
|
|
|
|
height: 11%;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: var(--pv-btn);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__row {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
-webkit-tap-highlight-color: transparent;
|
|
|
|
|
|
transition: transform 0.15s ease, border-color 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__row:active {
|
|
|
|
|
|
transform: scale(0.985);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__rowIcon {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__rowText {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 3px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ── Look: user-app-v2 ──────────────────────────────────────── */
|
|
|
|
|
|
// Borderless @hairlineSoft panels at the 22px radius, chosen = an @ink ring
|
|
|
|
|
|
// with a filled @ink check (the app's chips); the motion row is the
|
|
|
|
|
|
// dashboard's own settings row, wine icon tile and all.
|
|
|
|
|
|
.ap {
|
|
|
|
|
|
background: @page;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__head {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: @ink;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__sub {
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__note {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
color: @inkMuted;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__theme {
|
|
|
|
|
|
padding: 10px 10px 12px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 22px;
|
|
|
|
|
|
background: @hairlineSoft;
|
|
|
|
|
|
color: @inkMuted;
|
|
|
|
|
|
box-shadow: inset 0 0 0 1.5px transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__theme.is-on {
|
|
|
|
|
|
color: @ink;
|
2026-10-14 13:31:20 +03:30
|
|
|
|
box-shadow: inset 0 0 0 1.5px @wine;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__preview {
|
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
|
box-shadow: 0 0 0 1px rgba(20, 22, 26, 0.06);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__themeLabel {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__check {
|
|
|
|
|
|
width: 22px;
|
|
|
|
|
|
height: 22px;
|
|
|
|
|
|
border-radius: 50%;
|
2026-10-14 13:31:20 +03:30
|
|
|
|
color: #fff;
|
|
|
|
|
|
background: @wine;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__row {
|
|
|
|
|
|
padding: 13px 14px;
|
|
|
|
|
|
border: 1px solid @hairline;
|
|
|
|
|
|
border-radius: 17px;
|
|
|
|
|
|
background: @canvas;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__row:active {
|
|
|
|
|
|
border-color: alpha(@wine, 20%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__rowIcon {
|
|
|
|
|
|
width: 38px;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
color: @wine;
|
|
|
|
|
|
background: @wineWash;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ap__rowText b {
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: @ink;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ap__rowText small {
|
|
|
|
|
|
font-size: 10.5px;
|
|
|
|
|
|
line-height: 1.9;
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|