feat: add landing page components including FAQ, features, footer, header, hero, modalities, steps, and section title
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
.git
|
||||
node_modules
|
||||
vendor
|
||||
public/build
|
||||
public/hot
|
||||
public/storage
|
||||
storage/logs/*
|
||||
storage/framework/cache/*
|
||||
storage/framework/sessions/*
|
||||
storage/framework/views/*
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
graphify-out
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Enums\UserEnum;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Horizon\HorizonApplicationServiceProvider;
|
||||
|
||||
class HorizonServiceProvider extends HorizonApplicationServiceProvider
|
||||
{
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewHorizon', function ($user = null) {
|
||||
return $user?->level === UserEnum::Admin->value;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\HorizonServiceProvider::class,
|
||||
];
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"hekmatinasser/verta": "^8.5",
|
||||
"inertiajs/inertia-laravel": "^2.0",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/horizon": "^5.49",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"spatie/laravel-permission": "^6.17",
|
||||
|
||||
Generated
+137
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "7aaff316edab008067e96caa26142a24",
|
||||
"content-hash": "2f1cb367cfe5d850b1483797ac0810ee",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -1741,6 +1741,86 @@
|
||||
},
|
||||
"time": "2025-04-08T19:58:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/horizon",
|
||||
"version": "v5.49.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/horizon.git",
|
||||
"reference": "6dce8a97b426be5305adad537ad4b6a99de61b31"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/horizon/zipball/6dce8a97b426be5305adad537ad4b6a99de61b31",
|
||||
"reference": "6dce8a97b426be5305adad537ad4b6a99de61b31",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-posix": "*",
|
||||
"illuminate/contracts": "^9.21|^10.0|^11.0|^12.0|^13.0",
|
||||
"illuminate/queue": "^9.21|^10.0|^11.0|^12.0|^13.0",
|
||||
"illuminate/support": "^9.21|^10.0|^11.0|^12.0|^13.0",
|
||||
"laravel/sentinel": "^1.0",
|
||||
"nesbot/carbon": "^2.17|^3.0",
|
||||
"php": "^8.0",
|
||||
"ramsey/uuid": "^4.0",
|
||||
"symfony/console": "^6.0|^7.0|^8.0",
|
||||
"symfony/error-handler": "^6.0|^7.0|^8.0",
|
||||
"symfony/polyfill-php83": "^1.28",
|
||||
"symfony/process": "^6.0|^7.0|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"orchestra/testbench": "^7.56|^8.37|^9.16|^10.9|^11.0",
|
||||
"phpstan/phpstan": "^1.10|^2.0",
|
||||
"predis/predis": "^1.1|^2.0|^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-redis": "Required to use the Redis PHP driver.",
|
||||
"predis/predis": "Required when not using the Redis PHP driver (^1.1|^2.0|^3.0)."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Horizon": "Laravel\\Horizon\\Horizon"
|
||||
},
|
||||
"providers": [
|
||||
"Laravel\\Horizon\\HorizonServiceProvider"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "6.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Horizon\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Dashboard and code-driven configuration for Laravel queues.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"queue"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/horizon/issues",
|
||||
"source": "https://github.com/laravel/horizon/tree/v5.49.0"
|
||||
},
|
||||
"time": "2026-09-07T12:00:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
"version": "v0.3.5",
|
||||
@@ -1800,6 +1880,62 @@
|
||||
},
|
||||
"time": "2025-02-11T13:34:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sentinel",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sentinel.git",
|
||||
"reference": "972d9885d9d14312a118e9565c4e6ecc5e751ea1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sentinel/zipball/972d9885d9d14312a118e9565c4e6ecc5e751ea1",
|
||||
"reference": "972d9885d9d14312a118e9565c4e6ecc5e751ea1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"illuminate/container": "^8.37|^9.0|^10.0|^11.0|^12.0|^13.0",
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.27",
|
||||
"orchestra/testbench": "^6.47.1|^7.56|^8.37|^9.16|^10.9|^11.0",
|
||||
"phpstan/phpstan": "^2.1.33"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Sentinel\\SentinelServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Sentinel\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
},
|
||||
{
|
||||
"name": "Mior Muhammad Zaki",
|
||||
"email": "mior@laravel.com"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/laravel/sentinel/tree/v1.1.0"
|
||||
},
|
||||
"time": "2026-03-24T14:03:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v2.0.4",
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This name appears in notifications and in the Horizon UI. Unique names
|
||||
| can be useful while running multiple instances of Horizon within an
|
||||
| application, allowing you to identify the Horizon you're viewing.
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('HORIZON_NAME'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the subdomain where Horizon will be accessible from. If this
|
||||
| setting is null, Horizon will reside under the same domain as the
|
||||
| application. Otherwise, this value will serve as the subdomain.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('HORIZON_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the URI path where Horizon will be accessible from. Feel free
|
||||
| to change this path to anything you like. Note that the URI will not
|
||||
| affect the paths of its internal API that aren't exposed to users.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('HORIZON_PATH', 'admin/horizon'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Redis Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the name of the Redis connection where Horizon will store the
|
||||
| meta information required for it to function. It includes the list
|
||||
| of supervisors, failed jobs, job metrics, and other information.
|
||||
|
|
||||
*/
|
||||
|
||||
'use' => 'default',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Redis Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This prefix will be used when storing all Horizon data in Redis. You
|
||||
| may modify the prefix when you are running multiple installations
|
||||
| of Horizon on the same server so that they don't have problems.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env(
|
||||
'HORIZON_PREFIX',
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Horizon Route Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These middleware will get attached onto each Horizon route, giving you
|
||||
| the chance to add your own middleware to this list or change any of
|
||||
| the existing middleware. Or, you can simply stick with this list.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => ['web', 'admin.access'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Wait Time Thresholds
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to configure when the LongWaitDetected event
|
||||
| will be fired. Every connection / queue combination may have its
|
||||
| own, unique threshold (in seconds) before this event is fired.
|
||||
|
|
||||
*/
|
||||
|
||||
'waits' => [
|
||||
'redis:default' => 60,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Job Trimming Times
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you can configure for how long (in minutes) you desire Horizon to
|
||||
| persist the recent and failed jobs. Typically, recent jobs are kept
|
||||
| for one hour while all failed jobs are stored for an entire week.
|
||||
|
|
||||
*/
|
||||
|
||||
'trim' => [
|
||||
'recent' => 60,
|
||||
'pending' => 60,
|
||||
'completed' => 60,
|
||||
'recent_failed' => 10080,
|
||||
'failed' => 10080,
|
||||
'monitored' => 10080,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Silenced Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Silencing a job will instruct Horizon to not place the job in the list
|
||||
| of completed jobs within the Horizon dashboard. This setting may be
|
||||
| used to fully remove any noisy jobs from the completed jobs list.
|
||||
|
|
||||
*/
|
||||
|
||||
'silenced' => [
|
||||
// App\Jobs\ExampleJob::class,
|
||||
],
|
||||
|
||||
'silenced_tags' => [
|
||||
// 'notifications',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Metrics
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you can configure how many snapshots should be kept to display in
|
||||
| the metrics graph. This will get used in combination with Horizon's
|
||||
| `horizon:snapshot` schedule to define how long to retain metrics.
|
||||
|
|
||||
*/
|
||||
|
||||
'metrics' => [
|
||||
'trim_snapshots' => [
|
||||
'job' => 24,
|
||||
'queue' => 24,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fast Termination
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When this option is enabled, Horizon's "terminate" command will not
|
||||
| wait on all of the workers to terminate unless the --wait option
|
||||
| is provided. Fast termination can shorten deployment delay by
|
||||
| allowing a new instance of Horizon to start while the last
|
||||
| instance will continue to terminate each of its workers.
|
||||
|
|
||||
*/
|
||||
|
||||
'fast_termination' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Memory Limit (MB)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value describes the maximum amount of memory the Horizon master
|
||||
| supervisor may consume before it is terminated and restarted. For
|
||||
| configuring these limits on your workers, see the next section.
|
||||
|
|
||||
*/
|
||||
|
||||
'memory_limit' => 64,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Worker Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the queue worker settings used by your application
|
||||
| in all environments. These supervisors and settings handle all your
|
||||
| queued jobs and will be provisioned by Horizon during deployment.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'supervisor-1' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['default'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 128,
|
||||
'tries' => 1,
|
||||
'timeout' => 60,
|
||||
'nice' => 0,
|
||||
],
|
||||
],
|
||||
|
||||
'environments' => [
|
||||
'production' => [
|
||||
'supervisor-1' => [
|
||||
'maxProcesses' => 10,
|
||||
'balanceMaxShift' => 1,
|
||||
'balanceCooldown' => 3,
|
||||
],
|
||||
],
|
||||
|
||||
'local' => [
|
||||
'supervisor-1' => [
|
||||
'maxProcesses' => 3,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| File Watcher Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following list of directories and files will be watched when using
|
||||
| the `horizon:listen` command. Whenever any directories or files are
|
||||
| changed, Horizon will automatically restart to apply all changes.
|
||||
|
|
||||
*/
|
||||
|
||||
'watch' => [
|
||||
'app',
|
||||
'bootstrap',
|
||||
'config/**/*.php',
|
||||
'database/**/*.php',
|
||||
'public/**/*.php',
|
||||
'resources/**/*.php',
|
||||
'routes',
|
||||
'composer.lock',
|
||||
'composer.json',
|
||||
'.env',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,87 @@
|
||||
x-app: &app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/php/Dockerfile
|
||||
image: radical-app
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
environment: &app-env
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_DATABASE: ${DB_DATABASE:-radical}
|
||||
DB_USERNAME: ${DB_USERNAME:-radical}
|
||||
DB_PASSWORD: ${DB_PASSWORD:-secret}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
QUEUE_CONNECTION: redis
|
||||
CACHE_STORE: redis
|
||||
SESSION_DRIVER: redis
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
services:
|
||||
app:
|
||||
<<: *app
|
||||
|
||||
horizon:
|
||||
<<: *app
|
||||
command: php artisan horizon
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_started
|
||||
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:80"
|
||||
volumes:
|
||||
- .:/var/www/html:ro
|
||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- app
|
||||
|
||||
node:
|
||||
image: node:24-alpine
|
||||
working_dir: /var/www/html
|
||||
command: sh -c "npm install && npm run dev -- --host 0.0.0.0"
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
- node-modules:/var/www/html/node_modules
|
||||
ports:
|
||||
- "5173:5173"
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_DATABASE:-radical}
|
||||
POSTGRES_USER: ${DB_USERNAME:-radical}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
|
||||
ports:
|
||||
- "${DB_FORWARD_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-radical} -d ${DB_DATABASE:-radical}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
node-modules:
|
||||
@@ -0,0 +1,22 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /var/www/html/public;
|
||||
index index.php;
|
||||
client_max_body_size 64M;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass app:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM php:8.4-fpm-alpine
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash curl git postgresql-dev libzip-dev icu-dev libpng-dev libjpeg-turbo-dev freetype-dev oniguruma-dev linux-headers $PHPIZE_DEPS \
|
||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install -j"$(nproc)" pdo_pgsql pgsql gd zip intl bcmath exif pcntl opcache \
|
||||
&& pecl install redis \
|
||||
&& docker-php-ext-enable redis \
|
||||
&& apk del $PHPIZE_DEPS
|
||||
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
COPY docker/php/php.ini /usr/local/etc/php/conf.d/app.ini
|
||||
COPY docker/php/entrypoint.sh /usr/local/bin/entrypoint
|
||||
RUN chmod +x /usr/local/bin/entrypoint
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
ENTRYPOINT ["entrypoint"]
|
||||
CMD ["php-fpm"]
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "$1" = "php-fpm" ]; then
|
||||
[ -d vendor ] || composer install --no-interaction --prefer-dist
|
||||
[ -f .env ] || cp .env.example .env
|
||||
grep -q '^APP_KEY=.\+' .env || php artisan key:generate --no-interaction
|
||||
|
||||
until php -r "new PDO('pgsql:host=${DB_HOST};port=${DB_PORT};dbname=${DB_DATABASE}', '${DB_USERNAME}', '${DB_PASSWORD}');" >/dev/null 2>&1; do
|
||||
echo "waiting for postgres..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
php artisan migrate --force --no-interaction
|
||||
php artisan storage:link --no-interaction || true
|
||||
chown -R www-data:www-data storage bootstrap/cache
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
@@ -0,0 +1,6 @@
|
||||
upload_max_filesize = 64M
|
||||
post_max_size = 64M
|
||||
memory_limit = 512M
|
||||
max_execution_time = 120
|
||||
opcache.enable = 1
|
||||
opcache.validate_timestamps = 1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 419 KiB |
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -81,3 +81,10 @@
|
||||
background: #576989 !important;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.fast-request-dialog {
|
||||
width: calc(100vw - 1.5rem) !important;
|
||||
max-width: 688px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
<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">
|
||||
<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="!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"
|
||||
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>
|
||||
<div class="grid grid-cols-1 h-[4rem] content-center justify-start items-center">
|
||||
<div class="flex-1 min-w-0 max-w-[30rem]">
|
||||
<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]"
|
||||
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]"
|
||||
customClass="placeholder:!text-dominant-300 !border-dominant-50 !font-normal !text-[11px] truncate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<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())
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -32,6 +32,13 @@ export default defineConfig({
|
||||
'@': '/public',
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
hmr: {
|
||||
host: 'localhost',
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
'@fullcalendar/core',
|
||||
|
||||
Reference in New Issue
Block a user