Mockups for Designs
Build And Push Image / docker (push) Successful in 1m55s Details

This commit is contained in:
Matt 2025-09-03 21:04:44 +02:00
parent e75de8b9f4
commit 4d24315103
12 changed files with 5425 additions and 0 deletions

1
.serena/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/cache

View File

@ -0,0 +1,215 @@
<template>
<div
:class="[
'neumorphic-card',
sizeClasses,
elevationClasses,
{
'cursor-pointer': clickable,
'transition-all duration-300': animated
}
]"
@click="handleClick"
>
<div v-if="$slots.header || title" class="neumorphic-card__header">
<slot name="header">
<h3 v-if="title" class="neumorphic-card__title">{{ title }}</h3>
<p v-if="subtitle" class="neumorphic-card__subtitle">{{ subtitle }}</p>
</slot>
</div>
<div class="neumorphic-card__content">
<slot></slot>
</div>
<div v-if="$slots.footer" class="neumorphic-card__footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
title?: string;
subtitle?: string;
size?: 'sm' | 'md' | 'lg' | 'xl';
elevation?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
clickable?: boolean;
animated?: boolean;
pressed?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
size: 'md',
elevation: 'md',
clickable: false,
animated: true,
pressed: false
});
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
const sizeClasses = computed(() => {
const sizes = {
sm: 'p-4',
md: 'p-6',
lg: 'p-8',
xl: 'p-10'
};
return sizes[props.size];
});
const elevationClasses = computed(() => {
if (props.pressed) return 'neumorphic-pressed';
const elevations = {
none: '',
sm: 'neumorphic-elevation-sm',
md: 'neumorphic-elevation-md',
lg: 'neumorphic-elevation-lg',
xl: 'neumorphic-elevation-xl'
};
return elevations[props.elevation];
});
const handleClick = (event: MouseEvent) => {
if (props.clickable) {
emit('click', event);
}
};
</script>
<style lang="scss" scoped>
@import '../../styles/neumorphic-system.scss';
.neumorphic-card {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
border-radius: $radius-xl;
position: relative;
overflow: hidden;
// Base elevation styles
&.neumorphic-elevation-sm {
box-shadow: $shadow-soft-sm;
}
&.neumorphic-elevation-md {
box-shadow: $shadow-soft-md;
}
&.neumorphic-elevation-lg {
box-shadow: $shadow-soft-lg;
}
&.neumorphic-elevation-xl {
box-shadow: $shadow-soft-xl;
}
&.neumorphic-pressed {
box-shadow: $shadow-inset-md;
background: linear-gradient(145deg, #e6e6e6, #ffffff);
}
// Hover effects for clickable cards
&.cursor-pointer {
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-soft-lg;
}
&:active {
transform: translateY(0);
box-shadow: $shadow-inset-sm;
}
}
// Header section
&__header {
padding-bottom: $space-4;
border-bottom: 1px solid rgba($neutral-300, 0.5);
margin-bottom: $space-4;
}
&__title {
font-family: $font-heading;
font-size: $text-xl;
font-weight: $font-semibold;
color: $neutral-800;
margin: 0;
line-height: $leading-tight;
}
&__subtitle {
font-family: $font-body;
font-size: $text-sm;
color: $neutral-600;
margin-top: $space-2;
line-height: $leading-normal;
}
// Content section
&__content {
font-family: $font-body;
color: $neutral-700;
line-height: $leading-relaxed;
}
// Footer section
&__footer {
padding-top: $space-4;
border-top: 1px solid rgba($neutral-300, 0.5);
margin-top: $space-4;
}
// Dark mode support
@include dark-mode {
background: linear-gradient(145deg, $neutral-800, $neutral-700);
&.neumorphic-elevation-sm {
box-shadow: $shadow-dark-soft-sm;
}
&.neumorphic-elevation-md {
box-shadow: $shadow-dark-soft-md;
}
&.neumorphic-elevation-lg {
box-shadow: $shadow-dark-soft-lg;
}
&.neumorphic-pressed {
box-shadow: $shadow-dark-inset-md;
background: linear-gradient(145deg, $neutral-900, $neutral-800);
}
.neumorphic-card__title {
color: $neutral-100;
}
.neumorphic-card__subtitle {
color: $neutral-400;
}
.neumorphic-card__content {
color: $neutral-300;
}
.neumorphic-card__header,
.neumorphic-card__footer {
border-color: rgba($neutral-600, 0.5);
}
}
// Responsive adjustments
@include responsive('sm') {
&.p-4 { padding: $space-5; }
&.p-6 { padding: $space-8; }
&.p-8 { padding: $space-10; }
&.p-10 { padding: $space-12; }
}
}
</style>

View File

@ -0,0 +1,315 @@
<template>
<button
:class="[
'professional-button',
variantClasses,
sizeClasses,
{
'professional-button--loading': loading,
'professional-button--disabled': disabled || loading,
'professional-button--block': block,
'professional-button--icon-only': !$slots.default && icon
}
]"
:disabled="disabled || loading"
@click="handleClick"
>
<span v-if="loading" class="professional-button__spinner">
<svg class="animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
<span v-if="icon && !loading" class="professional-button__icon">
<component :is="icon" />
</span>
<span v-if="$slots.default" class="professional-button__content">
<slot></slot>
</span>
<span v-if="endIcon && !loading" class="professional-button__end-icon">
<component :is="endIcon" />
</span>
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { Component } from 'vue';
interface Props {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
disabled?: boolean;
block?: boolean;
icon?: Component;
endIcon?: Component;
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
loading: false,
disabled: false,
block: false
});
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
const variantClasses = computed(() => {
const variants = {
primary: 'professional-button--primary',
secondary: 'professional-button--secondary',
outline: 'professional-button--outline',
ghost: 'professional-button--ghost',
danger: 'professional-button--danger'
};
return variants[props.variant];
});
const sizeClasses = computed(() => {
const sizes = {
sm: 'professional-button--sm',
md: 'professional-button--md',
lg: 'professional-button--lg'
};
return sizes[props.size];
});
const handleClick = (event: MouseEvent) => {
if (!props.disabled && !props.loading) {
emit('click', event);
}
};
</script>
<style lang="scss" scoped>
@import '../../styles/neumorphic-system.scss';
.professional-button {
display: inline-flex;
align-items: center;
justify-center: center;
gap: $space-2;
border: none;
border-radius: $radius-lg;
font-family: $font-body;
font-weight: $font-medium;
cursor: pointer;
transition: all $transition-base $ease-in-out-soft;
position: relative;
overflow: hidden;
// Size variants
&--sm {
padding: $space-2 $space-4;
font-size: $text-sm;
min-height: $button-height-sm;
&.professional-button--icon-only {
width: $button-height-sm;
padding: $space-2;
}
}
&--md {
padding: $space-3 $space-5;
font-size: $text-base;
min-height: $button-height-md;
&.professional-button--icon-only {
width: $button-height-md;
padding: $space-3;
}
}
&--lg {
padding: $space-4 $space-6;
font-size: $text-lg;
min-height: $button-height-lg;
&.professional-button--icon-only {
width: $button-height-lg;
padding: $space-4;
}
}
// Variant styles
&--primary {
background: linear-gradient(135deg, $primary-500, $primary-600);
color: white;
box-shadow: $shadow-soft-sm;
&:hover:not(.professional-button--disabled) {
background: linear-gradient(135deg, $primary-600, $primary-700);
box-shadow: $shadow-soft-md;
transform: translateY(-1px);
}
&:active:not(.professional-button--disabled) {
box-shadow: $shadow-inset-sm;
transform: translateY(0);
}
}
&--secondary {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
color: $neutral-800;
box-shadow: $shadow-soft-sm;
&:hover:not(.professional-button--disabled) {
box-shadow: $shadow-soft-md;
transform: translateY(-1px);
}
&:active:not(.professional-button--disabled) {
box-shadow: $shadow-inset-sm;
transform: translateY(0);
}
}
&--outline {
background: transparent;
color: $primary-600;
border: 2px solid $primary-200;
box-shadow: none;
&:hover:not(.professional-button--disabled) {
background: rgba($primary-500, 0.05);
border-color: $primary-300;
}
&:active:not(.professional-button--disabled) {
background: rgba($primary-500, 0.1);
}
}
&--ghost {
background: transparent;
color: $neutral-700;
box-shadow: none;
&:hover:not(.professional-button--disabled) {
background: rgba($neutral-500, 0.08);
}
&:active:not(.professional-button--disabled) {
background: rgba($neutral-500, 0.12);
}
}
&--danger {
background: linear-gradient(135deg, $error-500, #dc2626);
color: white;
box-shadow: $shadow-soft-sm;
&:hover:not(.professional-button--disabled) {
background: linear-gradient(135deg, #dc2626, #b91c1c);
box-shadow: $shadow-soft-md;
transform: translateY(-1px);
}
&:active:not(.professional-button--disabled) {
box-shadow: $shadow-inset-sm;
transform: translateY(0);
}
}
// States
&--block {
width: 100%;
}
&--disabled {
opacity: 0.5;
cursor: not-allowed;
}
&--loading {
color: transparent;
}
// Icons
&__spinner {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 1.25rem;
height: 1.25rem;
color: inherit;
svg {
width: 100%;
height: 100%;
}
}
&__icon,
&__end-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.25em;
height: 1.25em;
flex-shrink: 0;
}
&__content {
flex: 1;
text-align: center;
}
// Focus state
&:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba($primary-500, 0.2);
}
// Dark mode support
@include dark-mode {
&--secondary {
background: linear-gradient(145deg, $neutral-700, $neutral-800);
color: $neutral-100;
&:hover:not(.professional-button--disabled) {
background: linear-gradient(145deg, $neutral-600, $neutral-700);
}
}
&--ghost {
color: $neutral-300;
&:hover:not(.professional-button--disabled) {
background: rgba($neutral-400, 0.1);
}
}
&--outline {
color: $primary-400;
border-color: $primary-800;
&:hover:not(.professional-button--disabled) {
background: rgba($primary-400, 0.1);
border-color: $primary-700;
}
}
}
}
// Animation
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1s linear infinite;
}
</style>

View File

@ -0,0 +1,320 @@
<template>
<NeumorphicCard :elevation="elevation" :animated="animated" class="stat-card">
<div class="stat-card__content">
<div class="stat-card__header">
<div class="stat-card__icon-wrapper" :class="`stat-card__icon-wrapper--${variant}`">
<component :is="icon" v-if="icon" class="stat-card__icon" />
<div v-else class="stat-card__icon-placeholder">
<svg fill="currentColor" viewBox="0 0 20 20">
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" />
</svg>
</div>
</div>
<div class="stat-card__trend" v-if="trend">
<span :class="['stat-card__trend-badge', trendClass]">
<svg v-if="trend > 0" class="stat-card__trend-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
</svg>
<svg v-else class="stat-card__trend-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6" />
</svg>
{{ Math.abs(trend) }}%
</span>
</div>
</div>
<div class="stat-card__body">
<h3 class="stat-card__label">{{ label }}</h3>
<div class="stat-card__value-wrapper">
<span v-if="prefix" class="stat-card__prefix">{{ prefix }}</span>
<span class="stat-card__value">{{ formattedValue }}</span>
<span v-if="suffix" class="stat-card__suffix">{{ suffix }}</span>
</div>
<p v-if="description" class="stat-card__description">{{ description }}</p>
</div>
<div v-if="showProgress" class="stat-card__progress">
<div class="stat-card__progress-bar">
<div
class="stat-card__progress-fill"
:class="`stat-card__progress-fill--${variant}`"
:style="{ width: `${progress}%` }"
></div>
</div>
<span class="stat-card__progress-text">{{ progress }}% of target</span>
</div>
<div v-if="$slots.footer" class="stat-card__footer">
<slot name="footer"></slot>
</div>
</div>
</NeumorphicCard>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { Component } from 'vue';
import NeumorphicCard from './NeumorphicCard.vue';
interface Props {
label: string;
value: number | string;
trend?: number;
prefix?: string;
suffix?: string;
description?: string;
variant?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
icon?: Component;
elevation?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
animated?: boolean;
showProgress?: boolean;
progress?: number;
format?: 'number' | 'currency' | 'percentage';
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
elevation: 'md',
animated: true,
showProgress: false,
progress: 0,
format: 'number'
});
const formattedValue = computed(() => {
const val = props.value;
if (typeof val === 'string') return val;
switch (props.format) {
case 'currency':
return val.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
case 'percentage':
return val.toFixed(1);
default:
return val.toLocaleString('en-US');
}
});
const trendClass = computed(() => {
if (!props.trend) return '';
return props.trend > 0 ? 'stat-card__trend-badge--up' : 'stat-card__trend-badge--down';
});
</script>
<style lang="scss" scoped>
@import '../../styles/neumorphic-system.scss';
.stat-card {
height: 100%;
&__content {
display: flex;
flex-direction: column;
gap: $space-4;
}
&__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
&__icon-wrapper {
width: 48px;
height: 48px;
border-radius: $radius-lg;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $shadow-soft-sm;
transition: all $transition-base;
&--primary {
background: linear-gradient(135deg, rgba($primary-500, 0.1), rgba($primary-600, 0.15));
color: $primary-600;
}
&--success {
background: linear-gradient(135deg, rgba($success-500, 0.1), rgba($success-500, 0.15));
color: $success-500;
}
&--warning {
background: linear-gradient(135deg, rgba($warning-500, 0.1), rgba($warning-500, 0.15));
color: $warning-500;
}
&--danger {
background: linear-gradient(135deg, rgba($error-500, 0.1), rgba($error-500, 0.15));
color: $error-500;
}
&--info {
background: linear-gradient(135deg, rgba($info-500, 0.1), rgba($info-500, 0.15));
color: $info-500;
}
}
&__icon,
&__icon-placeholder {
width: 24px;
height: 24px;
}
&__trend {
display: flex;
align-items: center;
}
&__trend-badge {
display: inline-flex;
align-items: center;
gap: $space-1;
padding: $space-1 $space-2;
border-radius: $radius-full;
font-size: $text-sm;
font-weight: $font-semibold;
&--up {
background: linear-gradient(135deg, rgba($success-500, 0.1), rgba($success-500, 0.15));
color: $success-500;
}
&--down {
background: linear-gradient(135deg, rgba($error-500, 0.1), rgba($error-500, 0.15));
color: $error-500;
}
}
&__trend-icon {
width: 14px;
height: 14px;
}
&__body {
flex: 1;
}
&__label {
font-size: $text-sm;
font-weight: $font-medium;
color: $neutral-600;
margin-bottom: $space-2;
text-transform: uppercase;
letter-spacing: 0.025em;
}
&__value-wrapper {
display: flex;
align-items: baseline;
gap: $space-1;
margin-bottom: $space-2;
}
&__prefix,
&__suffix {
font-size: $text-lg;
color: $neutral-500;
}
&__value {
font-family: $font-heading;
font-size: $text-3xl;
font-weight: $font-bold;
color: $neutral-800;
line-height: 1;
}
&__description {
font-size: $text-sm;
color: $neutral-600;
line-height: $leading-normal;
}
&__progress {
margin-top: auto;
}
&__progress-bar {
width: 100%;
height: 8px;
background: rgba($neutral-300, 0.3);
border-radius: $radius-full;
overflow: hidden;
box-shadow: $shadow-inset-sm;
margin-bottom: $space-2;
}
&__progress-fill {
height: 100%;
border-radius: $radius-full;
transition: width $transition-slow $ease-out-soft;
&--primary {
background: linear-gradient(90deg, $primary-500, $primary-600);
}
&--success {
background: linear-gradient(90deg, $success-500, #059669);
}
&--warning {
background: linear-gradient(90deg, $warning-500, #D97706);
}
&--danger {
background: linear-gradient(90deg, $error-500, #DC2626);
}
&--info {
background: linear-gradient(90deg, $info-500, #2563EB);
}
}
&__progress-text {
font-size: $text-xs;
color: $neutral-500;
}
&__footer {
padding-top: $space-3;
border-top: 1px solid rgba($neutral-300, 0.3);
}
// Hover effect
&:hover {
.stat-card__icon-wrapper {
transform: scale(1.05);
box-shadow: $shadow-soft-md;
}
}
// Dark mode
@include dark-mode {
.stat-card__label {
color: $neutral-400;
}
.stat-card__value {
color: $neutral-100;
}
.stat-card__description {
color: $neutral-400;
}
.stat-card__prefix,
.stat-card__suffix {
color: $neutral-500;
}
.stat-card__progress-bar {
background: rgba($neutral-600, 0.3);
}
.stat-card__progress-text {
color: $neutral-500;
}
}
}
</style>

315
design-mockups/index.vue Normal file
View File

@ -0,0 +1,315 @@
<template>
<div class="mockup-index">
<div class="container">
<header class="header">
<img src="/MONACOUSA-Flags_376x376.png" alt="MonacoUSA" class="logo" />
<h1 class="title">MonacoUSA Portal Design Mockups</h1>
<p class="subtitle">Professional Neumorphic Design System</p>
</header>
<section class="mockup-grid">
<NuxtLink to="/design-mockups/pages/auth/ProfessionalLogin" class="mockup-card">
<div class="card-icon card-icon--auth">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
</svg>
</div>
<h2 class="card-title">Login Page</h2>
<p class="card-description">Professional split-screen login with branding section and neumorphic form</p>
<span class="card-link">View Mockup </span>
</NuxtLink>
<NuxtLink to="/design-mockups/pages/admin/ProfessionalAdminDashboard" class="mockup-card">
<div class="card-icon card-icon--admin">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<h2 class="card-title">Admin Dashboard</h2>
<p class="card-description">Complete admin interface with sidebar navigation, stats, charts, and data tables</p>
<span class="card-link">View Mockup </span>
</NuxtLink>
<NuxtLink to="/design-mockups/pages/board/ProfessionalBoardDashboard" class="mockup-card">
<div class="card-icon card-icon--board">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
<h2 class="card-title">Board Dashboard</h2>
<p class="card-description">Strategic insights, KPIs, governance features, and executive metrics</p>
<span class="card-link">View Mockup </span>
</NuxtLink>
<NuxtLink to="/design-mockups/pages/member/ProfessionalMemberDashboard" class="mockup-card">
<div class="card-icon card-icon--member">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</div>
<h2 class="card-title">Member Dashboard</h2>
<p class="card-description">Member portal with events, resources, community features, and benefits</p>
<span class="card-link">View Mockup </span>
</NuxtLink>
</section>
<section class="components-section">
<h2 class="section-title">Core Components</h2>
<div class="component-list">
<div class="component-item">
<span class="component-name">NeumorphicCard</span>
<span class="component-status"> Complete</span>
</div>
<div class="component-item">
<span class="component-name">ProfessionalButton</span>
<span class="component-status"> Complete</span>
</div>
<div class="component-item">
<span class="component-name">StatCard</span>
<span class="component-status"> Complete</span>
</div>
</div>
</section>
<footer class="footer">
<p>Neumorphic Design System Professional & Inviting Built with Vue 3 & Nuxt 3</p>
</footer>
</div>
</div>
</template>
<script setup lang="ts">
// No additional logic needed for this index page
</script>
<style lang="scss" scoped>
// Import the neumorphic system
@import './styles/neumorphic-system.scss';
.mockup-index {
min-height: 100vh;
background: linear-gradient(135deg, $neutral-50 0%, $neutral-100 100%);
padding: $space-8 $space-6;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: $space-12;
.logo {
width: 80px;
height: 80px;
margin: 0 auto $space-4;
object-fit: contain;
}
.title {
font-family: $font-heading;
font-size: $text-4xl;
font-weight: $font-bold;
color: $neutral-800;
margin-bottom: $space-2;
}
.subtitle {
font-size: $text-lg;
color: $neutral-600;
}
}
.mockup-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: $space-6;
margin-bottom: $space-12;
}
.mockup-card {
display: block;
padding: $space-6;
background: linear-gradient(145deg, #ffffff, #f0f0f0);
border-radius: $radius-xl;
box-shadow: $shadow-soft-md;
text-decoration: none;
transition: all $transition-base;
&:hover {
transform: translateY(-4px);
box-shadow: $shadow-soft-lg;
.card-link {
color: $primary-700;
}
}
&:active {
transform: translateY(0);
box-shadow: $shadow-inset-sm;
}
.card-icon {
width: 60px;
height: 60px;
border-radius: $radius-lg;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: $space-4;
box-shadow: $shadow-soft-sm;
svg {
width: 28px;
height: 28px;
}
&--auth {
background: linear-gradient(135deg, rgba($primary-500, 0.1), rgba($primary-600, 0.15));
color: $primary-600;
}
&--admin {
background: linear-gradient(135deg, rgba($info-500, 0.1), rgba($info-500, 0.15));
color: $info-500;
}
&--board {
background: linear-gradient(135deg, rgba($success-500, 0.1), rgba($success-500, 0.15));
color: $success-500;
}
&--member {
background: linear-gradient(135deg, rgba($warning-500, 0.1), rgba($warning-500, 0.15));
color: $warning-500;
}
}
.card-title {
font-family: $font-heading;
font-size: $text-xl;
font-weight: $font-semibold;
color: $neutral-800;
margin-bottom: $space-2;
}
.card-description {
font-size: $text-sm;
color: $neutral-600;
line-height: $leading-relaxed;
margin-bottom: $space-4;
}
.card-link {
font-size: $text-sm;
font-weight: $font-medium;
color: $primary-600;
transition: color $transition-base;
}
}
.components-section {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
border-radius: $radius-xl;
padding: $space-8;
box-shadow: $shadow-soft-md;
margin-bottom: $space-8;
.section-title {
font-family: $font-heading;
font-size: $text-2xl;
font-weight: $font-semibold;
color: $neutral-800;
margin-bottom: $space-6;
}
.component-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: $space-4;
}
.component-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: $space-3 $space-4;
background: rgba($neutral-100, 0.5);
border-radius: $radius-lg;
.component-name {
font-family: $font-mono;
font-size: $text-sm;
color: $neutral-700;
}
.component-status {
font-size: $text-sm;
color: $success-500;
}
}
}
.footer {
text-align: center;
padding: $space-6;
p {
font-size: $text-sm;
color: $neutral-600;
}
}
// Dark mode support
@include dark-mode {
.mockup-index {
background: linear-gradient(135deg, $neutral-900 0%, $neutral-800 100%);
}
.mockup-card {
background: linear-gradient(145deg, $neutral-800, $neutral-700);
.card-title {
color: $neutral-100;
}
.card-description {
color: $neutral-400;
}
}
.components-section {
background: linear-gradient(145deg, $neutral-800, $neutral-700);
.section-title {
color: $neutral-100;
}
.component-item {
background: rgba($neutral-700, 0.5);
.component-name {
color: $neutral-300;
}
}
}
.header {
.title {
color: $neutral-100;
}
.subtitle {
color: $neutral-400;
}
}
.footer p {
color: $neutral-400;
}
}
</style>

View File

@ -0,0 +1,814 @@
<template>
<div class="admin-dashboard">
<!-- Sidebar Navigation -->
<aside class="sidebar" :class="{ 'sidebar--collapsed': isSidebarCollapsed }">
<div class="sidebar-header">
<div class="sidebar-logo">
<img src="/MONACOUSA-Flags_376x376.png" alt="MonacoUSA" />
<span v-if="!isSidebarCollapsed" class="sidebar-title">Admin Portal</span>
</div>
<button @click="isSidebarCollapsed = !isSidebarCollapsed" class="sidebar-toggle">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<nav class="sidebar-nav">
<a
v-for="item in navItems"
:key="item.id"
:class="['sidebar-item', { 'sidebar-item--active': activeNav === item.id }]"
@click="activeNav = item.id"
>
<span class="sidebar-item-icon">
<component :is="item.icon" />
</span>
<span v-if="!isSidebarCollapsed" class="sidebar-item-label">{{ item.label }}</span>
<span v-if="!isSidebarCollapsed && item.badge" class="sidebar-item-badge">{{ item.badge }}</span>
</a>
</nav>
<div class="sidebar-footer">
<div class="sidebar-user">
<div class="sidebar-user-avatar">
<img src="https://via.placeholder.com/40" alt="Admin" />
</div>
<div v-if="!isSidebarCollapsed" class="sidebar-user-info">
<div class="sidebar-user-name">John Admin</div>
<div class="sidebar-user-role">System Admin</div>
</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Top Bar -->
<header class="topbar">
<div class="topbar-left">
<h1 class="topbar-title">Dashboard Overview</h1>
<p class="topbar-subtitle">Welcome back, John. Here's what's happening today.</p>
</div>
<div class="topbar-right">
<button class="topbar-button">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
<button class="topbar-button topbar-button--notification">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
<span class="notification-badge">3</span>
</button>
<div class="topbar-divider"></div>
<button class="topbar-user">
<img src="https://via.placeholder.com/32" alt="User" class="topbar-user-avatar" />
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
</header>
<!-- Stats Grid -->
<div class="stats-grid">
<StatCard
v-for="stat in stats"
:key="stat.title"
:title="stat.title"
:value="stat.value"
:change="stat.change"
:trend="stat.trend"
:icon="stat.icon"
/>
</div>
<!-- Content Grid -->
<div class="content-grid">
<!-- Chart Card -->
<NeumorphicCard class="chart-card">
<template #header>
<div class="card-header">
<h2 class="card-title">Revenue Overview</h2>
<div class="card-actions">
<button class="card-action">Day</button>
<button class="card-action card-action--active">Week</button>
<button class="card-action">Month</button>
<button class="card-action">Year</button>
</div>
</div>
</template>
<div class="chart-container">
<canvas ref="chartCanvas"></canvas>
</div>
</NeumorphicCard>
<!-- Activity Feed -->
<NeumorphicCard class="activity-card">
<template #header>
<div class="card-header">
<h2 class="card-title">Recent Activity</h2>
<button class="card-link">View All</button>
</div>
</template>
<div class="activity-list">
<div v-for="activity in activities" :key="activity.id" class="activity-item">
<div class="activity-icon" :class="`activity-icon--${activity.type}`">
<component :is="activity.icon" />
</div>
<div class="activity-content">
<p class="activity-description">{{ activity.description }}</p>
<span class="activity-time">{{ activity.time }}</span>
</div>
</div>
</div>
</NeumorphicCard>
<!-- Members Table -->
<NeumorphicCard class="table-card">
<template #header>
<div class="card-header">
<h2 class="card-title">Recent Members</h2>
<ProfessionalButton variant="outline" size="sm">
View All Members
</ProfessionalButton>
</div>
</template>
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Member</th>
<th>Status</th>
<th>Joined</th>
<th>Last Active</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="member in recentMembers" :key="member.id">
<td>
<div class="member-cell">
<img :src="member.avatar" :alt="member.name" class="member-avatar" />
<div>
<div class="member-name">{{ member.name }}</div>
<div class="member-email">{{ member.email }}</div>
</div>
</div>
</td>
<td>
<span class="status-badge" :class="`status-badge--${member.status}`">
{{ member.status }}
</span>
</td>
<td>{{ member.joined }}</td>
<td>{{ member.lastActive }}</td>
<td>
<button class="table-action">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
</svg>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</NeumorphicCard>
</div>
</main>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import NeumorphicCard from '../../components/core/NeumorphicCard.vue';
import ProfessionalButton from '../../components/core/ProfessionalButton.vue';
import StatCard from '../../components/data/StatCard.vue';
import Chart from 'chart.js/auto';
// Icons (simplified for demo)
const HomeIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/></svg>' };
const UsersIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/></svg>' };
const CalendarIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/></svg>' };
const ChartIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/></svg>' };
// Data
const isSidebarCollapsed = ref(false);
const activeNav = ref('dashboard');
const chartCanvas = ref<HTMLCanvasElement | null>(null);
const navItems = [
{ id: 'dashboard', label: 'Dashboard', icon: HomeIcon },
{ id: 'members', label: 'Members', icon: UsersIcon, badge: '127' },
{ id: 'events', label: 'Events', icon: CalendarIcon, badge: '5' },
{ id: 'analytics', label: 'Analytics', icon: ChartIcon },
];
const stats = [
{ title: 'Total Members', value: '1,247', change: '+12%', trend: 'up', icon: UsersIcon },
{ title: 'Active Events', value: '18', change: '+3', trend: 'up', icon: CalendarIcon },
{ title: 'Monthly Revenue', value: '$48,392', change: '+8%', trend: 'up', icon: ChartIcon },
{ title: 'Engagement Rate', value: '87%', change: '-2%', trend: 'down', icon: ChartIcon },
];
const activities = [
{ id: 1, type: 'user', icon: UsersIcon, description: 'New member registration: Sarah Johnson', time: '5 minutes ago' },
{ id: 2, type: 'event', icon: CalendarIcon, description: 'Annual Gala event updated', time: '1 hour ago' },
{ id: 3, type: 'payment', icon: ChartIcon, description: 'Payment received from Michael Brown', time: '2 hours ago' },
{ id: 4, type: 'user', icon: UsersIcon, description: 'Member profile updated: Robert Davis', time: '3 hours ago' },
];
const recentMembers = [
{ id: 1, name: 'Sarah Johnson', email: 'sarah@example.com', avatar: 'https://via.placeholder.com/40', status: 'active', joined: 'Jan 15, 2024', lastActive: '2 hours ago' },
{ id: 2, name: 'Michael Brown', email: 'michael@example.com', avatar: 'https://via.placeholder.com/40', status: 'active', joined: 'Jan 10, 2024', lastActive: '1 day ago' },
{ id: 3, name: 'Emma Wilson', email: 'emma@example.com', avatar: 'https://via.placeholder.com/40', status: 'pending', joined: 'Jan 8, 2024', lastActive: '3 days ago' },
{ id: 4, name: 'James Taylor', email: 'james@example.com', avatar: 'https://via.placeholder.com/40', status: 'inactive', joined: 'Dec 20, 2023', lastActive: '1 week ago' },
];
// Initialize chart
onMounted(() => {
if (chartCanvas.value) {
new Chart(chartCanvas.value, {
type: 'line',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [{
label: 'Revenue',
data: [12000, 19000, 15000, 25000, 22000, 30000, 28000],
borderColor: '#DC2626',
backgroundColor: 'rgba(220, 38, 38, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.05)'
}
},
x: {
grid: {
display: false
}
}
}
}
});
}
});
</script>
<style lang="scss" scoped>
@import '../../styles/neumorphic-system.scss';
.admin-dashboard {
display: flex;
min-height: 100vh;
background: $neutral-100;
}
// Sidebar
.sidebar {
width: $sidebar-width;
background: white;
box-shadow: $shadow-soft-md;
display: flex;
flex-direction: column;
transition: width $transition-base;
&--collapsed {
width: $sidebar-width-collapsed;
}
&-header {
padding: $space-6;
border-bottom: 1px solid $neutral-200;
display: flex;
align-items: center;
justify-content: space-between;
}
&-logo {
display: flex;
align-items: center;
gap: $space-3;
img {
width: 40px;
height: 40px;
border-radius: $radius-lg;
}
}
&-title {
font-weight: $font-semibold;
color: $neutral-800;
white-space: nowrap;
}
&-toggle {
background: none;
border: none;
padding: $space-2;
cursor: pointer;
color: $neutral-600;
border-radius: $radius-md;
transition: all $transition-base;
svg {
width: 20px;
height: 20px;
}
&:hover {
background: $neutral-100;
}
}
&-nav {
flex: 1;
padding: $space-4;
}
&-item {
display: flex;
align-items: center;
gap: $space-3;
padding: $space-3 $space-4;
margin-bottom: $space-2;
border-radius: $radius-lg;
color: $neutral-600;
text-decoration: none;
cursor: pointer;
transition: all $transition-base;
position: relative;
&:hover {
background: $neutral-100;
color: $neutral-800;
}
&--active {
background: linear-gradient(135deg, $primary-500, $primary-600);
color: white;
box-shadow: $shadow-soft-sm;
}
&-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
svg {
width: 100%;
height: 100%;
}
}
&-label {
font-size: $text-sm;
font-weight: $font-medium;
white-space: nowrap;
}
&-badge {
margin-left: auto;
padding: 2px 8px;
background: $primary-100;
color: $primary-700;
border-radius: $radius-full;
font-size: $text-xs;
font-weight: $font-semibold;
}
}
&-footer {
padding: $space-4;
border-top: 1px solid $neutral-200;
}
&-user {
display: flex;
align-items: center;
gap: $space-3;
padding: $space-3;
&-avatar {
width: 40px;
height: 40px;
border-radius: $radius-full;
overflow: hidden;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
&-info {
flex: 1;
min-width: 0;
}
&-name {
font-size: $text-sm;
font-weight: $font-semibold;
color: $neutral-800;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&-role {
font-size: $text-xs;
color: $neutral-500;
}
}
}
// Main Content
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
// Topbar
.topbar {
background: white;
padding: $space-6;
box-shadow: $shadow-soft-sm;
display: flex;
align-items: center;
justify-content: space-between;
&-left {
flex: 1;
}
&-title {
font-size: $text-2xl;
font-weight: $font-bold;
color: $neutral-800;
margin-bottom: $space-1;
}
&-subtitle {
font-size: $text-sm;
color: $neutral-600;
}
&-right {
display: flex;
align-items: center;
gap: $space-3;
}
&-button {
position: relative;
padding: $space-2;
background: white;
border: none;
border-radius: $radius-lg;
cursor: pointer;
color: $neutral-600;
box-shadow: $shadow-soft-sm;
transition: all $transition-base;
svg {
width: 20px;
height: 20px;
}
&:hover {
box-shadow: $shadow-soft-md;
color: $neutral-800;
}
&--notification {
.notification-badge {
position: absolute;
top: -4px;
right: -4px;
width: 18px;
height: 18px;
background: $error-500;
color: white;
border-radius: $radius-full;
font-size: $text-xs;
display: flex;
align-items: center;
justify-content: center;
font-weight: $font-bold;
}
}
}
&-divider {
width: 1px;
height: 24px;
background: $neutral-200;
}
&-user {
display: flex;
align-items: center;
gap: $space-2;
padding: $space-2;
background: white;
border: none;
border-radius: $radius-lg;
cursor: pointer;
box-shadow: $shadow-soft-sm;
transition: all $transition-base;
&:hover {
box-shadow: $shadow-soft-md;
}
&-avatar {
width: 32px;
height: 32px;
border-radius: $radius-full;
}
}
}
// Stats Grid
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: $space-6;
padding: $space-6;
}
// Content Grid
.content-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: $space-6;
padding: 0 $space-6 $space-6;
@media (max-width: $breakpoint-lg) {
grid-template-columns: 1fr;
}
.chart-card {
grid-column: 1;
grid-row: 1;
}
.activity-card {
grid-column: 2;
grid-row: 1;
}
.table-card {
grid-column: 1 / -1;
}
}
// Card styles
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-title {
font-size: $text-lg;
font-weight: $font-semibold;
color: $neutral-800;
}
.card-actions {
display: flex;
gap: $space-2;
}
.card-action {
padding: $space-2 $space-3;
background: transparent;
border: none;
border-radius: $radius-md;
font-size: $text-sm;
color: $neutral-600;
cursor: pointer;
transition: all $transition-base;
&:hover {
background: $neutral-100;
color: $neutral-800;
}
&--active {
background: $primary-500;
color: white;
box-shadow: $shadow-soft-sm;
}
}
.card-link {
background: none;
border: none;
color: $primary-600;
font-size: $text-sm;
font-weight: $font-medium;
cursor: pointer;
&:hover {
color: $primary-700;
text-decoration: underline;
}
}
// Chart
.chart-container {
height: 300px;
padding: $space-4 0;
}
// Activity List
.activity-list {
display: flex;
flex-direction: column;
gap: $space-4;
}
.activity-item {
display: flex;
gap: $space-3;
&-icon {
width: 40px;
height: 40px;
border-radius: $radius-lg;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
svg {
width: 20px;
height: 20px;
}
&--user {
background: $info-100;
color: $info-500;
}
&--event {
background: $warning-100;
color: $warning-500;
}
&--payment {
background: $success-100;
color: $success-500;
}
}
&-content {
flex: 1;
min-width: 0;
}
&-description {
font-size: $text-sm;
color: $neutral-700;
margin-bottom: $space-1;
}
&-time {
font-size: $text-xs;
color: $neutral-500;
}
}
// Table
.table-wrapper {
overflow-x: auto;
}
.data-table {
width: 100%;
thead {
tr {
border-bottom: 2px solid $neutral-200;
}
th {
text-align: left;
padding: $space-3;
font-size: $text-xs;
font-weight: $font-semibold;
text-transform: uppercase;
letter-spacing: 0.05em;
color: $neutral-600;
}
}
tbody {
tr {
border-bottom: 1px solid $neutral-100;
transition: background $transition-base;
&:hover {
background: $neutral-50;
}
}
td {
padding: $space-3;
font-size: $text-sm;
color: $neutral-700;
}
}
}
.member-cell {
display: flex;
align-items: center;
gap: $space-3;
}
.member-avatar {
width: 40px;
height: 40px;
border-radius: $radius-full;
}
.member-name {
font-weight: $font-medium;
color: $neutral-800;
margin-bottom: 2px;
}
.member-email {
font-size: $text-xs;
color: $neutral-500;
}
.status-badge {
display: inline-block;
padding: 4px 12px;
border-radius: $radius-full;
font-size: $text-xs;
font-weight: $font-medium;
&--active {
background: $success-100;
color: $success-500;
}
&--pending {
background: $warning-100;
color: $warning-500;
}
&--inactive {
background: $neutral-100;
color: $neutral-500;
}
}
.table-action {
padding: $space-2;
background: none;
border: none;
cursor: pointer;
color: $neutral-500;
border-radius: $radius-md;
transition: all $transition-base;
&:hover {
background: $neutral-100;
color: $neutral-700;
}
}</style>

View File

@ -0,0 +1,419 @@
<template>
<div class="login-page">
<div class="login-container">
<!-- Left Side - Branding -->
<div class="login-branding">
<div class="branding-content">
<div class="logo-container">
<img src="/MONACOUSA-Flags_376x376.png" alt="MonacoUSA" class="logo" />
</div>
<h1 class="brand-title">MonacoUSA Portal</h1>
<p class="brand-tagline">Excellence in Partnership</p>
<div class="feature-list">
<div class="feature-item" v-for="feature in features" :key="feature">
<svg class="feature-icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
<span>{{ feature }}</span>
</div>
</div>
</div>
<!-- Background pattern -->
<div class="branding-pattern"></div>
</div>
<!-- Right Side - Login Form -->
<div class="login-form-section">
<NeumorphicCard size="lg" elevation="lg" class="login-card">
<template #header>
<h2 class="login-title">Welcome Back</h2>
<p class="login-subtitle">Sign in to access your account</p>
</template>
<form @submit.prevent="handleLogin" class="login-form">
<div class="form-group">
<label for="email" class="form-label">Email Address</label>
<div class="input-wrapper">
<input
id="email"
v-model="credentials.email"
type="email"
class="form-input"
placeholder="you@example.com"
required
/>
<span class="input-icon">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</span>
</div>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<div class="input-wrapper">
<input
id="password"
v-model="credentials.password"
:type="showPassword ? 'text' : 'password'"
class="form-input"
placeholder="Enter your password"
required
/>
<button
type="button"
@click="showPassword = !showPassword"
class="input-icon clickable"
>
<svg v-if="!showPassword" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
<svg v-else fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
</button>
</div>
</div>
<div class="form-options">
<label class="checkbox-label">
<input v-model="credentials.rememberMe" type="checkbox" class="checkbox-input" />
<span>Remember me</span>
</label>
<button type="button" class="forgot-password-link" @click="showForgotPassword = true">
Forgot password?
</button>
</div>
<ProfessionalButton
type="submit"
variant="primary"
size="lg"
block
:loading="loading"
class="login-button"
>
Sign In
</ProfessionalButton>
</form>
<template #footer>
<div class="login-footer">
<p class="signup-prompt">
Don't have an account?
<a href="/signup" class="signup-link">Create Account</a>
</p>
</div>
</template>
</NeumorphicCard>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import NeumorphicCard from '../../components/core/NeumorphicCard.vue';
import ProfessionalButton from '../../components/core/ProfessionalButton.vue';
// Data
const credentials = ref({
email: '',
password: '',
rememberMe: false
});
const showPassword = ref(false);
const loading = ref(false);
const showForgotPassword = ref(false);
const features = [
'Secure Member Access',
'Event Management',
'Document Library',
'Payment Processing'
];
// Methods
const handleLogin = async () => {
loading.value = true;
// Simulate API call
setTimeout(() => {
loading.value = false;
console.log('Login with:', credentials.value);
}, 2000);
};
</script>
<style lang="scss" scoped>
@import '../../styles/neumorphic-system.scss';
.login-page {
min-height: 100vh;
background: linear-gradient(135deg, $neutral-50 0%, $neutral-100 100%);
display: flex;
align-items: center;
justify-content: center;
}
.login-container {
width: 100%;
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 100vh;
@media (max-width: $breakpoint-md) {
grid-template-columns: 1fr;
}
}
// Left side - Branding
.login-branding {
position: relative;
background: linear-gradient(135deg, $primary-600, $primary-800);
display: flex;
align-items: center;
justify-content: center;
padding: $space-12;
overflow: hidden;
@media (max-width: $breakpoint-md) {
display: none;
}
.branding-content {
position: relative;
z-index: 2;
text-align: center;
color: white;
}
.logo-container {
width: 120px;
height: 120px;
margin: 0 auto $space-6;
background: white;
border-radius: $radius-2xl;
padding: $space-4;
box-shadow: $shadow-soft-lg;
.logo {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.brand-title {
font-family: $font-heading;
font-size: $text-3xl;
font-weight: $font-bold;
margin-bottom: $space-2;
letter-spacing: -0.025em;
}
.brand-tagline {
font-size: $text-lg;
opacity: 0.9;
margin-bottom: $space-12;
}
.feature-list {
text-align: left;
max-width: 300px;
margin: 0 auto;
}
.feature-item {
display: flex;
align-items: center;
gap: $space-3;
margin-bottom: $space-4;
font-size: $text-base;
.feature-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
color: rgba(white, 0.9);
}
}
.branding-pattern {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
opacity: 0.5;
}
}
// Right side - Login form
.login-form-section {
display: flex;
align-items: center;
justify-content: center;
padding: $space-8;
.login-card {
width: 100%;
max-width: 480px;
}
.login-title {
font-family: $font-heading;
font-size: $text-2xl;
font-weight: $font-bold;
color: $neutral-800;
margin-bottom: $space-2;
}
.login-subtitle {
font-size: $text-base;
color: $neutral-600;
}
.login-form {
.form-group {
margin-bottom: $space-5;
}
.form-label {
display: block;
font-size: $text-sm;
font-weight: $font-medium;
color: $neutral-700;
margin-bottom: $space-2;
}
.input-wrapper {
position: relative;
}
.form-input {
width: 100%;
padding: $space-3 $space-4;
padding-right: $space-12;
background: $neutral-50;
border: none;
border-radius: $radius-lg;
box-shadow: $shadow-inset-sm;
font-size: $text-base;
color: $neutral-800;
transition: all $transition-base;
&::placeholder {
color: $neutral-400;
}
&:focus {
outline: none;
box-shadow: $shadow-inset-md, 0 0 0 3px rgba($primary-500, 0.1);
}
}
.input-icon {
position: absolute;
right: $space-3;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
color: $neutral-400;
&.clickable {
cursor: pointer;
background: none;
border: none;
padding: 0;
transition: color $transition-base;
&:hover {
color: $neutral-600;
}
}
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-6;
}
.checkbox-label {
display: flex;
align-items: center;
gap: $space-2;
font-size: $text-sm;
color: $neutral-700;
cursor: pointer;
.checkbox-input {
width: 18px;
height: 18px;
border-radius: $radius-sm;
cursor: pointer;
}
}
.forgot-password-link {
background: none;
border: none;
color: $primary-600;
font-size: $text-sm;
font-weight: $font-medium;
cursor: pointer;
padding: 0;
transition: color $transition-base;
&:hover {
color: $primary-700;
text-decoration: underline;
}
}
.login-button {
margin-top: $space-4;
}
}
.login-footer {
text-align: center;
.signup-prompt {
font-size: $text-sm;
color: $neutral-600;
}
.signup-link {
color: $primary-600;
font-weight: $font-medium;
text-decoration: none;
margin-left: $space-1;
transition: color $transition-base;
&:hover {
color: $primary-700;
text-decoration: underline;
}
}
}
}
// Dark mode support
@include dark-mode {
.login-page {
background: linear-gradient(135deg, $neutral-900 0%, $neutral-800 100%);
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,320 @@
// MonacoUSA Portal - Neumorphic Design System
// Professional, Modern, and Inviting Design Language
// ============================================
// 1. Color Palette
// ============================================
// Primary - Monaco Red (Sophisticated)
$primary-50: #FEF2F2;
$primary-100: #FEE2E2;
$primary-200: #FECACA;
$primary-300: #FCA5A5;
$primary-400: #F87171;
$primary-500: #DC2626; // Main brand
$primary-600: #B91C1C; // Hover states
$primary-700: #991B1B;
$primary-800: #7F1D1D;
$primary-900: #450A0A;
// Neutral Grays (Professional Workspace)
$neutral-50: #FAFAFA;
$neutral-100: #F5F5F5;
$neutral-200: #E5E5E5;
$neutral-300: #D4D4D4;
$neutral-400: #A3A3A3;
$neutral-500: #737373;
$neutral-600: #525252;
$neutral-700: #404040;
$neutral-800: #262626;
$neutral-900: #171717;
// Semantic Colors
$success-500: #10B981;
$success-100: #D1FAE5;
$warning-500: #F59E0B;
$warning-100: #FEF3C7;
$error-500: #EF4444;
$error-100: #FEE2E2;
$info-500: #3B82F6;
$info-100: #DBEAFE;
// ============================================
// 2. Neumorphic Shadow System
// ============================================
// Light Mode Shadows
$shadow-soft-xs: 2px 2px 4px rgba(0, 0, 0, 0.05), -2px -2px 4px rgba(255, 255, 255, 0.8);
$shadow-soft-sm: 4px 4px 8px rgba(0, 0, 0, 0.08), -4px -4px 8px rgba(255, 255, 255, 0.9);
$shadow-soft-md: 8px 8px 16px rgba(0, 0, 0, 0.1), -8px -8px 16px rgba(255, 255, 255, 0.95);
$shadow-soft-lg: 15px 15px 30px rgba(0, 0, 0, 0.12), -15px -15px 30px rgba(255, 255, 255, 1);
$shadow-soft-xl: 20px 20px 40px rgba(0, 0, 0, 0.15), -20px -20px 40px rgba(255, 255, 255, 1);
// Inset Shadows (for pressed states)
$shadow-inset-sm: inset 2px 2px 4px rgba(0, 0, 0, 0.08), inset -2px -2px 4px rgba(255, 255, 255, 0.9);
$shadow-inset-md: inset 4px 4px 8px rgba(0, 0, 0, 0.1), inset -4px -4px 8px rgba(255, 255, 255, 0.95);
$shadow-inset-lg: inset 8px 8px 16px rgba(0, 0, 0, 0.12), inset -8px -8px 16px rgba(255, 255, 255, 1);
// Dark Mode Shadows
$shadow-dark-soft-sm: 4px 4px 8px rgba(0, 0, 0, 0.3), -4px -4px 8px rgba(255, 255, 255, 0.02);
$shadow-dark-soft-md: 8px 8px 16px rgba(0, 0, 0, 0.4), -8px -8px 16px rgba(255, 255, 255, 0.03);
$shadow-dark-soft-lg: 15px 15px 30px rgba(0, 0, 0, 0.5), -15px -15px 30px rgba(255, 255, 255, 0.04);
$shadow-dark-inset-md: inset 4px 4px 8px rgba(0, 0, 0, 0.4), inset -4px -4px 8px rgba(255, 255, 255, 0.03);
// ============================================
// 3. Typography System
// ============================================
$font-heading: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-body: 'Inter', 'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-mono: 'JetBrains Mono', 'Monaco', 'Courier New', monospace;
// Type Scale (Major Third - 1.25)
$text-xs: 0.75rem; // 12px
$text-sm: 0.875rem; // 14px
$text-base: 1rem; // 16px
$text-lg: 1.25rem; // 20px
$text-xl: 1.563rem; // 25px
$text-2xl: 1.953rem; // 31px
$text-3xl: 2.441rem; // 39px
$text-4xl: 3.052rem; // 49px
// Font Weights
$font-light: 300;
$font-regular: 400;
$font-medium: 500;
$font-semibold: 600;
$font-bold: 700;
// Line Heights
$leading-tight: 1.25;
$leading-normal: 1.5;
$leading-relaxed: 1.75;
// ============================================
// 4. Spacing System
// ============================================
$space-0: 0;
$space-1: 0.25rem; // 4px
$space-2: 0.5rem; // 8px
$space-3: 0.75rem; // 12px
$space-4: 1rem; // 16px
$space-5: 1.25rem; // 20px
$space-6: 1.5rem; // 24px
$space-8: 2rem; // 32px
$space-10: 2.5rem; // 40px
$space-12: 3rem; // 48px
$space-16: 4rem; // 64px
$space-20: 5rem; // 80px
$space-24: 6rem; // 96px
// ============================================
// 5. Border Radius
// ============================================
$radius-sm: 0.375rem; // 6px
$radius-md: 0.5rem; // 8px
$radius-lg: 0.75rem; // 12px
$radius-xl: 1rem; // 16px
$radius-2xl: 1.5rem; // 24px
$radius-3xl: 2rem; // 32px
$radius-full: 9999px; // Full round
// ============================================
// 6. Transitions
// ============================================
$transition-fast: 150ms ease-in-out;
$transition-base: 250ms ease-in-out;
$transition-slow: 350ms ease-in-out;
$transition-slower: 500ms ease-in-out;
// Easing Functions
$ease-in-out-soft: cubic-bezier(0.4, 0, 0.2, 1);
$ease-out-soft: cubic-bezier(0, 0, 0.2, 1);
$ease-in-soft: cubic-bezier(0.4, 0, 1, 1);
// ============================================
// 7. Breakpoints
// ============================================
$breakpoint-xs: 475px;
$breakpoint-sm: 640px;
$breakpoint-md: 768px;
$breakpoint-lg: 1024px;
$breakpoint-xl: 1280px;
$breakpoint-2xl: 1536px;
// ============================================
// 8. Z-Index Scale
// ============================================
$z-base: 0;
$z-dropdown: 1000;
$z-sticky: 1020;
$z-fixed: 1030;
$z-modal-backdrop: 1040;
$z-modal: 1050;
$z-popover: 1060;
$z-tooltip: 1070;
$z-notification: 1080;
// ============================================
// 9. Component-Specific Variables
// ============================================
// Buttons
$button-height-sm: 2rem; // 32px
$button-height-md: 2.5rem; // 40px
$button-height-lg: 3rem; // 48px
$button-padding-x: 1.5rem; // 24px
$button-border-width: 0;
// Cards
$card-padding: 1.5rem;
$card-background: $neutral-100;
$card-border-radius: $radius-xl;
// Inputs
$input-height: 2.75rem; // 44px
$input-padding-x: 1rem; // 16px
$input-border-width: 0;
$input-background: $neutral-100;
$input-border-radius: $radius-lg;
// Sidebar
$sidebar-width: 280px;
$sidebar-width-collapsed: 80px;
$sidebar-background: $neutral-100;
// ============================================
// 10. Mixins
// ============================================
@mixin neumorphic($size: 'md', $type: 'raised', $dark: false) {
@if $dark {
@if $type == 'raised' {
@if $size == 'sm' {
box-shadow: $shadow-dark-soft-sm;
} @else if $size == 'md' {
box-shadow: $shadow-dark-soft-md;
} @else if $size == 'lg' {
box-shadow: $shadow-dark-soft-lg;
}
} @else if $type == 'pressed' {
box-shadow: $shadow-dark-inset-md;
}
} @else {
@if $type == 'raised' {
@if $size == 'sm' {
box-shadow: $shadow-soft-sm;
} @else if $size == 'md' {
box-shadow: $shadow-soft-md;
} @else if $size == 'lg' {
box-shadow: $shadow-soft-lg;
} @else if $size == 'xl' {
box-shadow: $shadow-soft-xl;
}
} @else if $type == 'pressed' {
@if $size == 'sm' {
box-shadow: $shadow-inset-sm;
} @else if $size == 'md' {
box-shadow: $shadow-inset-md;
} @else if $size == 'lg' {
box-shadow: $shadow-inset-lg;
}
}
}
}
@mixin hover-lift($amount: 2px) {
transition: transform $transition-base, box-shadow $transition-base;
&:hover:not(:disabled) {
transform: translateY(-$amount);
}
}
@mixin focus-ring($color: $primary-500) {
&:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba($color, 0.2);
}
}
@mixin truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin responsive($breakpoint) {
@if $breakpoint == 'sm' {
@media (min-width: $breakpoint-sm) { @content; }
} @else if $breakpoint == 'md' {
@media (min-width: $breakpoint-md) { @content; }
} @else if $breakpoint == 'lg' {
@media (min-width: $breakpoint-lg) { @content; }
} @else if $breakpoint == 'xl' {
@media (min-width: $breakpoint-xl) { @content; }
} @else if $breakpoint == '2xl' {
@media (min-width: $breakpoint-2xl) { @content; }
}
}
// ============================================
// 11. Dark Mode Support
// ============================================
@mixin dark-mode {
@media (prefers-color-scheme: dark) {
@content;
}
.dark & {
@content;
}
}
// ============================================
// 12. Utility Classes
// ============================================
.neumorphic-raised {
@include neumorphic('md', 'raised');
}
.neumorphic-pressed {
@include neumorphic('md', 'pressed');
}
.neumorphic-card {
background: $neutral-100;
border-radius: $radius-xl;
padding: $card-padding;
@include neumorphic('md', 'raised');
}
.neumorphic-button {
background: $neutral-100;
border-radius: $radius-lg;
padding: $space-3 $space-6;
@include neumorphic('sm', 'raised');
transition: all $transition-base;
&:hover {
@include neumorphic('md', 'raised');
}
&:active {
@include neumorphic('sm', 'pressed');
}
}
.text-gradient {
background: linear-gradient(135deg, $primary-600, $primary-800);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}

160
package-lock.json generated
View File

@ -12,24 +12,34 @@
"@fullcalendar/interaction": "^6.1.19",
"@fullcalendar/list": "^6.1.19",
"@fullcalendar/vue3": "^6.1.19",
"@headlessui/vue": "^1.7.23",
"@tailwindcss/forms": "^0.5.10",
"@types/handlebars": "^4.0.40",
"@types/jsonwebtoken": "^9.0.10",
"@types/nodemailer": "^6.4.17",
"@vite-pwa/nuxt": "^0.10.8",
"@vueuse/core": "^13.8.0",
"@vueuse/motion": "^3.0.3",
"autoprefixer": "^10.4.21",
"chart.js": "^4.5.0",
"cookie": "^0.6.0",
"formidable": "^3.5.4",
"framer-motion": "^12.23.12",
"gsap": "^3.13.0",
"handlebars": "^4.7.8",
"jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.12.10",
"lottie-web": "^5.13.0",
"lucide-vue-next": "^0.542.0",
"mime-types": "^3.0.1",
"minio": "^8.0.5",
"nodemailer": "^7.0.5",
"nuxt": "^3.15.4",
"postcss": "^8.5.6",
"sharp": "^0.34.3",
"tailwindcss": "^4.1.12",
"vue": "latest",
"vue-chartjs": "^5.3.2",
"vue-country-flag-next": "^2.3.2",
"vue-router": "latest",
"vuetify-nuxt-module": "^0.18.3"
@ -2176,6 +2186,21 @@
"vue": "^3.0.11"
}
},
"node_modules/@headlessui/vue": {
"version": "1.7.23",
"resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz",
"integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==",
"license": "MIT",
"dependencies": {
"@tanstack/vue-virtual": "^3.0.0-beta.60"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
@ -2674,6 +2699,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
@ -5374,6 +5405,44 @@
"sourcemap-codec": "^1.4.8"
}
},
"node_modules/@tailwindcss/forms": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
"integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==",
"license": "MIT",
"dependencies": {
"mini-svg-data-uri": "^1.2.3"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.13.12",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
"integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/vue-virtual": {
"version": "3.13.12",
"resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.12.tgz",
"integrity": "sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.13.12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"vue": "^2.7.0 || ^3.0.0"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
@ -6911,6 +6980,18 @@
],
"license": "CC-BY-4.0"
},
"node_modules/chart.js": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@ -8921,6 +9002,33 @@
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/framer-motion": {
"version": "12.23.12",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.12.tgz",
"integrity": "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.12",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/framesync": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz",
@ -9312,6 +9420,12 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/gsap": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz",
"integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==",
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
},
"node_modules/gzip-size": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz",
@ -11528,6 +11642,12 @@
"node": ">= 12.0.0"
}
},
"node_modules/lottie-web": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz",
"integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==",
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -11726,6 +11846,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
"license": "MIT",
"bin": {
"mini-svg-data-uri": "cli.js"
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@ -11889,6 +12018,21 @@
"node": ">=18"
}
},
"node_modules/motion-dom": {
"version": "12.23.12",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.12.tgz",
"integrity": "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@ -15134,6 +15278,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tailwindcss": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
"integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
@ -16493,6 +16643,16 @@
"ufo": "^1.6.1"
}
},
"node_modules/vue-chartjs": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.2.tgz",
"integrity": "sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==",
"license": "MIT",
"peerDependencies": {
"chart.js": "^4.1.1",
"vue": "^3.0.0-0 || ^2.7.0"
}
},
"node_modules/vue-country-flag-next": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/vue-country-flag-next/-/vue-country-flag-next-2.3.2.tgz",

View File

@ -15,24 +15,34 @@
"@fullcalendar/interaction": "^6.1.19",
"@fullcalendar/list": "^6.1.19",
"@fullcalendar/vue3": "^6.1.19",
"@headlessui/vue": "^1.7.23",
"@tailwindcss/forms": "^0.5.10",
"@types/handlebars": "^4.0.40",
"@types/jsonwebtoken": "^9.0.10",
"@types/nodemailer": "^6.4.17",
"@vite-pwa/nuxt": "^0.10.8",
"@vueuse/core": "^13.8.0",
"@vueuse/motion": "^3.0.3",
"autoprefixer": "^10.4.21",
"chart.js": "^4.5.0",
"cookie": "^0.6.0",
"formidable": "^3.5.4",
"framer-motion": "^12.23.12",
"gsap": "^3.13.0",
"handlebars": "^4.7.8",
"jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.12.10",
"lottie-web": "^5.13.0",
"lucide-vue-next": "^0.542.0",
"mime-types": "^3.0.1",
"minio": "^8.0.5",
"nodemailer": "^7.0.5",
"nuxt": "^3.15.4",
"postcss": "^8.5.6",
"sharp": "^0.34.3",
"tailwindcss": "^4.1.12",
"vue": "latest",
"vue-chartjs": "^5.3.2",
"vue-country-flag-next": "^2.3.2",
"vue-router": "latest",
"vuetify-nuxt-module": "^0.18.3"