341 lines
8.8 KiB
Vue
341 lines
8.8 KiB
Vue
<!--
|
||||
|
|
تنظیمات ظاهری — 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 ─────────────────────────────────────────── */
|
|||
|
|
// White hairline-bordered cards at the 16px radius on the soft-grey ground,
|
|||
|
|
// chosen = a brand-green border with a green check (the app's chips and
|
|||
|
|
// variant picker); the motion row is a card with the dashboard tiles' grey
|
|||
|
|
// icon tile.
|
|||
|
|
.ap {
|
|||
|
|
background: @canvasSoft;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ap__head {
|
|||
|
|
margin-bottom: 13px;
|
|||
|
|
}
|
|||
|
|
.ap__title {
|
|||
|
|
font-size: 15px;
|
|||
|
|
font-weight: 800;
|
|||
|
|
color: @ink;
|
|||
|
|
letter-spacing: -0.1px;
|
|||
|
|
}
|
|||
|
|
.ap__sub {
|
|||
|
|
margin-top: 5px;
|
|||
|
|
font-size: 11.5px;
|
|||
|
|
color: @inkFaint;
|
|||
|
|
}
|
|||
|
|
.ap__note {
|
|||
|
|
margin-top: 10px;
|
|||
|
|
font-size: 11.5px;
|
|||
|
|
color: @inkMuted;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ap__theme {
|
|||
|
|
padding: 8px 8px 11px;
|
|||
|
|
border: 1px solid @hairline;
|
|||
|
|
border-radius: 16px;
|
|||
|
|
background: @canvas;
|
|||
|
|
color: @inkMuted;
|
|||
|
|
}
|
|||
|
|
.ap__theme.is-on {
|
|||
|
|
color: @ink;
|
|||
|
|
border-color: @primary;
|
|||
|
|
box-shadow: 0 0 0 1px @primary;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ap__preview {
|
|||
|
|
border-radius: 11px;
|
|||
|
|
box-shadow: 0 0 0 1px rgba(20, 22, 26, 0.07);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ap__themeLabel {
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ap__check {
|
|||
|
|
width: 22px;
|
|||
|
|
height: 22px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
color: #fff;
|
|||
|
|
background: @primary;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ap__row {
|
|||
|
|
padding: 14px;
|
|||
|
|
border: 1px solid @hairline;
|
|||
|
|
border-radius: 16px;
|
|||
|
|
background: @canvas;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ap__rowIcon {
|
|||
|
|
width: 38px;
|
|||
|
|
height: 38px;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
color: @inkSoft;
|
|||
|
|
background: @hairlineSoft;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ap__rowText b {
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 800;
|
|||
|
|
color: @ink;
|
|||
|
|
}
|
|||
|
|
.ap__rowText small {
|
|||
|
|
font-size: 11px;
|
|||
|
|
line-height: 1.9;
|
|||
|
|
color: @inkFaint;
|
|||
|
|
}
|
|||
|
|
</style>
|