feat(user-apps): appearance settings (dark mode + reduced motion), product morph, filter sheet
Applies to both user-app and user-app-v2. Logic and structure are shared; each app keeps its own look. Appearance settings - New "تنظیمات ظاهری" page (/settings/appearance), opened from the dashboard and available without login. It offers a light/dark/system theme and a "fast & light" motion switch. - appearanceStore saves the choice to localStorage. A small inline boot script in index.html applies it before first paint so there is no white flash. The "system" option follows OS changes live. - New ToggleSwitch component. Dark mode - Theme LESS tokens now compile to CSS custom properties. Light/dark values come from a generated virtual:palette.css (themeConfig/ appearance.ts, with colors.json + new colors.dark.json). - New LESS alpha() plugin replaces fade() on theme tokens. - New semantic tokens: page, onInk, heroSurface, hairlineStrong, photoMat, photoStage. - The dark palette is layered (page < card < panel < border) so surfaces separate without shadows. - Hard-coded colors were replaced by tokens according to their role. - antd inputs/text get dark overrides; vant uses its own .van-theme-dark. - Light mode looks the same as before. Reduced motion - With html.motion-reduced, transitions are capped at 120ms, keyframe loops stop, and backdrop blurs and permanent will-change are dropped. - Spinners and the toast progress bar (which closes the toast) are exempt via .keep-motion. - Carousel autoplay, the v1 Lottie and the product morph honor the setting. Product detail - Opening a product is now a container-transform morph from the tapped card (ProductMorph). It replaces the bottom Drawer and uses composited transform/opacity only. - The detail view is seeded with the card's data, so it no longer opens blank and jumps. Filters and sheets - Filters are now a full-width bottom sheet. Selections stay a draft until applied, so the result list no longer re-renders while dragging the slider. - DropDown collapse animates grid-template-rows instead of a capped max-height. - v2 IslandSheet is restyled to the v2 design language (API unchanged). Fixes - filtersStore.reset() shallow-copied shared arrays, so clearing the filters never cleared the selected brands.
This commit is contained in:
@@ -170,7 +170,7 @@
|
||||
<footer class="reg__footer">
|
||||
<div class="reg__footerInner">
|
||||
<button type="submit" class="reg__cta" :class="{ 'is-loading': loading }" :disabled="loading">
|
||||
<span v-if="loading" class="reg__spinner" aria-hidden="true" />
|
||||
<span v-if="loading" class="reg__spinner keep-motion" aria-hidden="true" />
|
||||
<span>{{ step === 1 ? 'ادامه' : loading ? 'در حال ثبت…' : 'ثبت درخواست' }}</span>
|
||||
<Icon v-if="step === 1" icon="vuesax-linear:arrow-left" size="18" />
|
||||
</button>
|
||||
@@ -248,7 +248,7 @@ async function onFinish() {
|
||||
<style scoped lang="less">
|
||||
.reg {
|
||||
min-height: 100vh;
|
||||
background: @canvas;
|
||||
background: @page;
|
||||
}
|
||||
|
||||
.reg__body {
|
||||
@@ -407,12 +407,12 @@ async function onFinish() {
|
||||
}
|
||||
|
||||
:deep(.ant-input::placeholder) {
|
||||
color: #b4b8c0;
|
||||
color: @inkFaint;
|
||||
}
|
||||
|
||||
:deep(.ant-input:hover),
|
||||
:deep(.ant-input-affix-wrapper:hover) {
|
||||
border-color: darken(@hairline, 8%);
|
||||
border-color: @hairlineStrong;
|
||||
}
|
||||
|
||||
:deep(.ant-input:focus),
|
||||
@@ -420,7 +420,7 @@ async function onFinish() {
|
||||
:deep(.ant-input-affix-wrapper-focused) {
|
||||
border-color: @wine;
|
||||
background: @canvas;
|
||||
box-shadow: 0 0 0 4px fade(@wine, 10%);
|
||||
box-shadow: 0 0 0 4px alpha(@wine, 10%);
|
||||
|
||||
.ant-input-prefix {
|
||||
color: @wine;
|
||||
@@ -435,12 +435,12 @@ async function onFinish() {
|
||||
:deep(.ant-form-item-has-error .ant-input),
|
||||
:deep(.ant-form-item-has-error .ant-input-affix-wrapper) {
|
||||
border-color: @red !important;
|
||||
background: fade(@red, 3%);
|
||||
background: alpha(@red, 3%);
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-has-error .ant-input:focus),
|
||||
:deep(.ant-form-item-has-error .ant-input-affix-wrapper-focused) {
|
||||
box-shadow: 0 0 0 4px fade(@red, 10%) !important;
|
||||
box-shadow: 0 0 0 4px alpha(@red, 10%) !important;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-has-error .ant-input-affix-wrapper .ant-input) {
|
||||
@@ -501,7 +501,7 @@ async function onFinish() {
|
||||
bottom: 0;
|
||||
z-index: 15;
|
||||
padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
|
||||
background: fade(@canvas, 86%);
|
||||
background: alpha(@canvas, 86%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
||||
backdrop-filter: blur(16px) saturate(180%);
|
||||
border-top: 1px solid @hairline;
|
||||
@@ -526,9 +526,9 @@ async function onFinish() {
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
color: @onInk;
|
||||
background: @ink;
|
||||
box-shadow: 0 10px 22px -12px fade(@ink, 70%);
|
||||
box-shadow: 0 10px 22px -12px alpha(@ink, 70%);
|
||||
transition: transform 0.15s ease, opacity 0.2s ease;
|
||||
|
||||
&:active {
|
||||
@@ -609,7 +609,7 @@ async function onFinish() {
|
||||
height: 88px;
|
||||
border-radius: 26px;
|
||||
color: @green;
|
||||
background: fade(@green, 10%);
|
||||
background: alpha(@green, 10%);
|
||||
animation: reg-pop 0.6s cubic-bezier(0.2, 1.5, 0.4, 1) both;
|
||||
|
||||
:deep(svg) {
|
||||
|
||||
Reference in New Issue
Block a user