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.
739 lines
19 KiB
Vue
739 lines
19 KiB
Vue
<!--
|
||
Store sign-up, redesigned as a two-step form:
|
||
1. اطلاعات شما — first/last name, mobile
|
||
2. اطلاعات فروشگاه — store name, store phone, city, address
|
||
followed by an in-page "request received" state.
|
||
|
||
Why two steps: seven fields in one column read as a wall on a phone. Split
|
||
by who they describe, each step fits one screen, the progress bar sets the
|
||
expectation, and the CTA sits in a sticky footer so it is always in reach.
|
||
|
||
Ant's Form only validates the FormItems currently mounted, so a submit on
|
||
step 1 validates just step 1's fields; `onFinish` then advances instead of
|
||
registering. Enter on the keyboard therefore behaves like the CTA.
|
||
|
||
Numeric fields are `type="tel"` + `inputmode="numeric"` (a real number input
|
||
drops the leading 0 and shows spinners), and Persian/Arabic digits are
|
||
converted to Latin as the user types, so the validators accept them.
|
||
|
||
STUBBED, unchanged from before: legacy's POST /major/register has no
|
||
counterpart in apps/api yet. Submitting shows the "awaiting review" state
|
||
without persisting anything — the copy says the request is under review,
|
||
which is what legacy said too, so it does not claim an account exists.
|
||
-->
|
||
<template>
|
||
<div class="reg">
|
||
<PageBar title="ثبت نام" :back-action="goBack" />
|
||
|
||
<!-- Success -->
|
||
<Transition name="reg-fade" mode="out-in">
|
||
<container v-if="done" key="done" class="reg__body reg__done">
|
||
<div class="reg__doneMark" aria-hidden="true">
|
||
<span class="reg__doneRing" />
|
||
<Icon icon="vuesax-linear:tick-circle" size="40" />
|
||
</div>
|
||
<h2 class="reg__doneTitle">درخواست شما ثبت شد</h2>
|
||
<p class="reg__doneText">
|
||
اطلاعات فروشگاه {{ formModel.store_name }} برای کارشناسان ما ارسال شد. پس از بررسی و تایید، میتوانید با
|
||
شماره <span class="reg__ltr">{{ formModel.mobile }}</span> وارد شوید.
|
||
</p>
|
||
<div class="reg__doneActions">
|
||
<button type="button" class="reg__cta" @click="router.push({ name: 'home' })">بازگشت به صفحه اصلی</button>
|
||
<button type="button" class="reg__ghost" @click="emit('register', false)">رفتن به صفحه ورود</button>
|
||
</div>
|
||
</container>
|
||
|
||
<!-- Form -->
|
||
<container v-else key="form" class="reg__body">
|
||
<header class="reg__head">
|
||
<div class="reg__steps" role="progressbar" :aria-valuenow="step" aria-valuemin="1" aria-valuemax="2">
|
||
<span class="reg__seg is-on" />
|
||
<span class="reg__seg" :class="{ 'is-on': step === 2 }" />
|
||
</div>
|
||
<p class="reg__stepLabel">مرحله {{ step === 1 ? '۱' : '۲' }} از ۲</p>
|
||
<h2 class="reg__title">{{ step === 1 ? 'اطلاعات شما' : 'اطلاعات فروشگاه' }}</h2>
|
||
<p class="reg__sub">
|
||
{{
|
||
step === 1
|
||
? 'برای ساخت حساب فروشگاه، ابتدا خودتان را معرفی کنید.'
|
||
: 'این اطلاعات برای ارسال سفارش و تایید حساب استفاده میشود.'
|
||
}}
|
||
</p>
|
||
</header>
|
||
|
||
<Form ref="formRef" :model="formModel" layout="vertical" class="reg__form" @finish="onFinish">
|
||
<Transition :name="step === 2 ? 'reg-next' : 'reg-prev'" mode="out-in">
|
||
<!-- Step 1 -->
|
||
<div v-if="step === 1" key="s1" class="reg__fields">
|
||
<div class="reg__row">
|
||
<FormItem name="fname" label="نام" :rules="[need('نام خود')]">
|
||
<Input
|
||
v-model:value="formModel.fname"
|
||
placeholder="مثلاً علی"
|
||
autocomplete="given-name"
|
||
enterkeyhint="next"
|
||
/>
|
||
</FormItem>
|
||
<FormItem name="lname" label="نام خانوادگی" :rules="[need('نام خانوادگی')]">
|
||
<Input
|
||
v-model:value="formModel.lname"
|
||
placeholder="مثلاً محمدی"
|
||
autocomplete="family-name"
|
||
enterkeyhint="next"
|
||
/>
|
||
</FormItem>
|
||
</div>
|
||
|
||
<FormItem
|
||
name="mobile"
|
||
label="شماره موبایل"
|
||
:rules="[need('شماره موبایل'), { pattern: /^09\d{9}$/, message: 'شماره موبایل باید ۱۱ رقم و با ۰۹ شروع شود' }]"
|
||
extra="کد ورود به این شماره پیامک میشود."
|
||
>
|
||
<Input
|
||
:value="formModel.mobile"
|
||
class="reg__num"
|
||
type="tel"
|
||
inputmode="numeric"
|
||
:maxlength="11"
|
||
placeholder="09123456789"
|
||
autocomplete="tel"
|
||
enterkeyhint="next"
|
||
@update:value="(v: string) => (formModel.mobile = digits(v))"
|
||
>
|
||
<template #prefix><Icon icon="vuesax-linear:mobile" size="18" /></template>
|
||
</Input>
|
||
</FormItem>
|
||
</div>
|
||
|
||
<!-- Step 2 -->
|
||
<div v-else key="s2" class="reg__fields">
|
||
<FormItem name="store_name" label="نام فروشگاه" :rules="[need('نام فروشگاه')]">
|
||
<Input
|
||
v-model:value="formModel.store_name"
|
||
placeholder="مثلاً گالری طلای آفتاب"
|
||
autocomplete="organization"
|
||
enterkeyhint="next"
|
||
>
|
||
<template #prefix><Icon icon="vuesax-linear:shop" size="18" /></template>
|
||
</Input>
|
||
</FormItem>
|
||
|
||
<FormItem
|
||
name="phone"
|
||
label="تلفن فروشگاه"
|
||
:rules="[need('تلفن فروشگاه'), { pattern: /^(0\d{10}|\d{8})$/, message: 'تلفن را با کد شهر وارد کنید، مثلاً 02112345678' }]"
|
||
>
|
||
<Input
|
||
:value="formModel.phone"
|
||
class="reg__num"
|
||
type="tel"
|
||
inputmode="numeric"
|
||
:maxlength="11"
|
||
placeholder="02112345678"
|
||
autocomplete="tel-national"
|
||
enterkeyhint="next"
|
||
@update:value="(v: string) => (formModel.phone = digits(v))"
|
||
>
|
||
<template #prefix><Icon icon="vuesax-linear:call" size="18" /></template>
|
||
</Input>
|
||
</FormItem>
|
||
|
||
<FormItem name="city" label="شهر" :rules="[need('شهر')]">
|
||
<Input
|
||
v-model:value="formModel.city"
|
||
placeholder="مثلاً تهران"
|
||
autocomplete="address-level2"
|
||
enterkeyhint="next"
|
||
>
|
||
<template #prefix><Icon icon="vuesax-linear:buildings" size="18" /></template>
|
||
</Input>
|
||
</FormItem>
|
||
|
||
<FormItem name="address" label="آدرس پستی" :rules="[need('آدرس فروشگاه')]">
|
||
<Textarea
|
||
v-model:value="formModel.address"
|
||
:rows="3"
|
||
placeholder="خیابان، کوچه، پلاک، واحد"
|
||
autocomplete="street-address"
|
||
/>
|
||
</FormItem>
|
||
|
||
<div class="reg__note">
|
||
<Icon icon="vuesax-linear:shield-tick" size="18" />
|
||
<span>حساب شما پس از بررسی اطلاعات توسط کارشناسان فعال میشود.</span>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
|
||
<!-- Sticky action bar; inside the Form so it submits it. -->
|
||
<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 keep-motion" aria-hidden="true" />
|
||
<span>{{ step === 1 ? 'ادامه' : loading ? 'در حال ثبت…' : 'ثبت درخواست' }}</span>
|
||
<Icon v-if="step === 1" icon="vuesax-linear:arrow-left" size="18" />
|
||
</button>
|
||
<p v-if="step === 1" class="reg__switch">
|
||
حساب دارید؟
|
||
<button type="button" @click="emit('register', false)">وارد شوید</button>
|
||
</p>
|
||
</div>
|
||
</footer>
|
||
</Form>
|
||
</container>
|
||
</Transition>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { Form, FormItem, Input, Textarea } from 'ant-design-vue'
|
||
import Icon from '../../components/icon/Icon.vue'
|
||
import PageBar from '../../components/ui/PageBar.vue'
|
||
|
||
const emit = defineEmits<{ (e: 'register', value: boolean): void }>()
|
||
const router = useRouter()
|
||
|
||
const formRef = ref()
|
||
const step = ref<1 | 2>(1)
|
||
const loading = ref(false)
|
||
const done = ref(false)
|
||
|
||
const formModel = reactive({
|
||
fname: undefined as string | undefined,
|
||
lname: undefined as string | undefined,
|
||
mobile: undefined as string | undefined,
|
||
store_name: undefined as string | undefined,
|
||
phone: undefined as string | undefined,
|
||
city: undefined as string | undefined,
|
||
address: undefined as string | undefined,
|
||
})
|
||
|
||
const need = (what: string) => ({ required: true, whitespace: true, message: `لطفاً ${what} را وارد کنید` })
|
||
|
||
// Persian (۰-۹) and Arabic (٠-٩) digits → Latin, everything else dropped.
|
||
function digits(v: string): string {
|
||
return (v ?? '')
|
||
.replace(/[۰-۹]/g, (d) => String(d.charCodeAt(0) - 0x06f0))
|
||
.replace(/[٠-٩]/g, (d) => String(d.charCodeAt(0) - 0x0660))
|
||
.replace(/\D/g, '')
|
||
}
|
||
|
||
function goBack() {
|
||
if (done.value) router.push({ name: 'home' })
|
||
else if (step.value === 2) step.value = 1
|
||
else emit('register', false)
|
||
}
|
||
|
||
async function onFinish() {
|
||
if (step.value === 1) {
|
||
step.value = 2
|
||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||
return
|
||
}
|
||
try {
|
||
loading.value = true
|
||
// Stubbed — no create-customer endpoint exists in apps/api yet.
|
||
await new Promise((r) => setTimeout(r, 600))
|
||
done.value = true
|
||
window.scrollTo({ top: 0 })
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.reg {
|
||
min-height: 100vh;
|
||
background: @page;
|
||
}
|
||
|
||
.reg__body {
|
||
padding-top: 78px;
|
||
// Room for the sticky footer.
|
||
padding-bottom: calc(150px + env(safe-area-inset-bottom, 0px));
|
||
}
|
||
|
||
/* ---------- Header + progress ---------- */
|
||
|
||
.reg__head {
|
||
padding: 6px 2px 22px;
|
||
}
|
||
|
||
.reg__steps {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 6px;
|
||
}
|
||
|
||
.reg__seg {
|
||
position: relative;
|
||
height: 4px;
|
||
border-radius: 4px;
|
||
background: @hairline;
|
||
overflow: hidden;
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
background: @wine;
|
||
transform: scaleX(0);
|
||
transform-origin: right center;
|
||
transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
|
||
}
|
||
|
||
&.is-on::after {
|
||
transform: scaleX(1);
|
||
}
|
||
}
|
||
|
||
.reg__stepLabel {
|
||
margin: 14px 0 0;
|
||
font-size: 11.5px;
|
||
font-weight: 700;
|
||
color: @wine;
|
||
}
|
||
|
||
.reg__title {
|
||
margin: 4px 0 0;
|
||
font-size: 20px;
|
||
font-weight: 800;
|
||
color: @ink;
|
||
letter-spacing: -0.2px;
|
||
}
|
||
|
||
.reg__sub {
|
||
margin: 6px 0 0;
|
||
font-size: 12.5px;
|
||
line-height: 1.8;
|
||
color: @inkMuted;
|
||
}
|
||
|
||
/* ---------- Fields ---------- */
|
||
|
||
// minmax(0, 1fr): a plain 1fr column won't shrink below its content's
|
||
// min-width, which pushed the second name field off the screen.
|
||
.reg__row {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||
gap: 10px;
|
||
}
|
||
|
||
.reg__form {
|
||
// Force label-above-input. Ant's FormItem is a wrapping flex row, so in a
|
||
// narrow cell its label and control ended up side by side.
|
||
:deep(.ant-form-item) {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-wrap: nowrap;
|
||
min-width: 0;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
:deep(.ant-form-item-label),
|
||
:deep(.ant-form-item-control) {
|
||
flex: none;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
min-width: 0;
|
||
text-align: right;
|
||
}
|
||
|
||
:deep(.ant-form-item-label) {
|
||
padding: 0 0 6px;
|
||
line-height: 1.5;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
:deep(.ant-form-item-label > label) {
|
||
height: auto;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
color: @inkSoft;
|
||
|
||
// Ant's required asterisk — every field here is required, so it's noise.
|
||
&::before {
|
||
display: none !important;
|
||
}
|
||
}
|
||
|
||
:deep(.ant-input),
|
||
:deep(.ant-input-affix-wrapper) {
|
||
border-radius: 14px;
|
||
border: 1.5px solid @hairline;
|
||
background: @canvasSoft;
|
||
font-family: inherit;
|
||
font-size: 14px;
|
||
color: @ink;
|
||
box-shadow: none;
|
||
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
:deep(.ant-input) {
|
||
min-height: 50px;
|
||
padding: 12px 14px;
|
||
}
|
||
|
||
:deep(.ant-input-affix-wrapper) {
|
||
min-height: 50px;
|
||
padding: 0 14px;
|
||
|
||
.ant-input {
|
||
min-height: 46px;
|
||
padding: 0;
|
||
border: 0;
|
||
background: transparent;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
|
||
:deep(textarea.ant-input) {
|
||
min-height: 96px;
|
||
line-height: 1.8;
|
||
resize: none;
|
||
}
|
||
|
||
:deep(.ant-input-prefix) {
|
||
margin-inline-end: 10px;
|
||
color: @inkFaint;
|
||
transition: color 0.2s ease;
|
||
}
|
||
|
||
:deep(.ant-input::placeholder) {
|
||
color: @inkFaint;
|
||
}
|
||
|
||
:deep(.ant-input:hover),
|
||
:deep(.ant-input-affix-wrapper:hover) {
|
||
border-color: @hairlineStrong;
|
||
}
|
||
|
||
:deep(.ant-input:focus),
|
||
:deep(.ant-input-focused),
|
||
:deep(.ant-input-affix-wrapper-focused) {
|
||
border-color: @wine;
|
||
background: @canvas;
|
||
box-shadow: 0 0 0 4px alpha(@wine, 10%);
|
||
|
||
.ant-input-prefix {
|
||
color: @wine;
|
||
}
|
||
}
|
||
|
||
:deep(.ant-input-affix-wrapper-focused .ant-input-prefix) {
|
||
color: @wine;
|
||
}
|
||
|
||
// Errors
|
||
:deep(.ant-form-item-has-error .ant-input),
|
||
:deep(.ant-form-item-has-error .ant-input-affix-wrapper) {
|
||
border-color: @red !important;
|
||
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 alpha(@red, 10%) !important;
|
||
}
|
||
|
||
:deep(.ant-form-item-has-error .ant-input-affix-wrapper .ant-input) {
|
||
background: transparent;
|
||
border: 0 !important;
|
||
}
|
||
|
||
:deep(.ant-form-item-explain),
|
||
:deep(.ant-form-item-extra) {
|
||
min-height: 0;
|
||
margin-top: 6px;
|
||
font-size: 11.5px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
:deep(.ant-form-item-extra) {
|
||
color: @inkFaint;
|
||
}
|
||
|
||
// Ant's own feedback icons duplicate the red border.
|
||
:deep(.ant-form-item-children-icon) {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
// Numbers are typed left-to-right but stay aligned with the RTL labels.
|
||
.reg__num :deep(input) {
|
||
direction: ltr;
|
||
text-align: right;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.reg__note {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
margin-top: 4px;
|
||
padding: 12px 14px;
|
||
border-radius: 14px;
|
||
background: @wineWash;
|
||
color: @wine;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
line-height: 1.8;
|
||
|
||
:deep(svg) {
|
||
flex: 0 0 auto;
|
||
margin-top: 2px;
|
||
}
|
||
}
|
||
|
||
/* ---------- Sticky footer ---------- */
|
||
|
||
.reg__footer {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 15;
|
||
padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
|
||
background: alpha(@canvas, 86%);
|
||
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
||
backdrop-filter: blur(16px) saturate(180%);
|
||
border-top: 1px solid @hairline;
|
||
}
|
||
|
||
.reg__footerInner {
|
||
max-width: 736px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.reg__cta {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
min-height: 52px;
|
||
padding: 0 18px;
|
||
border: 0;
|
||
cursor: pointer;
|
||
border-radius: 16px;
|
||
font-family: inherit;
|
||
font-size: 14px;
|
||
font-weight: 800;
|
||
color: @onInk;
|
||
background: @ink;
|
||
box-shadow: 0 10px 22px -12px alpha(@ink, 70%);
|
||
transition: transform 0.15s ease, opacity 0.2s ease;
|
||
|
||
&:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
&.is-loading,
|
||
&:disabled {
|
||
opacity: 0.75;
|
||
cursor: default;
|
||
}
|
||
|
||
:deep(svg) {
|
||
transition: transform 0.2s ease;
|
||
}
|
||
|
||
&:active :deep(svg) {
|
||
transform: translateX(-3px);
|
||
}
|
||
}
|
||
|
||
.reg__spinner {
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
border: 2px solid fade(#fff, 35%);
|
||
border-top-color: #fff;
|
||
animation: reg-spin 0.7s linear infinite;
|
||
}
|
||
|
||
.reg__ghost {
|
||
width: 100%;
|
||
min-height: 50px;
|
||
border: 1.5px solid @hairline;
|
||
border-radius: 16px;
|
||
background: @canvas;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
font-size: 13.5px;
|
||
font-weight: 700;
|
||
color: @ink;
|
||
}
|
||
|
||
.reg__switch {
|
||
margin: 10px 0 0;
|
||
text-align: center;
|
||
font-size: 12px;
|
||
color: @inkMuted;
|
||
|
||
button {
|
||
border: 0;
|
||
background: none;
|
||
cursor: pointer;
|
||
padding: 0 3px;
|
||
font-family: inherit;
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
color: @wine;
|
||
}
|
||
}
|
||
|
||
/* ---------- Success ---------- */
|
||
|
||
.reg__done {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
padding-top: 120px;
|
||
padding-bottom: 40px;
|
||
}
|
||
|
||
.reg__doneMark {
|
||
position: relative;
|
||
display: grid;
|
||
place-items: center;
|
||
width: 88px;
|
||
height: 88px;
|
||
border-radius: 26px;
|
||
color: @green;
|
||
background: alpha(@green, 10%);
|
||
animation: reg-pop 0.6s cubic-bezier(0.2, 1.5, 0.4, 1) both;
|
||
|
||
:deep(svg) {
|
||
animation: reg-pop 0.6s 0.15s cubic-bezier(0.2, 1.6, 0.4, 1) both;
|
||
}
|
||
}
|
||
|
||
.reg__doneRing {
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
border: 2px solid @green;
|
||
opacity: 0;
|
||
animation: reg-ring 1.1s 0.3s ease-out both;
|
||
}
|
||
|
||
.reg__doneTitle {
|
||
margin: 22px 0 0;
|
||
font-size: 19px;
|
||
font-weight: 800;
|
||
color: @ink;
|
||
}
|
||
|
||
.reg__doneText {
|
||
max-width: 320px;
|
||
margin: 10px 0 0;
|
||
font-size: 13px;
|
||
line-height: 2;
|
||
color: @inkMuted;
|
||
}
|
||
|
||
.reg__ltr {
|
||
direction: ltr;
|
||
unicode-bidi: isolate;
|
||
font-weight: 700;
|
||
color: @ink;
|
||
}
|
||
|
||
.reg__doneActions {
|
||
display: grid;
|
||
gap: 10px;
|
||
width: 100%;
|
||
max-width: 360px;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
/* ---------- Motion ---------- */
|
||
|
||
.reg-next-enter-active,
|
||
.reg-next-leave-active,
|
||
.reg-prev-enter-active,
|
||
.reg-prev-leave-active {
|
||
transition: transform 0.28s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.22s ease;
|
||
}
|
||
|
||
// RTL: moving forward slides content toward the left.
|
||
.reg-next-enter-from,
|
||
.reg-prev-leave-to {
|
||
transform: translateX(-24px);
|
||
opacity: 0;
|
||
}
|
||
|
||
.reg-next-leave-to,
|
||
.reg-prev-enter-from {
|
||
transform: translateX(24px);
|
||
opacity: 0;
|
||
}
|
||
|
||
.reg-fade-enter-active,
|
||
.reg-fade-leave-active {
|
||
transition: opacity 0.25s ease;
|
||
}
|
||
|
||
.reg-fade-enter-from,
|
||
.reg-fade-leave-to {
|
||
opacity: 0;
|
||
}
|
||
|
||
@keyframes reg-spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
@keyframes reg-pop {
|
||
0% {
|
||
transform: scale(0.4);
|
||
opacity: 0;
|
||
}
|
||
100% {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes reg-ring {
|
||
0% {
|
||
transform: scale(1);
|
||
opacity: 0.6;
|
||
}
|
||
100% {
|
||
transform: scale(1.55);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.reg-next-enter-active,
|
||
.reg-next-leave-active,
|
||
.reg-prev-enter-active,
|
||
.reg-prev-leave-active {
|
||
transition: opacity 0.2s ease;
|
||
}
|
||
.reg-next-enter-from,
|
||
.reg-next-leave-to,
|
||
.reg-prev-enter-from,
|
||
.reg-prev-leave-to {
|
||
transform: none;
|
||
}
|
||
.reg__doneMark,
|
||
.reg__doneMark :deep(svg),
|
||
.reg__doneRing {
|
||
animation: none;
|
||
}
|
||
}
|
||
</style>
|