feat: add landing page components including FAQ, features, footer, header, hero, modalities, steps, and section title

This commit is contained in:
2026-09-21 04:32:59 +03:30
parent 1e599b917b
commit 5f19f82f86
41 changed files with 1518 additions and 84 deletions
+195
View File
@@ -0,0 +1,195 @@
@keyframes landing-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
@keyframes landing-scan {
0% { top: 0; opacity: 0; }
8% { opacity: 1; }
92% { opacity: 1; }
100% { top: 100%; opacity: 0; }
}
@keyframes landing-pulse {
0% { transform: scale(0.9); opacity: 0.8; }
100% { transform: scale(1.7); opacity: 0; }
}
@keyframes landing-blob {
0%, 100% { border-radius: 42% 58% 60% 40% / 45% 40% 60% 55%; }
50% { border-radius: 58% 42% 40% 60% / 55% 60% 40% 45%; }
}
@keyframes landing-kenburns {
from { transform: scale(1); }
to { transform: scale(1.08); }
}
@keyframes landing-spin {
to { transform: rotate(360deg); }
}
@keyframes landing-marquee {
from { transform: translateX(0); }
to { transform: translateX(50%); }
}
@keyframes landing-draw {
to { stroke-dashoffset: 0; }
}
@keyframes landing-timeline {
0%, 12% { opacity: 0.35; }
18%, 100% { opacity: 1; }
}
@keyframes landing-toggle {
0%, 40% { transform: translateX(0); }
50%, 90% { transform: translateX(-100%); }
100% { transform: translateX(0); }
}
@keyframes landing-bubble {
0% { opacity: 0; transform: translateY(12px) scale(0.96); }
100% { opacity: 1; transform: translateY(0) scale(1); }
}
.landing-page {
background-image: radial-gradient(rgba(128, 14, 19, 0.08) 1px, transparent 1px);
background-size: 28px 28px;
}
.landing-grid {
background-image: linear-gradient(#DEE3EB 1px, transparent 1px), linear-gradient(90deg, #DEE3EB 1px, transparent 1px);
background-size: 32px 32px;
}
.landing-float {
animation: landing-float 6s ease-in-out infinite;
}
.landing-float--delay {
animation-delay: 1.6s;
}
.landing-scan {
animation: landing-scan 3.6s ease-in-out infinite;
}
.landing-pulse::after {
content: '';
@apply absolute inset-0 rounded-full border-2 border-complement-700;
animation: landing-pulse 2.2s ease-out infinite;
}
.landing-blob {
animation: landing-blob 14s ease-in-out infinite;
}
.landing-kenburns {
animation: landing-kenburns 24s ease-out forwards;
}
.landing-spin svg {
animation: landing-spin 18s linear infinite;
}
.landing-marquee {
animation: landing-marquee 32s linear infinite;
}
.landing-marquee:hover {
animation-play-state: paused;
}
.landing-underline path {
stroke-dasharray: 320;
stroke-dashoffset: 320;
animation: landing-draw 1.1s ease-out 0.5s forwards;
}
.landing-timeline-step {
animation: landing-timeline 8s ease-in-out infinite;
}
.landing-timeline-step:nth-child(2) { animation-delay: 1.6s; }
.landing-timeline-step:nth-child(3) { animation-delay: 3.2s; }
.landing-timeline-step:nth-child(4) { animation-delay: 4.8s; }
.landing-toggle-thumb {
animation: landing-toggle 6s ease-in-out infinite;
}
.landing-bubble {
opacity: 0;
animation: landing-bubble 0.5s ease-out forwards;
}
.landing-bubble:nth-child(2) { animation-delay: 0.6s; }
.landing-bubble:nth-child(3) { animation-delay: 1.2s; }
.landing-reveal {
@apply opacity-0 translate-y-8 transition-all duration-700 ease-out;
}
.landing-reveal.is-visible {
@apply opacity-100 translate-y-0;
}
.landing-reveal--1 { transition-delay: 100ms; }
.landing-reveal--2 { transition-delay: 200ms; }
.landing-reveal--3 { transition-delay: 300ms; }
.landing-reveal--4 { transition-delay: 400ms; }
.landing-reveal--5 { transition-delay: 500ms; }
.landing-timeline-line {
@apply origin-top scale-y-0 transition-transform duration-1000 ease-out;
}
.landing-reveal.is-visible .landing-timeline-line {
@apply scale-y-100;
}
.landing-faq summary::-webkit-details-marker {
display: none;
}
.landing-faq[open] .landing-faq-icon {
@apply rotate-45;
}
html:has(.landing-page) {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
.landing-float,
.landing-scan,
.landing-kenburns,
.landing-spin svg,
.landing-blob,
.landing-marquee,
.landing-timeline-step,
.landing-toggle-thumb,
.landing-pulse::after {
animation: none;
}
.landing-underline path {
animation: none;
stroke-dashoffset: 0;
}
.landing-bubble {
animation: none;
opacity: 1;
}
.landing-reveal {
@apply opacity-100 translate-y-0 transition-none;
}
.landing-timeline-line {
@apply scale-y-100 transition-none;
}
}
+4 -4
View File
@@ -23,19 +23,19 @@
}
.ui-button--dominant.p-button.p-button-outlined {
@apply bg-transparent text-dominant-500 border-dominant-500;
@apply !bg-transparent !text-dominant-500 border-dominant-500;
}
.ui-button--dominant.p-button.p-button-outlined:hover {
@apply bg-dominant-500 text-white;
@apply !bg-dominant-500 !text-white;
}
.ui-button--dominant.p-button.p-button-text {
@apply bg-transparent text-dominant-500 border-transparent;
@apply !bg-transparent !text-dominant-500 !border-transparent;
}
.ui-button--dominant.p-button.p-button-text:hover {
background-color: rgba(128, 14, 19, 0.1);
background-color: rgba(128, 14, 19, 0.1) !important;
}
+7
View File
@@ -81,3 +81,10 @@
background: #576989 !important;
}
@media (max-width: 767px) {
.fast-request-dialog {
width: calc(100vw - 1.5rem) !important;
max-width: 688px;
}
}
+10
View File
@@ -48,3 +48,13 @@
.p-datatable-tbody > tr > td{
@apply !border-0
}
.table-generic {
@apply w-full min-w-0;
}
.table-generic-scroll {
@apply w-full overflow-x-auto;
}
.table-generic-scroll .p-datatable-table {
@apply min-w-full;
}
+1
View File
@@ -14,6 +14,7 @@
@import "tailwindcss";
@import "./Client/sidebar/sidebar.css";
@import "./Client/page-transitions.css";
@import "./Client/landing.css";
@config "../../Client-tailwind.config.js";
@@ -55,7 +55,7 @@ defineProps({
<ActivitiesWidget :stats="stats" />
<UiCard title="ثبت آنی درخواست جدید" variant="default" class="mt-4">
<p class="w-full flex items-center justify-start font-[400] text-[11px]">فقط چند قدم ساده برای ثبت درخواست کافیه.</p>
<div class="w-full h-[179px] grid grid-cols-5 gap-3 justify-start pt-[10px]">
<div class="w-full grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 justify-start pt-[10px]">
<RequestCard
v-for="modality in modalities"
:key="modality.code"
+31 -29
View File
@@ -1,39 +1,41 @@
<script setup>
import { Link } from '@inertiajs/inertia-vue3'
import { ref } from 'vue'
import { usePage } from '@inertiajs/inertia-vue3'
import { useScrollReveal } from '../composables/useScrollReveal'
import LandingHeader from '../components/landing/LandingHeader.vue'
import LandingHero from '../components/landing/LandingHero.vue'
import LandingMarquee from '../components/landing/LandingMarquee.vue'
import LandingSteps from '../components/landing/LandingSteps.vue'
import LandingFeatures from '../components/landing/LandingFeatures.vue'
import LandingModalities from '../components/landing/LandingModalities.vue'
import LandingComparison from '../components/landing/LandingComparison.vue'
import LandingFaq from '../components/landing/LandingFaq.vue'
import LandingCta from '../components/landing/LandingCta.vue'
import LandingFooter from '../components/landing/LandingFooter.vue'
const {url ,props} = usePage();
const user = props.value.auth.User || null;
const { props } = usePage()
const user = props.value.auth.User || null
const page = ref(null)
useScrollReveal(page)
</script>
<template>
<div class="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-2xl w-full space-y-8">
<h1 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
پروژه رادیکال
</h1>
<div ref="page" class="landing-page min-h-screen bg-white text-dark">
<LandingHeader :user="user" />
<div class="bg-white p-6 rounded-lg space-y-6">
<p>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد. کتابهای زیادی در شصت و سه درصد گذشته، حال و آینده شناخت فراوان جامعه و متخصصان را می طلبد تا با نرم افزارها شناخت بیشتری را برای طراحان رایانه ای علی الخصوص طراحان خلاقی و فرهنگ پیشرو در زبان فارسی ایجاد کرد. در این صورت می توان امید داشت که تمام و دشواری موجود در ارائه راهکارها و شرایط سخت تایپ به پایان رسد و زمان مورد نیاز شامل حروفچینی دستاوردهای اصلی و جوابگوی سوالات پیوسته اهل دنیای موجود طراحی اساسا مورد استفاده قرار گیرد.
</p>
</div>
<main>
<LandingHero :user="user" />
<LandingMarquee />
<LandingSteps />
<LandingFeatures />
<LandingModalities />
<LandingComparison />
<LandingFaq />
<LandingCta :user="user" />
</main>
<div class="flex items-center justify-center gap-4" v-if="!user">
<Link :href="route('login')" class="text-center bg-primary text-white px-4 py-2 rounded-md hover:bg-primary-active transition-all duration-300">
ورود
</Link>
<Link :href="route('login')" class="text-center bg-primary text-white px-4 py-2 rounded-md hover:bg-primary-active transition-all duration-300">
ثبت نام
</Link>
</div>
<div class="flex items-center justify-center gap-4" v-if="user">
<a :href="route('dashboard.index')" class="text-center bg-primary text-white px-4 py-2 rounded-md hover:bg-primary-active transition-all duration-300">
داشبورد
</a>
</div>
</div>
</div>
<LandingFooter />
</div>
</template>
@@ -65,9 +65,9 @@
props.selectable && 'cursor-pointer',
fastRequestStore.requestType === props.value ? 'border-[#576989] border-2': 'border-[#DEE3EB]'
]">
<component :is="iconComponent" class="w-[87px] h-[80px]" />
<component :is="iconComponent" class="w-[72px] h-[66px] sm:w-[87px] sm:h-[80px] shrink-0" />
<div class="w-full flex flex-col gap-1">
<p class="w-full flex items-center justify-center font-[700] text-[14px]">{{ title }}</p>
<p class="w-full flex items-center justify-center text-center font-[700] text-[13px] sm:text-[14px] leading-6">{{ title }}</p>
<ui-button v-if="!selectable" @click="submitRequest" label="ثبت درخواست" severity="secondary" size="small" full-width="true"/>
</div>
</div>
@@ -32,7 +32,7 @@ const showChooseArchive = () => {
</p>
<button type="button" @click="fastRequestStore.clearArchivePet" class="text-[11px] font-[400] text-[#7A1419] cursor-pointer">حذف و ثبت کیس جدید</button>
</div>
<form v-else class="p-4 grid grid-cols-2 gap-4 w-full">
<form v-else class="p-4 grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
<InputText
v-model="fastRequestStore.requestCase.name"
type="text"
@@ -11,7 +11,7 @@ const fastRequestStore = useFastRequestStore();
<template>
<div class="w-full">
<p class="text-[11px] font-[400] text-[#3E4C63] py-3">جزئیات درخواست و اطلاعات مورد نیاز را با دقت و حوصله وارد نمایید.</p>
<div class="w-full grid grid-cols-2 gap-2 items-center justify-between">
<div class="w-full grid grid-cols-1 sm:grid-cols-2 gap-2 items-center justify-between">
<Select
v-model="fastRequestStore.requestDetails.priority"
:options="priorities"
@@ -29,7 +29,7 @@ const fastRequestStore = useFastRequestStore();
rows="3"
class="w-full h-full" />
</div>
<div class="w-full grid grid-cols-2 gap-2 items-start justify-between my-4">
<div class="w-full grid grid-cols-1 sm:grid-cols-2 gap-2 items-start justify-between my-4">
<FileUploadBox v-model="fastRequestStore.historyFiles" title="سوابق پزشکی قبلی (اختیاری)" />
<FileUploadBox v-model="fastRequestStore.scanFiles" :title="`فایل «${modalityTitle(fastRequestStore.requestType)}»`" />
</div>
@@ -9,7 +9,7 @@ const selectDoctor = (doctorId) => {
</script>
<template>
<div class="w-full h-full grid grid-cols-3 gap-2 items-center justify-between py-4">
<div class="w-full h-full grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2 items-center justify-between py-4">
<button
type="button"
@click="selectDoctor(null)"
@@ -17,7 +17,7 @@ const selectDoctor = (doctorId) => {
:class="fastRequestStore.doctorId === null ? 'border-[2px] border-[#576989]' : 'border-[1px] border-[#DEE3EB]'"
>
<h3 class="text-[13px] font-[600] text-[#576989]">انتخاب خودکار</h3>
<p class="w-[190px] text-center text-[11px] font-[600] text-[#3E4C63]">انتخاب در دسترس‌ ترین پزشک جهت بررسی سریع درخواست شما</p>
<p class="max-w-[190px] px-2 text-center text-[11px] font-[600] text-[#3E4C63]">انتخاب در دسترس‌ ترین پزشک جهت بررسی سریع درخواست شما</p>
</button>
<button
v-for="doctor in fastRequestStore.doctors"
@@ -6,7 +6,7 @@ import { modalities } from '../../../data/fastRequestOptions';
<template>
<div class="w-full space-y-3 py-6">
<p class="h-[17px] flex items-center justify-start text-[11px] font-[600] text-[#3E4C63]">نوع درخواست خود را مشخص کنید</p>
<div class="w-full h-[133px] grid grid-cols-5 gap-3 justify-start">
<div class="w-full grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3 justify-start">
<RequestCard
v-for="modality in modalities"
:key="modality.code"
@@ -45,44 +45,38 @@ const menuItems = [
</script>
<template>
<header class="grid items-center
shadow-client-gray/10 grid-cols-2a pe-4 justify-between !ps-[16rem] max-sm:!ps-4 fixed top-0 left-0 right-0 z-20 w-full h-[4.5rem] bg-white">
<div class="grid grid-cols-2a justify-start items-center gap-4">
<div class="grid grid-cols-1 max-sm:!grid-cols-2a justify-start items-center">
<Button
icon="fi-rr-menu-burger"
class="!hidden !invisible !px-0 !w-0 !h-0 max-sm:!w-10 max-sm:!h-10 max-sm:!visible max-sm:!block items-center !border-dominant-50"
pt:icon="flex !text-dominant-300 justify-center items-center"
variant="outlined"
size="large"
@click="toggleSidebar"
/>
</div>
<div class="grid grid-cols-1 h-[4rem] content-center justify-start items-center">
<UiInput
placeholder="جستجو سریع: نام سرپرست، نام حیوان و یا شماره پرونده"
variant="outlined"
prefixClasses="!border-dominant-50"
class="!w-[30rem] max-lg:!w-[22rem] max-md:!w-[26rem] max-sm:!w-[15rem] max-xs:!w-[14rem]"
prefixColor="dominant-700"
iconStart="svg:SearchSvg"
wrapperBorderClass="!border-dominant-50"
customClass="placeholder:!text-dominant-300 !border-dominant-50 !font-normal !text-[11px]"
/>
</div>
</div>
<div class="flex items-center gap-4">
<header class="flex items-center gap-3 sm:gap-4 shadow-client-gray/10 px-4 lg:!ps-[16rem] fixed top-0 left-0 right-0 z-20 w-full h-[4.5rem] bg-white">
<Button
icon="fi-rr-menu-burger"
class="lg:!hidden !w-10 !h-10 shrink-0 !border-dominant-50"
pt:icon="flex !text-dominant-300 justify-center items-center"
variant="outlined"
size="large"
@click="toggleSidebar"
/>
<div class="flex-1 min-w-0 max-w-[30rem]">
<UiInput
placeholder="جستجو سریع: نام سرپرست، نام حیوان و یا شماره پرونده"
variant="outlined"
prefixClasses="!border-dominant-50"
class="!w-full"
prefixColor="dominant-700"
iconStart="svg:SearchSvg"
wrapperBorderClass="!border-dominant-50"
customClass="placeholder:!text-dominant-300 !border-dominant-50 !font-normal !text-[11px] truncate"
/>
</div>
<div class="flex items-center gap-2 sm:gap-4 shrink-0 me-auto">
<Link href="/dashboard/wallet" class="flex items-center gap-2.5 rounded-lg bg-dominant-light border border-dominant-50 ps-1.5 pe-3 py-1.5 transition-colors hover:border-dominant-100">
<span class="flex items-center justify-center w-8 h-8 rounded-lg bg-white border border-dominant-50 text-dominant-400">
<i class="fi fi-rr-wallet flex text-[15px]"></i>
</span>
<span class="flex flex-col max-sm:hidden">
<span class="hidden md:flex flex-col">
<span class="text-[10px] font-normal text-dominant-200">موجودی کیف پول</span>
<span class="text-[12px] font-bold text-dominant-500">{{ walletBalance }} <span class="text-[10px] font-normal text-dominant-300">تومان</span></span>
</span>
</Link>
<div class="flex items-center justify-center border-l-[1px] !border-dominant-50 pl-4">
<div class="hidden sm:flex items-center justify-center border-l-[1px] !border-dominant-50 pl-4">
<GlobalSvg />
</div>
<div ref="userMenuRef" class="relative">
@@ -91,7 +85,7 @@ const menuItems = [
<img v-if="globalStore.current_user.profile_url" :src="globalStore.current_user.profile_url" alt="تصویر پروفایل" class="w-full h-full object-cover" />
<i v-else class="fi fi-rr-user flex text-sm text-dominant-300"></i>
</span>
<span class="text-dominant-300 text-[12px] font-normal max-sm:hidden">
<span class="text-dominant-300 text-[12px] font-normal hidden md:inline">
{{ globalStore.current_user.fullname }}
</span>
<i class="fi fi-rr-angle-small-down flex text-sm text-dominant-300 transition-transform duration-200" :class="{ 'rotate-180': userMenuOpen }"></i>
@@ -0,0 +1,34 @@
<script setup>
import LandingSectionTitle from './LandingSectionTitle.vue'
const rows = [
{ label: 'جابه‌جایی پت', before: 'سفر پراسترس تا کلینیک', after: 'پت در خانه می‌ماند' },
{ label: 'دسترسی به رادیولوژیست', before: 'وابسته به شهر و کلینیک', after: 'رادیولوژیست در شیفت، از هر جای ایران' },
{ label: 'زمان دریافت گزارش', before: 'روزها و تماس‌های پیگیری', after: '۲۴ ساعت عادی · ۳ ساعت اورژانسی' },
{ label: 'خبردار شدن', before: 'باید خودتان زنگ بزنید', after: 'پیامک در هر مرحله' },
{ label: 'نگهداری سوابق', before: 'کاغذ و CD گم‌شدنی', after: 'پرونده‌ی دیجیتال دائمی' },
]
</script>
<template>
<section class="bg-light py-20 lg:py-28">
<div class="container max-w-5xl px-4 sm:px-6">
<LandingSectionTitle eyebrow="چرا رادیکال" title="همان گزارش تخصصی، بدون دردسرهایش" />
<div class="landing-reveal mt-14 overflow-hidden rounded-[28px] border-2 border-dominant-50 bg-white">
<div class="grid grid-cols-[1.1fr_1fr_1fr] bg-dominant-50/40 text-xs sm:text-sm font-bold">
<span class="px-4 sm:px-6 py-4 text-dark/50">مقایسه</span>
<span class="px-4 sm:px-6 py-4 text-dark/50">مراجعه‌ی حضوری</span>
<span class="px-4 sm:px-6 py-4 bg-dominant-500 text-white flex items-center gap-2"><i class="fi fi-rr-paw leading-none text-[11px]"></i>رادیکال</span>
</div>
<dl>
<div v-for="row in rows" :key="row.label" class="grid grid-cols-[1.1fr_1fr_1fr] border-t border-dominant-50 text-xs sm:text-sm">
<dt class="px-4 sm:px-6 py-4 font-bold text-dark">{{ row.label }}</dt>
<dd class="px-4 sm:px-6 py-4 text-dark/50 flex items-start gap-2"><i class="fi fi-rr-cross-small text-danger leading-none mt-1"></i>{{ row.before }}</dd>
<dd class="px-4 sm:px-6 py-4 bg-dominant-50/50 font-semibold text-dark flex items-start gap-2"><i class="fi fi-rr-check text-success leading-none mt-1 text-[11px]"></i>{{ row.after }}</dd>
</div>
</dl>
</div>
</div>
</section>
</template>
@@ -0,0 +1,47 @@
<script setup>
import { Inertia } from '@inertiajs/inertia'
import UiButton from '../ui/UiButton.vue'
defineProps({
user: {
type: Object,
default: null
}
})
</script>
<template>
<section class="bg-white pb-20 lg:pb-28">
<div class="container max-w-7xl px-4 sm:px-6">
<div class="landing-reveal relative overflow-hidden rounded-[36px] bg-dark px-6 py-16 sm:px-12 lg:px-20 lg:py-20 grid lg:grid-cols-[1.3fr_1fr] gap-10 items-center">
<svg class="absolute -bottom-16 -left-10 w-72 h-72 text-white/[0.04] pointer-events-none" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M8.35 3c1.18-.17 2.43 1.12 2.79 2.9.36 1.77-.29 3.35-1.47 3.53-1.17.18-2.43-1.11-2.8-2.89-.36-1.77.3-3.35 1.48-3.54zm7.3 0c1.18.19 1.84 1.77 1.48 3.54-.37 1.78-1.63 3.07-2.8 2.89-1.18-.18-1.83-1.76-1.47-3.53.36-1.78 1.61-3.07 2.79-2.9zM3 7.6c1.14-.49 2.72.38 3.53 1.95.81 1.56.55 3.22-.58 3.71-1.14.48-2.72-.39-3.53-1.95C1.61 9.75 1.87 8.09 3 7.6zm18 0c1.13.49 1.39 2.15.58 3.71-.81 1.56-2.39 2.43-3.53 1.95-1.13-.49-1.39-2.15-.58-3.71.81-1.57 2.39-2.44 3.53-1.95zM12 12c2.8 0 5.6 3.2 6.2 6.2.4 2-.8 3.8-3 3.8-1.2 0-2.2-.7-3.2-.7s-2 .7-3.2.7c-2.2 0-3.4-1.8-3-3.8C6.4 15.2 9.2 12 12 12z" />
</svg>
<div class="relative">
<h2 class="text-2xl sm:text-3xl lg:text-[2.6rem] font-black leading-[1.45] text-white">
اولین پرونده‌ی پت‌تان را
<span class="text-dominant-300">همین امروز</span>
باز کنید
</h2>
<p class="mt-5 max-w-lg text-base leading-8 text-white/60">
ثبت‌نام فقط با شماره موبایل و در کمتر از یک دقیقه. تا وقتی درخواستی ثبت نکنید، هزینه‌ای در کار نیست.
</p>
<div class="mt-8 flex flex-wrap items-center gap-4">
<UiButton v-if="user" label="رفتن به پنل کاربری" severity="secondary" size="large" @click="Inertia.visit(route('dashboard.index'))" />
<UiButton v-else label="ثبت‌نام به‌عنوان سرپرست" severity="secondary" size="large" @click="Inertia.visit(route('register.chooseType'))" />
<span class="text-xs text-white/40">بدون نیاز به کارت بانکی</span>
</div>
</div>
<ul class="relative space-y-3" aria-hidden="true">
<li v-for="(item, i) in ['ساخت پروفایل پت', 'آپلود تصاویر', 'انتخاب رادیولوژیست', 'دریافت گزارش']" :key="item" class="flex items-center gap-3 rounded-2xl bg-white/5 border border-white/10 px-4 py-3 text-sm text-white/80">
<span :class="i < 3 ? 'bg-success text-white' : 'bg-white/10 text-white/50'" class="w-6 h-6 rounded-full flex items-center justify-center text-[10px]"><i class="fi fi-rr-check leading-none"></i></span>
{{ item }}
<span v-if="i === 3" class="mr-auto text-[10px] text-dominant-300">پیامک ↓</span>
</li>
</ul>
</div>
</div>
</section>
</template>
@@ -0,0 +1,54 @@
<script setup>
import LandingSectionTitle from './LandingSectionTitle.vue'
const faqs = [
{
question: 'برای ثبت‌نام به چه چیزی نیاز دارم؟',
answer: 'فقط شماره موبایل. با کد تأیید پیامکی وارد می‌شوید و به‌عنوان «سرپرست» ثبت‌نام می‌کنید. نیازی به مدارک پزشکی یا پروانه نیست.'
},
{
question: 'تصاویر را با چه فرمتی می‌توانم بفرستم؟',
answer: 'تصاویر و فایل‌هایی که مرکز تصویربرداری به شما داده (عکس، PDF یا فایل فشرده) را می‌توانید مستقیم در فرم درخواست بارگذاری کنید. سوابق قبلی پت را هم می‌توانید جداگانه ضمیمه کنید.'
},
{
question: 'گزارش چقدر طول می‌کشد؟',
answer: 'با اولویت عادی، مهلت بررسی پرونده ۲۴ ساعت پس از پرداخت است و با اولویت اورژانسی ۳ ساعت. اولویت را هنگام ثبت درخواست انتخاب می‌کنید.'
},
{
question: 'گزارش را چطور دریافت می‌کنم؟',
answer: 'به‌محض آماده شدن گزارش، پیامک دریافت می‌کنید و گزارش کامل همراه با پیوست‌های پزشک در بخش «پرونده‌ها»ی پنل شما قابل مشاهده و دانلود است. برای حفظ حریم خصوصی، متن گزارش هرگز در پیامک ارسال نمی‌شود.'
},
{
question: 'اگر پزشک پرونده را رد کند چه می‌شود؟',
answer: 'اگر تصاویر قابل گزارش‌دهی نباشند، پزشک با ذکر دلیل پرونده را رد می‌کند و مبلغ فاکتور به‌صورت کامل به کیف پول شما بازگردانده می‌شود.'
},
]
</script>
<template>
<section id="faq" class="bg-white py-20 lg:py-28 scroll-mt-20">
<div class="container max-w-7xl px-4 sm:px-6 grid lg:grid-cols-[1fr_1.6fr] gap-12 lg:gap-20 items-start">
<div class="lg:sticky lg:top-28">
<LandingSectionTitle class="!text-right !mx-0" eyebrow="سوالات متداول" title="چیزهایی که سرپرستان قبل از اولین درخواست می‌پرسند" />
<p class="landing-reveal landing-reveal--2 mt-6 text-sm leading-7 text-dark/55">جوابتان را پیدا نکردید؟ بعد از ورود، از داخل پنل با ما در تماس باشید.</p>
</div>
<div class="space-y-3">
<details
v-for="(faq, index) in faqs"
:key="faq.question"
:class="`landing-reveal--${Math.min(index + 1, 5)}`"
class="landing-faq landing-reveal group rounded-2xl bg-light border-2 border-transparent open:border-dominant-100 open:bg-white transition-colors duration-300"
>
<summary class="flex items-center justify-between gap-4 cursor-pointer list-none px-6 py-5 text-sm sm:text-base font-bold text-dark">
{{ faq.question }}
<span class="landing-faq-icon shrink-0 w-8 h-8 rounded-full bg-white border border-dominant-50 flex items-center justify-center text-dominant-500 transition-transform duration-300">
<i class="fi fi-rr-plus text-xs leading-none"></i>
</span>
</summary>
<p class="px-6 pb-6 text-sm leading-7 text-dark/60">{{ faq.answer }}</p>
</details>
</div>
</div>
</section>
</template>
@@ -0,0 +1,72 @@
<script setup>
import LandingSectionTitle from './LandingSectionTitle.vue'
</script>
<template>
<section id="features" class="bg-light py-20 lg:py-28 scroll-mt-20">
<div class="container max-w-7xl px-4 sm:px-6">
<LandingSectionTitle
eyebrow="امکانات"
title="ساخته‌شده برای کسی که نگران پتش است، نه برای کلینیک"
description="بدون اصطلاحات تخصصی، بدون فرم‌های طولانی. فقط چیزهایی که یک سرپرست واقعاً لازم دارد."
/>
<div class="mt-14 grid gap-5 md:grid-cols-2 lg:grid-cols-4 lg:grid-rows-2">
<article class="landing-reveal lg:col-span-2 lg:row-span-2 relative overflow-hidden rounded-[28px] bg-dark p-7 sm:p-9 text-white">
<div class="landing-blob absolute -bottom-24 -left-24 w-72 h-72 bg-dominant-500/40 pointer-events-none" aria-hidden="true"></div>
<div class="relative">
<span class="inline-flex w-12 h-12 items-center justify-center rounded-2xl bg-white/10 text-xl"><i class="fi fi-rr-comment-alt-dots leading-none"></i></span>
<h3 class="mt-6 text-2xl font-extrabold">در هر مرحله پیامک می‌گیرید</h3>
<p class="mt-3 max-w-md text-sm leading-7 text-white/65">ثبت درخواست، پرداخت موفق و آماده شدن گزارش؛ سه پیامک، صفر پیگیری. متن گزارش هیچ‌وقت در پیامک نیست — فقط خبرش.</p>
</div>
<div class="relative mt-8 mr-auto w-full max-w-[260px] rounded-[26px] bg-white/10 p-3" aria-hidden="true">
<div class="rounded-[20px] bg-white p-3 space-y-2.5">
<div class="landing-bubble rounded-xl rounded-tr-sm bg-light px-3 py-2 text-[11px] leading-5 text-dark">درخواست شما ثبت شد. شماره پرونده: ۲۶۴۹۵۸</div>
<div class="landing-bubble rounded-xl rounded-tr-sm bg-light px-3 py-2 text-[11px] leading-5 text-dark">پرداخت فاکتور ۰۰۰۶۹ به مبلغ ۱٬۲۰۰٬۰۰۰ تومان انجام شد.</div>
<div class="landing-bubble rounded-xl rounded-tr-sm bg-dominant-500 px-3 py-2 text-[11px] leading-5 text-white">گزارش پرونده ۲۶۴۹۵۸ آماده شد.</div>
</div>
</div>
</article>
<article class="landing-reveal landing-reveal--1 lg:col-span-2 rounded-[28px] bg-white border-2 border-dominant-50 p-7 flex flex-col sm:flex-row sm:items-center gap-6">
<div class="flex-1">
<span class="inline-flex w-12 h-12 items-center justify-center rounded-2xl bg-dominant-50 text-dominant-500 text-xl"><i class="fi fi-rr-time-fast leading-none"></i></span>
<h3 class="mt-5 text-lg font-extrabold text-dark">عادی یا اورژانسی؟ شما تعیین می‌کنید</h3>
<p class="mt-2 text-sm leading-7 text-dark/60">مهلت بررسی با اولویت عادی ۲۴ ساعت است؛ با اورژانسی، ۳ ساعت.</p>
</div>
<div class="relative w-full sm:w-[200px] shrink-0 rounded-full bg-light p-1.5 grid grid-cols-2 text-center text-xs font-bold" aria-hidden="true">
<span class="landing-toggle-thumb absolute top-1.5 bottom-1.5 right-1.5 w-[calc(50%-6px)] rounded-full bg-dominant-500"></span>
<span class="relative z-10 py-2.5 text-white mix-blend-difference">عادی · ۲۴h</span>
<span class="relative z-10 py-2.5 text-white mix-blend-difference">اورژانسی · ۳h</span>
</div>
</article>
<article class="landing-reveal landing-reveal--2 rounded-[28px] bg-white border-2 border-dominant-50 p-7">
<span class="inline-flex w-12 h-12 items-center justify-center rounded-2xl bg-dominant-50 text-dominant-500 text-xl"><i class="fi fi-rr-wallet leading-none"></i></span>
<h3 class="mt-5 text-lg font-extrabold text-dark">پرداخت شفاف، برگشت کامل</h3>
<p class="mt-2 text-sm leading-7 text-dark/60">قیمت هر خدمت قبل از پرداخت مشخص است. اگر پزشک پرونده را رد کند، کل مبلغ به کیف پول برمی‌گردد.</p>
</article>
<article class="landing-reveal landing-reveal--3 rounded-[28px] bg-dominant-500 p-7 text-white">
<span class="inline-flex w-12 h-12 items-center justify-center rounded-2xl bg-white/15 text-xl"><i class="fi fi-rr-lock-alt leading-none"></i></span>
<h3 class="mt-5 text-lg font-extrabold">گزارش فقط برای شما</h3>
<p class="mt-2 text-sm leading-7 text-white/70">گزارش و تصاویر فقط با ورود به حساب شما و توسط پزشک پرونده قابل مشاهده‌اند؛ هیچ لینک عمومی وجود ندارد.</p>
</article>
</div>
<ul class="mt-5 grid gap-5 md:grid-cols-3">
<li v-for="item in [
{ icon: 'fi fi-rr-document', title: 'آرشیو دائمی پرونده', text: 'همه‌ی تصاویر و گزارش‌های هر پت، مرتب و قابل دانلود.' },
{ icon: 'fi fi-rr-stethoscope', title: 'انتخاب پزشک یا شیفت خودکار', text: 'رادیولوژیست دلخواه، یا اختصاص فوری به پزشکِ در شیفت.' },
{ icon: 'fi fi-rr-badge-check', title: 'رد پرونده با دلیل', text: 'اگر تصویر قابل گزارش نباشد، دلیلش را می‌بینید و هزینه برمی‌گردد.' },
]" :key="item.title" class="landing-reveal flex items-start gap-4 rounded-2xl bg-white/60 border border-dominant-50 px-5 py-4">
<i :class="item.icon" class="mt-1 text-dominant-500 leading-none"></i>
<div>
<h3 class="text-sm font-bold text-dark">{{ item.title }}</h3>
<p class="mt-1 text-xs leading-6 text-dark/55">{{ item.text }}</p>
</div>
</li>
</ul>
</div>
</section>
</template>
@@ -0,0 +1,48 @@
<script setup>
import { Link } from '@inertiajs/inertia-vue3'
const links = [
{ label: 'چطور کار می‌کند', href: '#how-it-works' },
{ label: 'امکانات', href: '#features' },
{ label: 'خدمات تصویربرداری', href: '#modalities' },
{ label: 'سوالات متداول', href: '#faq' },
]
</script>
<template>
<footer class="bg-dominant-50 border-t border-dominant-100/60">
<div class="container max-w-7xl px-4 sm:px-6 py-12 grid gap-10 md:grid-cols-[1.5fr_1fr_1fr]">
<div>
<Link href="/">
<img src="@/images/radical_logo.png" alt="تله‌رادیولوژی رادیکال" class="h-10 w-auto object-contain">
</Link>
<p class="mt-4 max-w-sm text-sm leading-7 text-dark/60">
پلتفرم تله‌رادیولوژی دامپزشکی رادیکال؛ پل ارتباطی سرپرستان حیوانات خانگی و رادیولوژیست‌های دامپزشکی برای گزارش‌دهی تخصصی تصاویر پزشکی.
</p>
</div>
<nav aria-label="دسترسی سریع">
<h3 class="text-sm font-bold text-dominant-700">دسترسی سریع</h3>
<ul class="mt-4 space-y-2">
<li v-for="link in links" :key="link.href">
<a :href="link.href" class="text-sm text-dark/60 hover:text-dominant-500 transition-colors duration-200">{{ link.label }}</a>
</li>
</ul>
</nav>
<nav aria-label="حساب کاربری">
<h3 class="text-sm font-bold text-dominant-700">حساب کاربری</h3>
<ul class="mt-4 space-y-2">
<li><Link :href="route('login')" class="text-sm text-dark/60 hover:text-dominant-500 transition-colors duration-200">ورود</Link></li>
<li><Link :href="route('register.chooseType')" class="text-sm text-dark/60 hover:text-dominant-500 transition-colors duration-200">ثبت‌نام سرپرست</Link></li>
</ul>
</nav>
</div>
<div class="border-t border-dominant-100/60">
<p class="container max-w-7xl px-4 sm:px-6 py-5 text-center text-[13px] text-dominant-700">
تمامی حقوق این وب‌سایت برای مجموعه <span class="font-semibold">تله‌رادیولوژی رادیکال</span> محفوظ می‌باشد.
</p>
</div>
</footer>
</template>
@@ -0,0 +1,73 @@
<script setup>
import { ref } from 'vue'
import { Link } from '@inertiajs/inertia-vue3'
import { Inertia } from '@inertiajs/inertia'
import UiButton from '../ui/UiButton.vue'
defineProps({
user: {
type: Object,
default: null
}
})
const isMenuOpen = ref(false)
const links = [
{ label: 'چطور کار می‌کند', href: '#how-it-works' },
{ label: 'امکانات', href: '#features' },
{ label: 'خدمات تصویربرداری', href: '#modalities' },
{ label: 'سوالات متداول', href: '#faq' },
]
</script>
<template>
<header class="sticky top-0 z-40 bg-white/90 backdrop-blur border-b border-dominant-50">
<div class="container max-w-7xl px-4 sm:px-6 h-[72px] flex items-center justify-between gap-6">
<Link href="/" class="shrink-0">
<img src="@/images/radical_logo.png" alt="تله‌رادیولوژی رادیکال" class="h-10 w-auto object-contain">
</Link>
<nav class="hidden lg:block">
<ul class="flex items-center gap-8">
<li v-for="link in links" :key="link.href">
<a :href="link.href" class="text-sm font-medium text-dark hover:text-dominant-500 transition-colors duration-200">
{{ link.label }}
</a>
</li>
</ul>
</nav>
<div class="hidden lg:flex items-center gap-3">
<template v-if="user">
<UiButton label="ورود به پنل" severity="dominant" icon="fi fi-rr-user" @click="Inertia.visit(route('dashboard.index'))" />
</template>
<template v-else>
<UiButton label="ورود" severity="dominant" variant="text" text @click="Inertia.visit(route('login'))" />
<UiButton label="ثبت‌نام رایگان" severity="dominant" @click="Inertia.visit(route('register.chooseType'))" />
</template>
</div>
<button type="button" class="lg:hidden w-10 h-10 flex items-center justify-center rounded-lg text-dominant-700 hover:bg-dominant-50" :aria-expanded="isMenuOpen" aria-controls="landing-mobile-menu" aria-label="منو" @click="isMenuOpen = !isMenuOpen">
<i :class="isMenuOpen ? 'fi fi-rr-cross' : 'fi fi-rr-menu-burger'" class="text-lg leading-none"></i>
</button>
</div>
<nav v-show="isMenuOpen" id="landing-mobile-menu" class="lg:hidden border-t border-dominant-50 bg-white">
<ul class="container max-w-7xl px-4 py-4 flex flex-col gap-1">
<li v-for="link in links" :key="link.href">
<a :href="link.href" class="block px-3 py-2 rounded-lg text-sm font-medium text-dark hover:bg-dominant-50" @click="isMenuOpen = false">
{{ link.label }}
</a>
</li>
<li class="pt-3 flex flex-col gap-2">
<UiButton v-if="user" label="ورود به پنل" severity="dominant" full-width @click="Inertia.visit(route('dashboard.index'))" />
<template v-else>
<UiButton label="ثبت‌نام رایگان" severity="dominant" full-width @click="Inertia.visit(route('register.chooseType'))" />
<UiButton label="ورود" severity="dominant" outlined full-width @click="Inertia.visit(route('login'))" />
</template>
</li>
</ul>
</nav>
</header>
</template>
@@ -0,0 +1,75 @@
<script setup>
import { Link } from '@inertiajs/inertia-vue3'
import { Inertia } from '@inertiajs/inertia'
import UiButton from '../ui/UiButton.vue'
defineProps({
user: {
type: Object,
default: null
}
})
const facts = [
{ value: '۵', label: 'نوع تصویربرداری، از MRI تا رادیولوژی' },
{ value: '۳ ساعت', label: 'مهلت گزارش برای پرونده‌های اورژانسی' },
{ value: '۳ پیامک', label: 'ثبت، پرداخت، آماده شدن گزارش' },
]
</script>
<template>
<section class="relative min-h-[92svh] flex flex-col overflow-hidden bg-dark text-white">
<img
src="@/images/landing-hero.jpg"
alt="یک گربه و یک سگ کنار هم روی چمن"
class="landing-kenburns absolute inset-0 w-full h-full object-cover object-[30%_center] lg:object-[15%_center]"
>
<div class="absolute inset-0 bg-gradient-to-l from-dark via-dark/85 via-45% to-dark/25" aria-hidden="true"></div>
<div class="absolute inset-x-0 bottom-0 h-2/3 bg-gradient-to-t from-dark via-dark/70 to-transparent" aria-hidden="true"></div>
<div class="container max-w-7xl px-4 sm:px-6 relative flex-1 flex flex-col justify-end pt-16 pb-10 lg:pb-14">
<div class="max-w-3xl">
<p class="landing-reveal text-xs sm:text-sm font-semibold tracking-wide text-dominant-300">
تله‌رادیولوژی دامپزشکی رادیکال
</p>
<h1 class="mt-5 text-[2.6rem] leading-[1.25] sm:text-6xl lg:text-[5.2rem] lg:leading-[1.2] font-black">
<span class="landing-reveal landing-reveal--1 block">اون که نگرانشی،</span>
<span class="landing-reveal landing-reveal--2 block">لازم نیست از خونه</span>
<span class="landing-reveal landing-reveal--3 block">بیرون بره<span class="text-dominant-300">.</span></span>
</h1>
<p class="landing-reveal landing-reveal--4 mt-7 max-w-xl text-base sm:text-lg leading-8 text-white/70">
تصاویر MRI، سی‌تی‌اسکن یا سونوگرافی پت‌تان را همین‌جا آپلود کنید؛ رادیولوژیست دامپزشکی گزارش می‌نویسد و شما با پیامک خبردار می‌شوید.
</p>
<div class="landing-reveal landing-reveal--5 mt-9 flex flex-wrap items-center gap-6">
<UiButton v-if="user" label="رفتن به پنل کاربری" severity="dominant" size="large" icon="fi fi-rr-arrow-small-left" icon-position="end" @click="Inertia.visit(route('dashboard.index'))" />
<UiButton v-else label="ثبت اولین درخواست" severity="dominant" size="large" icon="fi fi-rr-arrow-small-left" icon-position="end" @click="Inertia.visit(route('register.chooseType'))" />
<Link v-if="!user" :href="route('login')" class="text-sm font-semibold text-white/80 underline underline-offset-8 decoration-white/30 hover:text-white hover:decoration-dominant-300 transition-colors duration-200">
قبلاً حساب دارم
</Link>
</div>
</div>
<dl class="landing-reveal landing-reveal--5 mt-14 lg:mt-20 grid sm:grid-cols-3 border-t border-white/15 pt-6 gap-6 sm:gap-0 sm:divide-x sm:divide-x-reverse sm:divide-white/15">
<div v-for="fact in facts" :key="fact.label" class="sm:px-6 first:pr-0 last:pl-0">
<dt class="text-2xl sm:text-3xl font-black text-white">{{ fact.value }}</dt>
<dd class="mt-1 text-xs sm:text-sm leading-6 text-white/55">{{ fact.label }}</dd>
</div>
</dl>
</div>
<a href="#how-it-works" class="landing-spin absolute top-28 left-6 sm:left-10 hidden md:flex w-28 h-28 items-center justify-center rounded-full bg-dark/50 backdrop-blur-sm border border-white/15 text-white/85 hover:text-white hover:border-white/40 transition-colors duration-300" aria-label="ببینید چطور کار می‌کند">
<svg viewBox="0 0 100 100" class="absolute inset-0 w-full h-full" aria-hidden="true">
<defs>
<path id="landing-hero-circle" d="M50,50 m-38,0 a38,38 0 1,1 76,0 a38,38 0 1,1 -76,0" />
</defs>
<text class="fill-current text-[9px] font-semibold tracking-[0.25em]">
<textPath href="#landing-hero-circle">گزارش تخصصی · پیامک · بدون مراجعه ·</textPath>
</text>
</svg>
<i class="fi fi-rr-arrow-small-down text-lg leading-none"></i>
</a>
</section>
</template>
@@ -0,0 +1,16 @@
<script setup>
const items = ['MRI', 'سی‌تی‌اسکن', 'سونوگرافی', 'اکوکاردیوگرافی', 'رادیولوژی', 'سگ', 'گربه', 'خرگوش', 'پرنده', 'همستر', 'لاک‌پشت']
</script>
<template>
<div class="overflow-hidden border-y border-dominant-100/60 bg-dominant-500 py-4" aria-hidden="true">
<div class="landing-marquee flex w-max">
<ul v-for="copy in 2" :key="copy" class="flex shrink-0 items-center">
<li v-for="item in items" :key="item" class="flex items-center gap-6 px-6 text-sm font-semibold text-white/90 whitespace-nowrap">
{{ item }}
<i class="fi fi-rr-paw text-[10px] text-white/40 leading-none"></i>
</li>
</ul>
</div>
</div>
</template>
@@ -0,0 +1,55 @@
<script setup>
import LandingSectionTitle from './LandingSectionTitle.vue'
const modalities = [
{ index: '۰۱', title: 'MRI', description: 'مغز، نخاع و بافت‌های نرم؛ برای تشنج، لنگش عصبی و توده‌ها' },
{ index: '۰۲', title: 'سی‌تی‌اسکن', description: 'استخوان، جمجمه، قفسه‌ی سینه و شکم با جزئیات مقطعی' },
{ index: '۰۳', title: 'سونوگرافی', description: 'اندام‌های شکمی، بارداری، کلیه و کبد' },
{ index: '۰۴', title: 'اکوکاردیوگرافی', description: 'ساختار و عملکرد قلب، دریچه‌ها و جریان خون' },
{ index: '۰۵', title: 'رادیولوژی', description: 'شکستگی، مفاصل، قفسه‌ی سینه و اجسام خارجی' },
]
const species = [
{ icon: 'fi fi-rr-dog', label: 'سگ' },
{ icon: 'fi fi-rr-cat', label: 'گربه' },
{ icon: 'fi fi-rr-rabbit', label: 'خرگوش' },
{ icon: 'fi fi-rr-bird', label: 'پرنده' },
{ icon: 'fi fi-rr-turtle', label: 'لاک‌پشت' },
{ icon: 'fi fi-rr-fish', label: 'ماهی' },
{ icon: 'fi fi-rr-paw', label: 'همستر و خوکچه' },
]
</script>
<template>
<section id="modalities" class="bg-white py-20 lg:py-28 scroll-mt-20">
<div class="container max-w-7xl px-4 sm:px-6 grid lg:grid-cols-[1fr_1.4fr] gap-12 lg:gap-20 items-start">
<div class="lg:sticky lg:top-28">
<LandingSectionTitle
class="!text-right !mx-0"
eyebrow="خدمات تصویربرداری"
title="هر تصویری که دارید، جای خودش را دارد"
description="تصویر را از هر مرکز تصویربرداری گرفته باشید فرقی نمی‌کند؛ فقط نوعش را انتخاب کنید."
/>
<ul class="landing-reveal landing-reveal--2 mt-8 flex flex-wrap gap-2">
<li v-for="item in species" :key="item.label" class="flex items-center gap-2 rounded-full bg-light border border-dominant-50 px-3.5 py-2 text-xs font-semibold text-dark/70">
<i :class="item.icon" class="text-dominant-500 leading-none"></i>
{{ item.label }}
</li>
</ul>
</div>
<ol class="divide-y-2 divide-dominant-50 border-y-2 border-dominant-50">
<li
v-for="(modality, index) in modalities"
:key="modality.title"
:class="`landing-reveal--${index + 1}`"
class="landing-reveal group grid grid-cols-[3.5rem_1fr] sm:grid-cols-[5rem_11rem_1fr] items-center gap-4 py-6 transition-colors duration-300 hover:bg-white"
>
<span class="text-3xl sm:text-4xl font-black text-dominant-100 transition-colors duration-300 group-hover:text-dominant-500">{{ modality.index }}</span>
<h3 class="text-xl sm:text-2xl font-extrabold text-dark">{{ modality.title }}</h3>
<p class="col-span-2 sm:col-span-1 text-sm leading-7 text-dark/55 sm:pl-4">{{ modality.description }}</p>
</li>
</ol>
</div>
</section>
</template>
@@ -0,0 +1,24 @@
<script setup>
defineProps({
eyebrow: {
type: String,
required: true
},
title: {
type: String,
required: true
},
description: {
type: String,
default: ''
}
})
</script>
<template>
<div class="landing-reveal max-w-2xl mx-auto text-center">
<span class="inline-block rounded-full bg-dominant-50 px-4 py-1.5 text-xs font-semibold text-dominant-500">{{ eyebrow }}</span>
<h2 class="mt-4 text-2xl sm:text-3xl lg:text-4xl font-extrabold leading-[1.5] text-dark">{{ title }}</h2>
<p v-if="description" class="mt-4 text-base leading-8 text-dark/60">{{ description }}</p>
</div>
</template>
@@ -0,0 +1,120 @@
<script setup>
import LandingSectionTitle from './LandingSectionTitle.vue'
const steps = [
{
number: '۰۱',
title: 'پروفایل پت را بسازید',
description: 'نام، گونه، نژاد، سن و وزن؛ یک‌بار ثبت می‌کنید و برای همه‌ی درخواست‌های بعدی آماده است. هر پت پرونده‌ی مستقل خودش را دارد.',
mock: 'pet'
},
{
number: '۰۲',
title: 'تصاویر را آپلود کنید',
description: 'نوع تصویربرداری را انتخاب کنید، فایل اسکن و سوابق قبلی را بکشید و رها کنید. اگر عجله دارید، اولویت اورژانسی بگذارید.',
mock: 'upload'
},
{
number: '۰۳',
title: 'رادیولوژیست را انتخاب کنید',
description: 'پزشک مورد اعتمادتان را از لیست انتخاب کنید، یا بگذارید سیستم رادیولوژیستِ در شیفت را همان لحظه به پرونده اختصاص دهد.',
mock: 'doctor'
},
{
number: '۰۴',
title: 'پیامک بگیرید، گزارش را بخوانید',
description: 'به‌محض ثبت گزارش، پیامک می‌گیرید. گزارش کامل با تصاویر پیوست در پنل شماست؛ بدون تماس، بدون پیگیری.',
mock: 'sms'
},
]
</script>
<template>
<section id="how-it-works" class="bg-white py-20 lg:py-28 scroll-mt-20">
<div class="container max-w-7xl px-4 sm:px-6">
<LandingSectionTitle
eyebrow="چطور کار می‌کند"
title="از عکس تا گزارش، چهار قدم"
description="همان مسیری که در پنل طی می‌کنید؛ نه بیشتر."
/>
<ol class="landing-reveal relative mt-16 space-y-10 lg:space-y-0">
<span class="landing-timeline-line hidden lg:block absolute right-1/2 translate-x-1/2 top-0 bottom-0 w-0.5 bg-dominant-100" aria-hidden="true"></span>
<li
v-for="(step, index) in steps"
:key="step.number"
class="relative grid lg:grid-cols-2 gap-8 lg:gap-24 items-center lg:py-10"
>
<span class="hidden lg:flex absolute right-1/2 translate-x-1/2 w-12 h-12 items-center justify-center rounded-full bg-dominant-500 text-white text-sm font-bold ring-8 ring-white" aria-hidden="true">{{ step.number }}</span>
<div :class="index % 2 === 0 ? 'lg:order-1 lg:pl-6' : 'lg:order-2 lg:pr-6'">
<span class="lg:hidden inline-flex w-10 h-10 items-center justify-center rounded-full bg-dominant-500 text-white text-xs font-bold">{{ step.number }}</span>
<h3 class="mt-4 lg:mt-0 text-xl sm:text-2xl font-extrabold text-dark">{{ step.title }}</h3>
<p class="mt-3 text-sm sm:text-base leading-8 text-dark/60">{{ step.description }}</p>
</div>
<div :class="index % 2 === 0 ? 'lg:order-2' : 'lg:order-1'" class="rounded-[28px] bg-light border-2 border-dominant-50 p-5 sm:p-7" aria-hidden="true">
<div v-if="step.mock === 'pet'" class="rounded-2xl bg-white p-5 space-y-3">
<div class="flex items-center gap-3">
<span class="w-12 h-12 rounded-2xl bg-dominant-50 text-dominant-500 flex items-center justify-center text-xl"><i class="fi fi-rr-cat leading-none"></i></span>
<div class="flex-1 space-y-1.5">
<span class="block h-2 w-1/3 rounded-full bg-dark/10"></span>
<span class="block h-2 w-1/5 rounded-full bg-dark/5"></span>
</div>
</div>
<div class="grid grid-cols-2 gap-3 pt-2">
<div v-for="field in ['نام: پیشی', 'گونه: گربه', 'سن: ۳ سال', 'وزن: ۴.۲ کیلو']" :key="field" class="rounded-xl border border-dominant-50 px-3 py-2.5 text-xs text-dark/70">{{ field }}</div>
</div>
<div class="flex justify-end pt-1">
<span class="rounded-lg bg-dominant-500 px-4 py-2 text-[11px] font-semibold text-white">ذخیره‌ی پروفایل</span>
</div>
</div>
<div v-else-if="step.mock === 'upload'" class="rounded-2xl bg-white p-5 space-y-3">
<div class="flex gap-2">
<span v-for="(modality, i) in ['MRI', 'سی‌تی', 'سونو', 'اکو']" :key="modality" :class="i === 0 ? 'bg-dominant-500 text-white' : 'bg-dominant-50 text-dark/60'" class="rounded-full px-3 py-1 text-[11px] font-semibold">{{ modality }}</span>
</div>
<div class="rounded-2xl border-2 border-dashed border-dominant-200 bg-light/60 p-5 text-center">
<i class="fi fi-rr-cloud-upload text-2xl text-dominant-500 leading-none"></i>
<p class="mt-2 text-xs font-semibold text-dark">فایل‌ها را اینجا رها کنید</p>
</div>
<ul class="space-y-2">
<li v-for="file in ['Pishi_MRI_T2.zip · ۴۸ MB', 'سابقه‌ی آزمایش خون.pdf']" :key="file" class="flex items-center justify-between rounded-xl bg-light px-3 py-2 text-[11px] text-dark/70">
{{ file }}
<i class="fi fi-rr-check-circle text-success leading-none"></i>
</li>
</ul>
<div class="flex items-center justify-between rounded-xl bg-dominant-50 px-3 py-2 text-[11px] font-semibold text-dominant-700">
اولویت
<span class="flex gap-1"><span class="rounded-md bg-white px-2 py-0.5">عادی</span><span class="rounded-md bg-dominant-500 px-2 py-0.5 text-white">اورژانسی</span></span>
</div>
</div>
<div v-else-if="step.mock === 'doctor'" class="space-y-3">
<div v-for="(doctor, i) in [{ name: 'دکتر آرمان صادقی', spec: 'رادیولوژیست · MRI و CT', shift: true }, { name: 'دکتر نگار کریمی', spec: 'رادیولوژیست · سونوگرافی', shift: false }]" :key="doctor.name" :class="i === 0 ? 'border-dominant-500' : 'border-transparent'" class="flex items-center gap-3 rounded-2xl bg-white border-2 p-4">
<span class="w-11 h-11 rounded-full bg-dark text-white flex items-center justify-center"><i class="fi fi-rr-user-md leading-none"></i></span>
<div class="flex-1">
<p class="text-xs font-bold text-dark">{{ doctor.name }}</p>
<p class="text-[10px] text-dark/50">{{ doctor.spec }}</p>
</div>
<span v-if="doctor.shift" class="flex items-center gap-1.5 rounded-full bg-complement-50 px-2.5 py-1 text-[10px] font-semibold text-complement-700"><span class="w-1.5 h-1.5 rounded-full bg-success"></span>در شیفت</span>
<span v-else class="text-[10px] text-dark/40">شیفت بعدی: ۱۸:۰۰</span>
</div>
<p class="text-center text-[11px] text-dark/50">یا <span class="font-semibold text-dominant-500">انتخاب خودکار</span> پزشکِ در شیفت</p>
</div>
<div v-else class="mx-auto w-full max-w-[280px] rounded-[30px] bg-dark p-3">
<div class="rounded-[22px] bg-white p-4 space-y-3 min-h-[230px]">
<p class="text-center text-[10px] text-dark/40">رادیکال</p>
<div class="landing-bubble rounded-2xl rounded-tr-md bg-light px-3 py-2.5 text-[11px] leading-5 text-dark">درخواست شما ثبت شد. شماره پرونده: ۲۶۴۹۵۸</div>
<div class="landing-bubble rounded-2xl rounded-tr-md bg-light px-3 py-2.5 text-[11px] leading-5 text-dark">پرداخت فاکتور ۰۰۰۶۹ با موفقیت انجام شد. پرونده به پزشک ارجاع شد.</div>
<div class="landing-bubble rounded-2xl rounded-tr-md bg-dominant-500 px-3 py-2.5 text-[11px] leading-5 text-white">گزارش پرونده ۲۶۴۹۵۸ آماده شد. برای مشاهده به پنل مراجعه کنید.</div>
</div>
</div>
</div>
</li>
</ol>
</div>
</section>
</template>
@@ -176,12 +176,12 @@ const updateExpandedKeys = () => {
<template>
<div v-if="globalStore.sidebarOpen"
class="fixed inset-0 bg-black/10 backdrop-blur-sm bg-opacity-50 z-10 sm:hidden"
class="fixed inset-0 bg-black/10 backdrop-blur-sm bg-opacity-50 z-10 lg:hidden"
@click="globalStore.sidebarOpen = false">
</div>
<aside class="shadow-client-gray/5 w-[16rem] bg-white
py-4 pt-0 pb-8 fixed h-full top-0 z-30 inset-y-0 right-0 sm:inset-auto transition-transform duration-300"
:class="[globalStore.sidebarOpen ? 'translate-x-0' : 'translate-x-full sm:translate-x-0']">
py-4 pt-0 pb-8 fixed h-full top-0 z-30 inset-y-0 right-0 lg:inset-auto transition-transform duration-300"
:class="[globalStore.sidebarOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0']">
<figure class="w-[16rem] h-[5rem] flex justify-center">
<img class="!w-full h-full object-contain" src="@/images/SidebarPanelLogo.png" alt="">
</figure>
@@ -123,6 +123,7 @@ const getColumnStyle = (column) => {
</template>
</Toolbar>
</div>
<div class="table-generic-scroll">
<DataTable
:value="data"
dataKey="id"
@@ -159,6 +160,7 @@ const getColumnStyle = (column) => {
</template>
</Column>
</DataTable>
</div>
<Pagination
v-if="pagination && pagination.total"
@@ -172,8 +174,3 @@ const getColumnStyle = (column) => {
</div>
</template>
<style scoped>
.table-generic {
width: 100%;
}
</style>
@@ -0,0 +1,27 @@
import { onBeforeUnmount, onMounted } from 'vue'
export function useScrollReveal(rootRef, selector = '.landing-reveal') {
let observer = null
onMounted(() => {
const elements = rootRef.value?.querySelectorAll(selector) ?? []
if (!('IntersectionObserver' in window)) {
elements.forEach((element) => element.classList.add('is-visible'))
return
}
observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible')
observer.unobserve(entry.target)
}
})
}, { threshold: 0.05 })
elements.forEach((element) => observer.observe(element))
})
onBeforeUnmount(() => observer?.disconnect())
}
+2 -2
View File
@@ -19,8 +19,8 @@ onMounted(() => {
<header-component />
<div class="!bg-white">
<section class="">
<main class="overflow-hidden !ps-[16rem] max-sm:!ps-4 pt-20 min-h-[calc(100svh)] p-4 grid grid-cols-1">
<section class="!rounded-2xl border-[1px] border-dominant-50 !bg-dominant-50/50 content-start p-4 grid grid-cols-1">
<main class="overflow-hidden lg:!ps-[16rem] pt-20 min-h-[calc(100svh)] p-4 grid grid-cols-1">
<section class="!rounded-2xl border-[1px] border-dominant-50 !bg-dominant-50/50 content-start p-3 sm:p-4 grid grid-cols-1 min-w-0">
<div v-if="$page.props.flash.success" class="w-full mb-4">
<Message size="small" closable class="!text-xs py-1.5" severity="success">{{$page.props.flash.success}}</Message>
</div>