Mockups for Designs
Build And Push Image / docker (push) Successful in 1m55s
Details
Build And Push Image / docker (push) Successful in 1m55s
Details
This commit is contained in:
parent
e75de8b9f4
commit
4d24315103
|
|
@ -0,0 +1 @@
|
|||
/cache
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue