This commit is contained in:
Parsa Nazer
2025-09-21 10:34:43 +03:30
59 changed files with 509 additions and 511 deletions
-73
View File
@@ -1,73 +0,0 @@
@layer base {
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-UltraLight.woff2");
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-Light.woff2");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-Regular.woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-Medium.woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-SemiBold.woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-Bold.woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-ExtraBold.woff2");
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-Black.woff2");
font-weight: 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/Morabba-Heavy.woff2");
font-weight: 1000;
font-style: normal;
font-display: swap;
}
}
-41
View File
@@ -1,41 +0,0 @@
@layer base {
@font-face {
font-family: "YekanBakh";
src: url("/fonts/YekanBakh/YekanBakh-Thin.woff");
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "YekanBakh";
src: url("/fonts/YekanBakh/YekanBakh-Light.woff");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "YekanBakh";
src: url("/fonts/YekanBakh/YekanBakh-Regular.woff");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "YekanBakh";
src: url("/fonts/YekanBakh/YekanBakh-SemiBold.woff");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "YekanBakh";
src: url("/fonts/YekanBakh/YekanBakh-Bold.woff");
font-weight: 700;
font-style: normal;
font-display: swap;
}
}
-4
View File
@@ -6,8 +6,6 @@
@import "./button.comp.css";
@import "./input.comp.css";
@import "./fonts/iran-yekan-x.css";
@import "./fonts/morabba.css";
@import "./fonts/yekan-bakh.css";
:root {
/* CONTAINER */
@@ -117,8 +115,6 @@
--font-inter: "Inter", sans-serif;
--font-dmsans: "DM Sans", sans-serif;
--font-iran-yekan-x: "IRANYekanXVF", "sans-serif";
--font-yekan-bakh: "YekanBakh", "sans-serif";
--font-morabba: "Morabba", "sans-serif";
/* BREAKPOINTS */
--breakpoint-2xs: 400px;
+12 -7
View File
@@ -22,15 +22,16 @@ const emit = defineEmits<Emits>();
<template>
<button
@click="emit('select', null)"
:class="
isSelected
? 'ring-2 ring-offset-2 ring-blue-500 border-blue-500'
: 'border-slate-200'
"
:class="isSelected ? 'ring-2 ring-offset-2 ring-blue-500 border-blue-500' : 'border-slate-200'"
class="w-full p-5 border rounded-xl flex flex-col gap-4 transition-all cursor-pointer relative overflow-hidden"
>
<div class="aspect-square flex-center">
<NuxtImg :src="gateway.picture" class="object-cover" />
<NuxtImg
:src="gateway.picture"
loading="lazy"
fetch-priority="low"
class="object-cover"
/>
</div>
<span class="typo-label-sm text-right text-black">
{{ gateway.title }}
@@ -43,7 +44,11 @@ const emit = defineEmits<Emits>();
v-if="isSelected"
class="bg-blue-500 rounded-md p-0.5 text-center bottom-4 left-4 text-slate-200 text-[10px] lg:text-xs absolute"
>
<Icon name="bi:check" size="20" class="**:fill-white" />
<Icon
name="bi:check"
size="20"
class="**:fill-white"
/>
</span>
</Transition>
</button>
@@ -30,15 +30,11 @@ const visible = computed({
class="fixed inset-0 w-full h-svh z-9999 flex-center"
v-if="visible"
>
<div
class="overflow-y-auto max-h-svh absolute left-[50%] py-10 w-fit max-w-[50rem] translate-x-[-50%]"
>
<div class="overflow-y-auto max-h-svh absolute left-[50%] py-10 w-fit max-w-[50rem] translate-x-[-50%]">
<DialogContent
class="min-w-[30vw] max-w-[50vw] data-[state=open]:animate-content-show text-black font-iran-yekan-x focus:outline-none z-[100]"
>
<div
class="w-full h-[350px] shrink-0 rounded-2xl relative overflow-hidden flex-center"
>
<div class="w-full h-[350px] shrink-0 rounded-2xl relative overflow-hidden flex-center">
<div
class="bg-custom-conic size-[200%] absolute -top-1/2 -left-1/2 animate-spin [animation-duration:3s] z-[1]"
></div>
@@ -48,6 +44,8 @@ const visible = computed({
<NuxtImg
class="aspect-square w-[300px]"
src="/img/heymlz/heymlz-payment-progress.gif"
loading="lazy"
fetch-priority="low"
/>
<div class="-translate-y-28 flex-center gap-3">
@@ -55,9 +53,7 @@ const visible = computed({
name="svg-spinners:3-dots-bounce"
size="20"
/>
<span class="text-lg">
در حال انتقال به درگاه پرداخت
</span>
<span class="text-lg"> در حال انتقال به درگاه پرداخت </span>
</div>
</div>
</div>
@@ -72,15 +68,7 @@ const visible = computed({
.bg-custom-conic {
background-size: 100% 100%;
background-position: 0px 0px, 0px 0px;
background-image: radial-gradient(
142% 91% at -6% 90%,
#ff000000 1%,
#ff000000 99%
),
conic-gradient(
from 0deg at 50% 50%,
var(--color-blue-500) 0%,
#ff000000 34%
);
background-image: radial-gradient(142% 91% at -6% 90%, #ff000000 1%, #ff000000 99%),
conic-gradient(from 0deg at 50% 50%, var(--color-blue-500) 0%, #ff000000 34%);
}
</style>
@@ -27,15 +27,19 @@ const { isLoading: cartImageIsLoading } = useImage({
v-if="!cartImageIsLoading"
class="size-[3.5rem] shrink-0 rounded-100 border border-gray-300 overflow-hidden"
>
<NuxtImg :src="image" alt="product" class="object-conver" />
<NuxtImg
:src="image"
alt="product"
loading="lazy"
fetch-priority="low"
class="object-conver"
/>
</div>
<Skeleton
v-else
class="!size-[3.5rem] aspect-square shrink-0 !rounded-100 border border-slate-200"
/>
<span
class="text-xs font-semibold lg:text-sm text-gray-800 line-clamp-2"
>
<span class="text-xs font-semibold lg:text-sm text-gray-800 line-clamp-2">
{{ title }}
</span>
</div>
@@ -123,6 +123,8 @@ watch(
>
<NuxtImg
:src="data.product.image"
loading="lazy"
fetch-priority="low"
class="object-cover size-full"
alt="product"
/>
@@ -38,6 +38,8 @@ const remaining = computed(() => items.value.length - max.value);
>
<NuxtImg
:src="item"
loading="lazy"
fetch-priority="low"
alt="avatar"
class="rounded-full object-cover w-full h-full"
/>
+4
View File
@@ -51,6 +51,8 @@ const createdAt = usePersianTimeAgo(new Date(date.value));
<NuxtImg
:src="image"
loading="lazy"
fetch-priority="low"
class="group-hover:scale-105 transition-transform duration-200 absolute size-full object-cover z-10"
alt=""
/>
@@ -98,6 +100,8 @@ const createdAt = usePersianTimeAgo(new Date(date.value));
<NuxtImg
v-if="variant === 'lg'"
loading="lazy"
fetch-priority="low"
:src="image"
class="group-hover:scale-105 transition-transform duration-200 absolute size-full object-cover z-10"
alt=""
+4 -20
View File
@@ -41,6 +41,8 @@ const brands = ref([
HEYMLZ
</div>
<NuxtImg
loading="lazy"
fetch-priority="low"
src="/img/heymlz/heymlz-logo.png"
class="h-[25px] sm:h-[45px] invert"
/>
@@ -62,6 +64,8 @@ const brands = ref([
class="flex items-center px-6 sm:px-10 h-[90px] sm:h-[140px]"
>
<NuxtImg
loading="lazy"
fetch-priority="low"
:src="brand"
class="h-[25px] sm:h-[45px] opacity-25"
/>
@@ -70,23 +74,3 @@ const brands = ref([
</div>
</div>
</template>
<!-- <NuxtImg
src="/img/brands/brand-2.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-3.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/> -->
@@ -44,6 +44,8 @@ const { colorObject } = useImageColor(`#category-image-${id.value}`);
:id="`category-image-${id}`"
class="group-hover:scale-105 transition-transform duration-200 absolute object-contain size-full"
:src="picture"
loading="lazy"
fetch-priority="low"
alt=""
/>
</Transition>
+4 -1
View File
@@ -1,7 +1,10 @@
<script setup lang="ts"></script>
<template>
<SideModal title="فیلتر محصولات">
<SideModal
modalId="product-filters"
title="فیلتر محصولات"
>
<template #button>
<Button
end-icon="ci:filter"
+4
View File
@@ -3,6 +3,8 @@
<NuxtImg
src="/img/footer-bg.jpg"
alt=""
loading="lazy"
fetch-priority="low"
class="absolute z-10 object-cover opacity-45"
:style="{
mask: 'linear-gradient(to bottom, black 0%, rgba(0,0,0,0) 100%',
@@ -13,6 +15,8 @@
<div class="flex items-center flex-col gap-8 pb-[10px] pt-[80px] lg:pt-[100px] lg:pb-[50px] justify-center">
<img
src="/img/heymlz/heymlz-small-idle.gif"
loading="lazy"
fetch-priority="low"
class="size-[150px] lg:size-[220px] rounded-full drop-shadow-2xl"
/>
<span class="font-bold text-2xl lg:text-5xl text-gradient bg-gradient-to-l from-blue-500 to-blue-700">
+11 -1
View File
@@ -122,8 +122,18 @@ const isHomePage = computed(() => route.path === "/");
</div>
<div class="header-navbar-item flex items-center justify-end h-full">
<NuxtImg
<!-- <NuxtImg
src="/img/heymlz/heymlz-logomotion.gif"
preload
loading="eager"
fetch-priority="high"
class="h-2/3"
/> -->
<NuxtImg
src="/img/static-logo.png"
preload
loading="eager"
fetch-priority="high"
class="h-2/3"
/>
</div>
@@ -35,24 +35,26 @@ const highlights = ref<Highlight[]>([
<template>
<section class="w-full border-t-[0.5px] border-slate-200">
<div
class="w-full py-[3rem] lg:py-[5rem] gap-8 sm:gap-12 xl:gap-0 container grid grid-cols-2 lg:grid-cols-4"
<div class="w-full py-[3rem] lg:py-[5rem] gap-8 sm:gap-12 xl:gap-0 container grid grid-cols-2 lg:grid-cols-4">
<template
v-for="(highlight, index) in highlights"
:key="index"
>
<template v-for="(highlight, index) in highlights" :key="index">
<div class="flex flex-col-center gap-[.75rem] px-5">
<div class="size-[70px] md:size-[100px] flex-center">
<NuxtImg :src="highlight.icon" class="w-full" />
<NuxtImg
:src="highlight.icon"
loading="lazy"
fetch-priority="low"
class="w-full"
/>
</div>
<div class="w-full flex-col-center gap-[.25rem]">
<span
class="typo-sub-h-sm lg:typo-sub-h-md text-black text-center"
>
<span class="typo-sub-h-sm lg:typo-sub-h-md text-black text-center">
{{ highlight.title }}
</span>
<p
class="text-slate-500 typo-p-xs lg:typo-p-sm mt-1 text-center"
>
<p class="text-slate-500 typo-p-xs lg:typo-p-sm mt-1 text-center">
{{ highlight.description }}
</p>
</div>
+4 -2
View File
@@ -3,13 +3,15 @@
type Props = {
title: string;
modalId: string;
};
defineProps<Props>();
const props = defineProps<Props>();
const { modalId } = toRefs(props);
// state
const isSideShow = ref(false);
const isSideShow = useState(`side-modal-${modalId.value}`, () => false);
const isLocked = useScrollLock(window);
@@ -90,6 +90,8 @@ const changeSlide = (id: number) => {
<NuxtImg
class="absolute object-cover size-full"
:src="slide.image"
loading="lazy"
fetch-priority="low"
:alt="String(slide.id)"
/>
</div>
@@ -67,6 +67,8 @@ const parallaxStyle = computed(() => {
<NuxtImg
:id="`product-image-${id}`"
:src="picture"
loading="lazy"
fetch-priority="low"
class="group-hover:scale-105 transition-transform duration-200 size-full object-contain absolute inset-0"
alt="product-background"
/>
+2
View File
@@ -42,6 +42,8 @@ const onSlideChange = (swiper: SwiperClass) => {
:style="{
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.4))',
}"
loading="lazy"
fetch-priority="low"
src="/img/heymlz/heymlz-category-seat.gif"
/>
<Swiper
+8 -2
View File
@@ -209,11 +209,15 @@ onUnmounted(() => {
webkit-playsinline
class="slide-video absolute inset-0 size-full object-cover brightness-90"
:src="slide.video"
poster="/img/test-thumbnail.jpeg"
/>
</template>
<NuxtImg
v-else
preload
loading="eager"
fetch-priority="high"
class="absolute inset-0 size-full object-cover"
:src="slide.image!"
:alt="slide.title"
@@ -259,13 +263,15 @@ onUnmounted(() => {
:to="slide.link"
class="relative max-sm:hidden"
>
<NuxtImg
<!-- <NuxtImg
class="aspect-square w-[110px] lg:w-[120px] translate-y-[-75px] lg:translate-y-[-82px] absolute left-1/2 -translate-x-1/2 z-10"
:style="{
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.4))',
}"
src="/img/heymlz/heymlz-seat.gif"
/>
loading="lazy"
fetch-priority="low"
/> -->
<Button
variant="primary"
+15 -14
View File
@@ -1,5 +1,4 @@
<script setup lang="ts">
// import
import { Swiper, SwiperSlide } from "swiper/vue";
@@ -7,7 +6,7 @@ import type { SwiperClass } from "swiper/react";
// types
type Props = {}
type Props = {};
// props
@@ -23,7 +22,6 @@ const swiper_instance = ref<SwiperClass | null>(null);
const onSwiper = (swiper: SwiperClass) => {
swiper_instance.value = swiper;
};
</script>
<template>
@@ -31,6 +29,8 @@ const onSwiper = (swiper: SwiperClass) => {
<NuxtImg
class="absolute size-full object-cover"
src="/img/hero-bg.jpg"
loading="lazy"
fetch-priority="low"
alt=""
/>
<div class="absolute bg-black/60 size-full z-10" />
@@ -50,15 +50,18 @@ const onSwiper = (swiper: SwiperClass) => {
>
<div class="flex justify-center items-center">
<div class="max-w-[900px] px-4 text-white flex flex-col items-center gap-4">
<Icon name="ci:instagram" size="28" class="**:stroke-white" />
<p class="text-base xs:text-lg sm:typo-h-6 lg:typo-h-5 !font-normal !leading-[150%] lg:leading-[175%] max-sm:px-4 sm:max-w-[600px] lg:max-w-[800px] text-center">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با
استفاده از طراحان گرافیک است. چاپگرها و متون بلکه روزنامه و مجله
در ستون و سطرآنچنان که لازم.
<Icon
name="ci:instagram"
size="28"
class="**:stroke-white"
/>
<p
class="text-base xs:text-lg sm:typo-h-6 lg:typo-h-5 !font-normal !leading-[150%] lg:leading-[175%] max-sm:px-4 sm:max-w-[600px] lg:max-w-[800px] text-center"
>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک
است. چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم.
</p>
<span class="typo-p-sm md:typo-p-xl text-center">
- منصور مرزبان
</span>
<span class="typo-p-sm md:typo-p-xl text-center"> - منصور مرزبان </span>
</div>
</div>
</SwiperSlide>
@@ -69,10 +72,8 @@ const onSwiper = (swiper: SwiperClass) => {
v-for="(i, index) in 6"
:class="swiper_instance?.realIndex === index ? 'bg-white' : 'bg-transparent'"
class="border border-white size-1.5 md:size-2 rounded-full transition-all duration-200"
>
></div>
</div>
</div>
</div>
</div>
</template>
+6 -6
View File
@@ -88,6 +88,8 @@ watch(
:src="homeData!.difreance_section.image1"
class="select-none absolute size-full object-cover transition-[filter] duration-250 brightness-[95%]"
:alt="homeData!.difreance_section.title1"
loading="lazy"
fetch-priority="low"
/>
<video
v-else
@@ -110,6 +112,8 @@ watch(
:src="homeData!.difreance_section.image2"
class="overlay-image select-none absolute object-cover size-full transition-[filter] duration-250 brightness-[95%]"
:alt="homeData!.difreance_section.title2"
loading="lazy"
fetch-priority="low"
/>
<video
v-else
@@ -126,9 +130,7 @@ watch(
:style="{
left: `${clipPathPercent}%`,
}"
:class="[
activeSlideVideo !== 'none' ? 'opacity-10' : '',
]"
:class="[activeSlideVideo !== 'none' ? 'opacity-10' : '']"
class="select-none w-[5px] sm:w-2 bg-black h-full absolute left-0 flex items-center justify-center transition-opacity duration-250"
>
<div
@@ -143,9 +145,7 @@ watch(
</div>
</div>
<div
class="absolute bottom-0 p-6 md:p-10 w-full flex justify-between items-end transition-opacity"
>
<div class="absolute bottom-0 p-6 md:p-10 w-full flex justify-between items-end transition-opacity">
<div
class="flex flex-col gap-2 text-black transition-opacity"
:class="activeSlideVideo === 'right' ? 'opacity-0' : ''"
+208
View File
@@ -0,0 +1,208 @@
<script setup lang="ts">
// import
import useHomeData from "~/composables/api/home/useHomeData";
// state
const { data: homeData } = useHomeData();
const clipPathPercent = ref(49);
const activeSlideVideo = ref<"left" | "right" | "none">("none");
const draggableEl = ref<HTMLElement | null>(null);
const previewContainerEl = ref<HTMLElement | null>(null);
const heymlzElement = useTemplateRef<HTMLDivElement>("heymlzElement");
const heymlzElementIsVisible = useElementVisibility(heymlzElement, {
rootMargin: "0px 0px -40% 0px",
});
const showHeymlzAnimation = ref(false);
const { x: dragAxisX } = useDraggable(draggableEl, {
initialValue: { x: 0, y: 0 },
axis: "x",
});
// watch
watch(
heymlzElementIsVisible,
(newValue) => {
if (newValue) {
setTimeout(() => {
showHeymlzAnimation.value = true;
setTimeout(() => {
showHeymlzAnimation.value = false;
}, 3200);
}, 400);
}
},
{
once: true,
}
);
watch(
() => clipPathPercent.value,
(newValue) => {
if (newValue > 80) {
activeSlideVideo.value = "right";
} else if (newValue < 20) {
activeSlideVideo.value = "left";
} else {
activeSlideVideo.value = "none";
}
}
);
watch(
() => dragAxisX.value,
(newValue) => {
const clientRect = previewContainerEl.value?.getBoundingClientRect()!;
const percent = clientRect.width / 100;
const clipPercent = (newValue + draggableEl.value!.clientWidth / 2 - clientRect.x - 8) / percent;
if (clipPercent >= 1 && clipPercent <= 99) {
clipPathPercent.value = clipPercent;
}
}
);
</script>
<template>
<div class="container mb-40 lg:mb-40 max-lg:mt-20 lg:-mt-32">
<div>
<div class="flex flex-col items-center gap-3 mb-10 lg:mb-16">
<span class="typo-p-sm md:typo-p-md text-slate-500"> مقایسه محصولات </span>
<span class="typo-h-6 md:typo-h-5 lg:typo-h-3 text-black"> تفاوت محصلات ما را ببینید </span>
</div>
<div
ref="previewContainerEl"
class="rounded-200 overflow-hidden h-[70svh] relative"
>
<Transition name="fade">
<NuxtImg
v-if="activeSlideVideo !== 'right'"
:src="homeData!.difreance_section.image1"
:class="showHeymlzAnimation ? 'brightness-25 blur-sm' : 'brightness-[95%] blur-[0px]'"
class="select-none absolute size-full object-cover transition-[filter] duration-250"
:alt="homeData!.difreance_section.title1"
/>
<video
v-else
autoplay
muted
playsinline
webkit-playsinline
src="/video/vid-3.mp4"
class="select-none absolute size-full object-cover brightness-[95%]"
/>
</Transition>
<div
class="absolute size-full right-0 w-full"
ref="heymlzElement"
>
<Transition name="fade">
<NuxtImg
v-if="activeSlideVideo !== 'left'"
:src="homeData!.difreance_section.image2"
:class="showHeymlzAnimation ? 'brightness-25 blur-sm' : 'brightness-[95%] blur-[0px]'"
class="overlay-image select-none absolute object-cover size-full transition-[filter] duration-250"
:alt="homeData!.difreance_section.title2"
/>
<video
v-else
autoplay
muted
playsinline
webkit-playsinline
src="/video/vid-3.mp4"
class="overlay-image select-none absolute object-cover size-full brightness-[95%]"
/>
</Transition>
<Transition
name="fade"
:duration="250"
>
<NuxtImg
v-if="showHeymlzAnimation"
src="/img/heymlz/heymlz-pullingg.gif"
class="size-[250px] sm:size-[400px] absolute translate-x-[-62px] sm:translate-x-[-107px] z-10 top-[50%] -translate-y-1/2"
:style="{
left: `${clipPathPercent}%`,
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.3))',
}"
/>
</Transition>
<div
:style="{
left: `${clipPathPercent}%`,
}"
:class="[
activeSlideVideo !== 'none' ? 'opacity-10' : '',
showHeymlzAnimation ? 'bg-neutral-200' : 'bg-black',
]"
class="select-none w-[5px] sm:w-2 h-full absolute left-0 flex items-center justify-center transition-opacity duration-250"
>
<div
ref="draggableEl"
:class="showHeymlzAnimation ? 'bg-neutral-200' : 'bg-black'"
class="touch-none cursor-grab hover:scale-115 transition-transform rounded-full absolute size-9 sm:size-11 flex items-center justify-center"
>
<Icon
name="ci:arrows"
class="transition-all size-5 sm:size-6"
:class="showHeymlzAnimation ? '**:stroke-black' : '**:stroke-white'"
/>
</div>
</div>
</div>
<div
:class="showHeymlzAnimation ? 'opacity-0' : 'opacity-100'"
class="absolute bottom-0 p-6 md:p-10 w-full flex justify-between items-end transition-opacity"
>
<div
class="flex flex-col gap-2 text-black transition-opacity"
:class="activeSlideVideo === 'right' ? 'opacity-0' : ''"
>
<span class="typo-p-sm md:typo-p-md">
{{ homeData!.difreance_section.description1 }}
</span>
<NuxtLink
:to="homeData!.difreance_section.link1"
class="typo-h-6 md:typo-h-5 lg:typo-h-3"
>
{{ homeData!.difreance_section.title1 }}
</NuxtLink>
</div>
<div
class="flex flex-col gap-2 text-black transition-opacity"
:class="activeSlideVideo === 'left' ? 'opacity-0' : ''"
>
<span class="typo-p-sm md:typo-p-md text-end">
{{ homeData!.difreance_section.description2 }}
</span>
<NuxtLink
:to="homeData!.difreance_section.link2"
class="typo-h-6 md:typo-h-5 lg:typo-h-3 text-end"
>
{{ homeData!.difreance_section.title2 }}
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
</template>
<style>
.overlay-image {
clip-path: polygon(v-bind('clipPathPercent + "%"') 0, 100% 0, 100% 100%, v-bind('clipPathPercent + "%"') 100%);
}
</style>
+12 -2
View File
@@ -106,6 +106,8 @@ const childImageVariants = {
<NuxtImg
:src="slide.background_image"
class="absolute size-full object-cover"
loading="lazy"
fetch-priority="low"
/>
</motion.div>
</template>
@@ -133,6 +135,8 @@ const childImageVariants = {
<NuxtImg
class="w-[130px] sm:w-[180px] lg:w-[250px] xl:w-[300px] z-20 mt-40"
:src="slide.image3"
loading="lazy"
fetch-priority="low"
alt=""
/>
</motion.div>
@@ -144,6 +148,8 @@ const childImageVariants = {
<NuxtImg
class="w-[130px] sm:w-[180px] lg:w-[250px] xl:w-[300px] z-20"
:src="slide.image2"
loading="lazy"
fetch-priority="low"
alt=""
/>
</motion.div>
@@ -155,6 +161,8 @@ const childImageVariants = {
<NuxtImg
class="w-[130px] sm:w-[180px] lg:w-[250px] xl:w-[300px] z-20 mt-40"
:src="slide.image1"
loading="lazy"
fetch-priority="low"
alt=""
/>
</motion.div>
@@ -211,17 +219,19 @@ const childImageVariants = {
<span class="text-white typo-h-6 sm:typo-h-5 lg:typo-h-4 xl:typo-h-3">
{{ slide.title }}
</span>
<p
<!-- <p
class="text-white max-w-[320px] xs:max-w-[360px] sm:max-w-[480px] lg:max-w-[550px] xl:max-w-[750px] typo-p-sm lg:typo-p-md xl:typo-p-lg"
>
{{ slide.description }}
</p>
</p> -->
<NuxtLink
:to="`/resellers/category/${slide.id}`"
class="relative"
>
<NuxtImg
src="/img/heymlz/heymlz-falling.gif"
loading="lazy"
fetch-priority="low"
class="absolute top-[101px] sm:top-[100px] lg:top-[117px] left-1/2 -translate-1/2 w-[200px] lg:w-[250px] drop-shadow-md"
/>
<Button
@@ -187,6 +187,8 @@ whenever(
<NuxtImg
class="size-[225px] sm:size-[250px] drop-shadow-2xl"
src="/img/heymlz/heymlz-small-idle.gif"
loading="lazy"
fetch-priority="low"
alt=""
/>
</div>
@@ -199,6 +201,8 @@ whenever(
<NuxtImg
class="size-[225px] sm:size-[250px] drop-shadow-2xl"
src="/img/heymlz/heymlz-small-idle.gif"
loading="lazy"
fetch-priority="low"
alt=""
/>
<div class="flex flex-col gap-4 items-center">
@@ -1,5 +1,5 @@
<script setup lang="ts">
import useGetAccount from '~/composables/api/account/useGetAccount';
import useGetAccount from "~/composables/api/account/useGetAccount";
// types
@@ -85,31 +85,34 @@ onMounted(() => {
src="/img/heymlz/heymlz-full-body.jpg"
class="size-full object-cover absolute"
alt="profile"
loading="lazy"
fetch-priority="low"
/>
<NuxtImg
v-else
:src="account?.profile_photo ?? ''"
class="size-full object-cover absolute"
loading="lazy"
fetch-priority="low"
alt="profile"
/>
</div>
<div
class="rounded-150 px-4 py-3 whitespace-pre-wrap overflow-hidden"
:class="
reverse
? 'bg-slate-100 text-slate-600'
: 'bg-black text-white'
"
:class="reverse ? 'bg-slate-100 text-slate-600' : 'bg-black text-white'"
>
<div
v-if="!loadingContent"
:id="`chat-message-content-${id}`"
class="typo-p-sm font-normal whitespace-pre-wrap"
v-html="content"
>
</div>
></div>
<Icon v-else name="svg-spinners:3-dots-bounce" size="20" />
<Icon
v-else
name="svg-spinners:3-dots-bounce"
size="20"
/>
</div>
</div>
</div>
@@ -127,6 +127,8 @@ const limitedComments = computed(() => {
>
<NuxtImg
src="/img/heymlz/heymlz-contact-us.gif"
loading="lazy"
fetch-priority="low"
class="w-[200px] lg:w-[300px] translate-y-[-25px]"
/>
<span class="text-xl text-black font-semibold translate-y-[-25px]"> هیچ نظری ثبت نشده است </span>
+5 -11
View File
@@ -1,5 +1,4 @@
<script lang="ts" setup>
// import
import type { ProductVariantProvideType } from "~/pages/product/[id].vue";
@@ -7,7 +6,6 @@ import type { ProductVariantProvideType } from "~/pages/product/[id].vue";
// provide / inject
const { selectedVariant } = inject("productVariant") as ProductVariantProvideType;
</script>
<template>
@@ -20,25 +18,21 @@ const { selectedVariant } = inject("productVariant") as ProductVariantProvideTyp
<Accordion />
</div>
<div class="w-full lg:w-[450px] xl:w-[600px]">
<div
class="w-full bg-slate-50 rounded-xl flex-col-center px-5 py-16 sm:p-[5rem] gap-[1.5rem]"
>
<div class="w-full bg-slate-50 rounded-xl flex-col-center px-5 py-16 sm:p-[5rem] gap-[1.5rem]">
<span class="typo-h-6 mb-8">داخل جعبه چیه؟</span>
<div
class="w-full grid grid-cols-2 gap-y-[1.5rem] sm:gap-x-[3rem]"
>
<div class="w-full grid grid-cols-2 gap-y-[1.5rem] sm:gap-x-[3rem]">
<div
v-for="inPackItem in selectedVariant!.in_pack_items"
class="w-full flex-col-center gap-[.75rem]"
>
<div
class="size-[6.25rem] rounded-full border-slate-200 bg-white flex-center"
>
<div class="size-[6.25rem] rounded-full border-slate-200 bg-white flex-center">
<div class="size-11 relative">
<NuxtImg
class="size-full absolute object-cover"
:src="inPackItem.cover"
:alt="inPackItem.item_title"
loading="lazy"
fetch-priority="low"
/>
</div>
</div>
@@ -253,6 +253,26 @@ watch(
<UpdateQuantity v-else-if="selectedVariant!.in_stock > 0" />
</div>
<div
class="flex items-center gap-4"
v-if="selectedVariant!.cart_quantity === 0"
>
<Button
variant="secondary"
class="w-full rounded-full h-full max-sm:h-[48px]"
end-icon="bi:percent"
>
۱۰۰ تومان تخفیف بگیر
</Button>
<Button
variant="secondary"
class="w-full rounded-full h-full max-sm:h-[48px]"
end-icon="bi:person-check"
>
پورسانت بگیر
</Button>
</div>
<InfoCard />
<Share />
@@ -12,6 +12,8 @@ const router = useRouter();
const params = inject("params") as GetProductsFilters;
const isSideShow = useState("side-modal-product-filters");
const currentCategory = computed({
get: () => {
return Array.isArray(route.params.slug) ? route.params.slug[1] ?? undefined : undefined;
@@ -238,6 +240,26 @@ watch(
/>
</div>
</Transition>
<div class="flex items-center gap-4 w-full">
<Button
:disabled="productsIsPending"
variant="primary"
@click="() => (isSideShow = false)"
class="w-full rounded-full py-4 !cursor-pointer disabled:pointer-events-none z-[3]"
>
<Transition
name="fade"
mode="out-in"
>
<span class="flex-center gap-3">
ثبت فیلتر جدید
<Icon
name="ci:plus"
size="20"
/>
</span>
</Transition>
</Button>
<Button
:disabled="productsIsPending"
variant="solid"
@@ -272,4 +294,5 @@ watch(
</Button>
</div>
</div>
</div>
</template>
@@ -38,9 +38,7 @@ const profile = computed(() => {
});
const username = computed(() => {
return is_user.value
? `${account.value?.first_name} ${account.value?.last_name}`
: "ادمین پشتیبانی هی ملز";
return is_user.value ? `${account.value?.first_name} ${account.value?.last_name}` : "ادمین پشتیبانی هی ملز";
});
</script>
@@ -54,7 +52,12 @@ const username = computed(() => {
:class="is_user ? 'rounded-br-none' : 'rounded-bl-none'"
>
<div class="w-2/12 flex items-start justify-start">
<NuxtImg :src="profile" class="size-16 rounded-full" />
<NuxtImg
:src="profile"
loading="lazy"
fetch-priority="low"
class="size-16 rounded-full"
/>
</div>
<div class="w-10/12 flex flex-col items-start pt-2">
@@ -65,9 +68,7 @@ const username = computed(() => {
>
{{ timeAgo }}
</p>
<p
class="text-xs font-semibold text-dynamic-secondary line-clamp-1"
>
<p class="text-xs font-semibold text-dynamic-secondary line-clamp-1">
{{ username }}
</p>
</div>
+3 -1
View File
@@ -76,7 +76,9 @@ const useSlider = ({ duration = 0, count }: Props) => {
restartSliderTimer();
});
onUnmounted(() => {});
onUnmounted(() => {
restartSliderTimer();
});
return {
activeSlide,
+1 -1
View File
@@ -56,7 +56,7 @@ const hasCartItem = computed(() => !!cart.value && cart.value.items.length! > 0)
<div class="hidden w-3/12 shrink-0 lg:block">&nbsp;</div>
</div>
<div
class="w-full flex flex-col items-center relative justify-between gap-4 lg:gap-6 lg:flex-row lg:items-start"
class="w-full flex flex-col items-center relative justify-between gap-8 lg:gap-6 lg:flex-row lg:items-start"
>
<div
class="flex flex-col w-full gap-4 lg:gap-6 shrink-0"
+11 -11
View File
@@ -61,17 +61,17 @@ export default defineNuxtConfig({
},
modules: [
[
"@nuxtjs/google-fonts",
{
families: {
"DM Sans": "100..900",
Inter: "100..900",
download: true,
inject: false,
},
},
],
// [
// "@nuxtjs/google-fonts",
// {
// families: {
// "DM Sans": "100..900",
// Inter: "100..900",
// download: true,
// inject: false,
// },
// },
// ],
"@nuxt/icon",
"reka-ui/nuxt",
"@vueuse/nuxt",
-26
View File
@@ -25,7 +25,6 @@
"date-fns-jalali": "^4.1.0-0",
"fast-average-color": "^9.4.0",
"gsap": "^3.12.7",
"highlight.js": "^11.11.1",
"jalali-ts": "^8.0.0",
"motion-v": "^1.1.1",
"nuxt": "^3.15.4",
@@ -36,7 +35,6 @@
"vue": "latest",
"vue-image-zoomer": "^2.4.4",
"vue-router": "latest",
"vue-scrollto": "^2.20.0",
"vue-skeletor": "^1.0.6",
"vue3-marquee": "^4.2.2",
"vue3-persian-datetime-picker": "^1.2.2",
@@ -6555,12 +6553,6 @@
],
"license": "MIT"
},
"node_modules/bezier-easing": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
"integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==",
"license": "MIT"
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@@ -9240,15 +9232,6 @@
"integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==",
"license": "MIT"
},
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
@@ -16810,15 +16793,6 @@
"vue": "^3.2.0"
}
},
"node_modules/vue-scrollto": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/vue-scrollto/-/vue-scrollto-2.20.0.tgz",
"integrity": "sha512-7i+AGKJTThZnMEkhIPgrZjyAX+fXV7/rGdg+CV283uZZwCxwiMXaBLTmIc5RTA4uwGnT+E6eJle3mXQfM2OD3A==",
"license": "MIT",
"dependencies": {
"bezier-easing": "2.1.0"
}
},
"node_modules/vue-skeletor": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/vue-skeletor/-/vue-skeletor-1.0.6.tgz",
-2
View File
@@ -33,7 +33,6 @@
"date-fns-jalali": "^4.1.0-0",
"fast-average-color": "^9.4.0",
"gsap": "^3.12.7",
"highlight.js": "^11.11.1",
"jalali-ts": "^8.0.0",
"motion-v": "^1.1.1",
"nuxt": "^3.15.4",
@@ -44,7 +43,6 @@
"vue": "latest",
"vue-image-zoomer": "^2.4.4",
"vue-router": "latest",
"vue-scrollto": "^2.20.0",
"vue-skeletor": "^1.0.6",
"vue3-marquee": "^4.2.2",
"vue3-persian-datetime-picker": "^1.2.2",
+5
View File
@@ -38,6 +38,9 @@ if (response.isError) {
class="absolute object-cover size-full"
:alt="article!.title"
:src="article!.cover_image"
preload
loading="eager"
fetch-priority="high"
/>
<div class="absolute bg-linear-to-t from-black/75 to-transparent size-full" />
<div class="absolute pl-10 right-10 bottom-10 flex flex-col gap-6">
@@ -62,6 +65,8 @@ if (response.isError) {
class="size-full object-cover absolute"
:src="article!.author.profile_photo"
alt="article-author"
loading="lazy"
fetch-priority="low"
/>
</div>
<span class="typo-label-sm">
+2
View File
@@ -61,6 +61,8 @@ const deliveryData = ref<DeliveryData>({
<div class="flex items-center gap-3 py-3">
<NuxtImg
src="/img/location.gif"
loading="lazy"
fetch-priority="low"
class="size-12 pb-1 -mr-3"
/>
<span class="typo-sub-h-xl -mr-3"> آدرس های شما </span>
+41 -16
View File
@@ -6,7 +6,7 @@ import useGetCartOrders from "~/composables/api/orders/useGetCartOrders";
// meta
useSeoMeta({
title : "سبد خرید"
title: "سبد خرید",
});
definePageMeta({
@@ -22,9 +22,7 @@ const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
// computed
const hasCartItem = computed(
() => !!cart.value && cart.value.items.length! > 0
);
const hasCartItem = computed(() => !!cart.value && cart.value.items.length! > 0);
</script>
<template>
@@ -38,11 +36,17 @@ const hasCartItem = computed(
class="!w-36 !h-[43px] !rounded-lg"
/>
<div v-else class="flex items-center w-full gap-2 lg:w-1/2">
<NuxtImg src="/img/box.gif" class="size-12 pb-2" />
<p class="font-semibold lg:text-lg text-black">
{{ cart?.items.length }} مرسوله
</p>
<div
v-else
class="flex items-center w-full gap-2 lg:w-1/2"
>
<NuxtImg
src="/img/box.gif"
loading="lazy"
fetch-priority="low"
class="size-12 pb-2"
/>
<p class="font-semibold lg:text-lg text-black">{{ cart?.items.length }} مرسوله</p>
</div>
<Skeleton
@@ -53,7 +57,10 @@ const hasCartItem = computed(
<DeleteCartAllModal v-else />
</div>
<ul v-if="cartIsLoading" class="w-full flex flex-col gap-4 lg:gap-6">
<ul
v-if="cartIsLoading"
class="w-full flex flex-col gap-4 lg:gap-6"
>
<Skeleton
v-for="i in 4"
:key="i"
@@ -61,20 +68,38 @@ const hasCartItem = computed(
/>
</ul>
<div v-else class="w-full h-max">
<div v-if="!cart?.items.length" class="flex flex-grow w-full">
<Placeholder title="سبد خرید شما خالی است :(" icon="bi:cart">
<div
v-else
class="w-full h-max"
>
<div
v-if="!cart?.items.length"
class="flex flex-grow w-full"
>
<Placeholder
title="سبد خرید شما خالی است :("
icon="bi:cart"
>
<template #actions>
<NuxtLink :to="{ name: 'products' }">
<Button class="rounded-full" size="md">
<Button
class="rounded-full"
size="md"
>
<span> مشاهده محصولات </span>
<Icon name="ci:left-rotation" size="24" />
<Icon
name="ci:left-rotation"
size="24"
/>
</Button>
</NuxtLink>
</template>
</Placeholder>
</div>
<ul v-else class="w-full flex flex-col gap-4 lg:gap-6">
<ul
v-else
class="w-full flex flex-col gap-4 lg:gap-6"
>
<CartItem
v-for="(item, index) in cart?.items"
:key="index"
+1 -1
View File
@@ -22,7 +22,7 @@ if (response.isError) {
<template>
<div class="w-full">
<LoadingOverlay />
<!-- <LoadingOverlay /> -->
<Hero class="mb-20 max-md:mt-[80px]" />
<Preview />
<ProductsShowcase class="lg:mb-12" />
-173
View File
@@ -1,173 +0,0 @@
<script lang="ts" setup>
// import
import hljs from "highlight.js";
import json from "highlight.js/lib/languages/json";
import xml from "highlight.js/lib/languages/xml";
import "highlight.js/styles/atom-one-dark.css";
import LogDate from "~/components/server-logs/LogDate.vue";
import { useQuery } from "@tanstack/vue-query";
// meta
definePageMeta({
middleware: "check-is-debug",
layout: "none"
});
// state
const { $axios: axios } = useNuxtApp();
const { data: serverLogs, isFetching, suspense } = useQuery({
queryKey: ["server-logs"],
queryFn: async () => {
const response = await axios.get<AxiosLogType[]>("http://localhost:3000/api/server-logger");
return response.data.reverse();
},
refetchInterval: 5000,
staleTime: 0
});
await suspense();
// computed
const logIcon = (status: number) => {
if (status >= 200 && status < 300) return "bi:check-circle-fill";
return "bi:x-circle-fill";
};
// lifecycle
onMounted(() => {
hljs.registerLanguage("json", json);
hljs.registerLanguage("xml", xml);
hljs.highlightAll();
});
</script>
<template>
<div class="bg-neutral-900 w-full min-h-svh py-32">
<div class="fixed top-10 right-1/2 translate-x-1/2 flex-center" v-if="isFetching">
<Icon
name="svg-spinners:180-ring-with-bg"
class="size-12 mb-1 **:fill-neutral-500"
/>
</div>
<div class="w-full container flex flex-col gap-8">
<div
v-for="(log,index) in serverLogs"
:key="index"
class="border-2 p-5 rounded-xl log-item-animation"
:class="{
'bg-success-950/30 border-success-800' : log.status >= 200 && log.status < 300,
'bg-danger-950/30 border-danger-800' : log.status >= 400 && log.status < 600,
}"
>
<div class="flex items-center gap-4 mt-4">
<Icon
:name="logIcon(log.status)"
class="size-8 mb-1"
:class="{
'**:fill-success-500' : log.status >= 200 && log.status < 300,
'**:fill-danger-500' : log.status >= 400 && log.status < 600,
}"
/>
<h3 class="text-white font-medium text-3xl">
{{ log.url }}
</h3>
</div>
<div class="flex items-center gap-2 py-8">
<div
class="px-3 pb-1 pt-1.5 rounded-lg uppercase font-bold text-white"
:class="{
'bg-success-500' : log.status >= 200 && log.status < 300,
'bg-danger-500' : log.status >= 400 && log.status < 600,
}"
>
{{ log.method }}
</div>
<div
class="px-3 pb-1 pt-1.5 rounded-lg font-bold text-white"
:class="{
'bg-success-500' : log.status >= 200 && log.status < 300,
'bg-danger-500' : log.status >= 400 && log.status < 600,
}"
>
{{ log.status }}
</div>
<LogDate :date="log.date" />
</div>
<details class="text-white">
<summary class="cursor-pointer select-none">Details :</summary>
<div class="flex flex-col gap-2 mt-2 ml-4">
<details
v-if="log.response && typeof log.response === 'string' && (log.response.startsWith('<!DOCTYPE html>') || log.response.startsWith('<html>'))"
class="text-white"
>
<summary class="cursor-pointer select-none">Preview :</summary>
<iframe class="w-full h-[500px] bg-white" :srcdoc="log.response"></iframe>
</details>
<details v-if="log.response" class="text-white">
<summary class="cursor-pointer select-none">Response :</summary>
<pre style="tab-size: 2">
<code class="whitespace-pre-wrap">
{{ String(log.response) }}
</code>
</pre>
</details>
<details class="text-white">
<summary class="cursor-pointer select-none">Req Headers :</summary>
<pre style="tab-size: 2">
<code class="whitespace-pre-line">
{{ log.requestHeaders }}
</code>
</pre>
</details>
<details class="text-white">
<summary class="cursor-pointer select-none">Res Headers :</summary>
<pre style="tab-size: 2">
<code class="whitespace-pre-line">
{{ log.responseHeaders }}
</code>
</pre>
</details>
<details v-if="log.payload" class="text-white">
<summary class="cursor-pointer select-none">Payload :</summary>
<pre style="tab-size: 2">
<code class="whitespace-pre-line">
{{ log.payload }}
</code>
</pre>
</details>
</div>
</details>
</div>
</div>
</div>
</template>
<style>
.log-item-animation {
animation-name: log-fade-in;
animation-duration: 0.5s;
}
@keyframes log-fade-in {
from {
opacity: 0;
scale: 0.8;
}
to {
opacity: 1;
scale: 1;
}
}
</style>
+4 -2
View File
@@ -9,8 +9,8 @@ import usePersianDate from "~/composables/global/usePersianDate";
useSeoMeta({
title: "نتیجه تراکنش",
description: "",
keywords : ""
})
keywords: "",
});
definePageMeta({
layout: "none",
@@ -91,6 +91,8 @@ const statusTitle = computed(() => {
class="aspect-square w-[220px] md:w-[280px] lg:w-[320px] absolute -top-[70px] md:-top-[110px] lg:-top-[138px] left-1/2 -translate-x-1/2 z-10 [filter:_drop-shadow(0px_4px_20px_rgba(0, 0, 0, 0.15))]"
src="/img/heymlz/heymlz-seat.gif"
:class="statusVariants.hue_deg"
loading="lazy"
fetch-priority="low"
/>
<div
-9
View File
@@ -1,9 +0,0 @@
import VueScrollTo from "vue-scrollto";
export default defineNuxtPlugin(() => {
return {
provide: {
vScrollTo: VueScrollTo,
},
};
});
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB