Implement MonacoUSA Portal redesign foundations
Some checks failed
Build And Push Image / docker (push) Failing after 1m11s

- Added VueUse Motion for animations with custom presets
- Created base UI component library with glass morphism effects:
  * GlassCard - Flexible card component with 4 variants
  * MonacoButton - Multi-variant button system
  * FloatingInput - Modern input with floating labels
  * StatsCard - Dashboard statistics display
  * AnimatedNumber - Smooth number animations
  * Icon system - Modular icon components
- Created comprehensive page mockups:
  * Dashboard mockup with stats, activity feed, and widgets
  * Events page with filtering, search, and calendar
- Established Monaco brand design system (red #dc2626)
- Configured spring animations and glass effects

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-30 18:25:21 +02:00
parent de75d2d764
commit c39936984b
29 changed files with 7278 additions and 1 deletions

480
Design/README.md Normal file
View File

@@ -0,0 +1,480 @@
# MonacoUSA Portal Design System Implementation Guide
## 🎨 Overview
This comprehensive guide outlines the complete visual redesign of the MonacoUSA Portal, transitioning from a standard Vuetify implementation to a premium, custom design system featuring Monaco's signature red and white color scheme with modern glass morphism effects.
## 📋 Table of Contents
1. [Design Philosophy](#design-philosophy)
2. [Technical Stack](#technical-stack)
3. [Migration Strategy](#migration-strategy)
4. [Component Architecture](#component-architecture)
5. [Implementation Roadmap](#implementation-roadmap)
6. [Performance Guidelines](#performance-guidelines)
7. [Accessibility Standards](#accessibility-standards)
## Design Philosophy
### Core Principles
- **Premium Feel**: Every interaction should feel smooth and sophisticated
- **Brand Identity**: Monaco's red (#dc2626) as the primary accent color
- **Modern Aesthetics**: Glass morphism, subtle animations, and floating elements
- **User Experience**: Intuitive navigation with clear visual hierarchy
- **Performance**: Animations that enhance, not hinder, user experience
### Visual Language
- **Glass Morphism**: Semi-transparent surfaces with backdrop blur
- **Gradient Accents**: Dynamic gradients from Monaco red to deeper shades
- **Floating Elements**: Subtle shadows and depth for interactive components
- **Micro-animations**: Smooth transitions on hover, click, and state changes
- **Consistent Spacing**: 8px grid system for alignment and padding
## Technical Stack
### Current Setup
- **Framework**: Nuxt 3.8.2
- **UI Library**: Vuetify 3.4.7 (to be replaced)
- **Icons**: Material Design Icons (transitioning to Lucide)
- **Authentication**: Keycloak integration
- **Styling**: SCSS with scoped components
### New Additions
- **Animation Libraries**:
- VueUse Motion (preferred for Vue integration)
- GSAP (for complex animations)
- Anime.js (lightweight alternative)
- **Icons**: Lucide Icons (modern, customizable)
- **Utilities**: Tailwind CSS (for rapid prototyping)
- **State Management**: Pinia (already implemented)
## Migration Strategy
### Phase 1: Foundation (Week 1-2)
1. Set up new style architecture
2. Create base component library
3. Implement color system and typography
4. Set up animation utilities
### Phase 2: Core Components (Week 3-4)
1. Replace navigation components
2. Implement custom dropdowns and selects
3. Create button variants
4. Build card components
### Phase 3: Page Templates (Week 5-6)
1. Dashboard layouts
2. Data tables with glass effects
3. Form components
4. Modal and dialog systems
### Phase 4: Polish & Optimization (Week 7-8)
1. Performance tuning
2. Accessibility audit
3. Cross-browser testing
4. Documentation completion
## Component Architecture
### File Structure
```
components/
├── ui/ # Base UI components
│ ├── MonacoButton.vue
│ ├── GlassCard.vue
│ ├── AnimatedDropdown.vue
│ └── FloatingInput.vue
├── layout/ # Layout components
│ ├── DashboardSidebar.vue
│ ├── AppHeader.vue
│ └── PageContainer.vue
├── features/ # Feature-specific components
│ ├── MemberCard.vue
│ ├── EventCalendar.vue
│ └── DuesTracker.vue
└── shared/ # Shared utilities
├── LoadingSpinner.vue
├── ErrorBoundary.vue
└── TransitionWrapper.vue
```
### Component Guidelines
#### Naming Convention
- PascalCase for component files
- Prefix with "Monaco" for custom branded components
- Use descriptive names (e.g., `GlassDropdownMenu` not `Dropdown`)
#### Props & Events
```vue
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'glass' | 'gradient'
size?: 'sm' | 'md' | 'lg'
animated?: boolean
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
animated: true
})
const emit = defineEmits<{
click: [event: MouseEvent]
hover: [state: boolean]
}>()
</script>
```
#### Composition API Pattern
```vue
<script setup lang="ts">
import { useMonacoTheme } from '~/composables/useMonacoTheme'
import { useGlassEffect } from '~/composables/useGlassEffect'
const { primaryColor, gradients } = useMonacoTheme()
const { glassStyle, blurAmount } = useGlassEffect()
</script>
```
## Implementation Roadmap
### Week 1: Setup & Foundation
- [ ] Configure animation libraries
- [ ] Set up SCSS architecture
- [ ] Create color system utilities
- [ ] Implement base glass morphism styles
- [ ] Set up Lucide icons integration
### Week 2: Core Components
- [ ] Custom dropdown components
- [ ] Button system with variants
- [ ] Card components with glass effects
- [ ] Form inputs with floating labels
- [ ] Navigation components
### Week 3: Dashboard Implementation
- [ ] Member dashboard layout
- [ ] Board dashboard enhancements
- [ ] Admin panel structure
- [ ] Widget components
- [ ] Chart integrations
### Week 4: Advanced Features
- [ ] Event calendar with animations
- [ ] Member management interface
- [ ] Dues payment flow
- [ ] Profile components
- [ ] Settings panels
### Week 5: Responsive Design
- [ ] Mobile navigation
- [ ] Tablet optimizations
- [ ] Touch interactions
- [ ] Gesture support
- [ ] PWA enhancements
### Week 6: Testing & Optimization
- [ ] Performance profiling
- [ ] Bundle optimization
- [ ] Lazy loading implementation
- [ ] Animation performance tuning
- [ ] Memory leak detection
## Performance Guidelines
### Animation Performance
```scss
// Use transform and opacity for animations
.animated-element {
will-change: transform, opacity;
transform: translateZ(0); // Enable hardware acceleration
}
// Avoid animating expensive properties
// BAD: width, height, padding, margin
// GOOD: transform, opacity, filter
```
### Component Optimization
```vue
<!-- Use v-show for frequently toggled elements -->
<div v-show="isVisible" class="glass-panel">
<!-- Content -->
</div>
<!-- Use v-if for conditionally rendered heavy components -->
<HeavyComponent v-if="shouldRender" />
<!-- Implement lazy loading for routes -->
<script setup>
const MemberDashboard = defineAsyncComponent(() =>
import('~/components/dashboards/MemberDashboard.vue')
)
</script>
```
### Bundle Size Management
- Tree-shake unused Vuetify components
- Lazy load animation libraries
- Use dynamic imports for heavy features
- Implement code splitting by route
- Optimize images with next-gen formats
## Accessibility Standards
### WCAG 2.1 Compliance
- **Color Contrast**: Ensure 4.5:1 ratio for normal text
- **Focus Indicators**: Clear visual focus states
- **Keyboard Navigation**: Full keyboard support
- **Screen Readers**: Proper ARIA labels
- **Motion Sensitivity**: Respect prefers-reduced-motion
### Implementation Examples
```vue
<template>
<button
:aria-label="ariaLabel"
:aria-pressed="isPressed"
@click="handleClick"
@keydown.enter="handleClick"
@keydown.space.prevent="handleClick"
class="monaco-button"
:class="{ 'reduced-motion': prefersReducedMotion }"
>
<slot />
</button>
</template>
<script setup>
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)')
</script>
<style scoped>
.monaco-button {
transition: all 0.3s ease;
}
.monaco-button.reduced-motion {
transition: none;
}
</style>
```
## Style Guidelines
### Color System
```scss
// Primary Colors
$monaco-red: #dc2626;
$monaco-red-dark: #b91c1c;
$monaco-red-light: #ef4444;
$monaco-white: #ffffff;
// Gradients
$monaco-gradient: linear-gradient(135deg, $monaco-red 0%, $monaco-red-dark 100%);
$monaco-gradient-reverse: linear-gradient(135deg, $monaco-red-light 0%, $monaco-red 100%);
// Glass Effects
$glass-bg: rgba(255, 255, 255, 0.7);
$glass-border: rgba(255, 255, 255, 0.3);
$glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
$glass-blur: 20px;
```
### Typography Scale
```scss
// Font Family
$font-primary: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
$font-mono: 'Fira Code', 'Monaco', monospace;
// Font Sizes
$text-xs: 0.75rem; // 12px
$text-sm: 0.875rem; // 14px
$text-base: 1rem; // 16px
$text-lg: 1.125rem; // 18px
$text-xl: 1.25rem; // 20px
$text-2xl: 1.5rem; // 24px
$text-3xl: 1.875rem; // 30px
$text-4xl: 2.25rem; // 36px
```
### Spacing System
```scss
// Based on 8px grid
$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
```
## Component Examples
### Glass Card Component
```vue
<template>
<div class="glass-card" :class="[sizeClass, variantClass]">
<div v-if="hasHeader" class="glass-card-header">
<slot name="header" />
</div>
<div class="glass-card-body">
<slot />
</div>
<div v-if="hasFooter" class="glass-card-footer">
<slot name="footer" />
</div>
</div>
</template>
<script setup lang="ts">
interface Props {
size?: 'sm' | 'md' | 'lg'
variant?: 'light' | 'dark' | 'colored'
}
const props = withDefaults(defineProps<Props>(), {
size: 'md',
variant: 'light'
})
const slots = useSlots()
const hasHeader = computed(() => !!slots.header)
const hasFooter = computed(() => !!slots.footer)
const sizeClass = computed(() => `glass-card--${props.size}`)
const variantClass = computed(() => `glass-card--${props.variant}`)
</script>
<style scoped lang="scss">
.glass-card {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}
&--dark {
background: rgba(0, 0, 0, 0.7);
border-color: rgba(255, 255, 255, 0.1);
}
&--colored {
background: linear-gradient(135deg,
rgba(220, 38, 38, 0.1) 0%,
rgba(185, 28, 28, 0.1) 100%);
}
}
</style>
```
## Testing Strategy
### Unit Testing
```typescript
// components/__tests__/MonacoButton.spec.ts
import { mount } from '@vue/test-utils'
import MonacoButton from '~/components/ui/MonacoButton.vue'
describe('MonacoButton', () => {
it('renders with correct variant class', () => {
const wrapper = mount(MonacoButton, {
props: { variant: 'gradient' }
})
expect(wrapper.classes()).toContain('monaco-button--gradient')
})
it('emits click event', async () => {
const wrapper = mount(MonacoButton)
await wrapper.trigger('click')
expect(wrapper.emitted('click')).toHaveLength(1)
})
})
```
### E2E Testing
```typescript
// e2e/dashboard.spec.ts
import { test, expect } from '@playwright/test'
test('dashboard loads with glass morphism effects', async ({ page }) => {
await page.goto('/dashboard')
const glassCard = page.locator('.glass-card').first()
await expect(glassCard).toBeVisible()
const styles = await glassCard.evaluate(el =>
window.getComputedStyle(el)
)
expect(styles.backdropFilter).toContain('blur')
})
```
## Deployment Checklist
### Pre-deployment
- [ ] Run full test suite
- [ ] Check bundle size (<500KB initial)
- [ ] Validate accessibility scores
- [ ] Test on all target browsers
- [ ] Optimize images and assets
- [ ] Review security headers
### Performance Metrics
- First Contentful Paint: <1.5s
- Time to Interactive: <3s
- Cumulative Layout Shift: <0.1
- First Input Delay: <100ms
- Lighthouse Score: >90
### Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
- Mobile Safari 14+
- Chrome Mobile 90+
## Resources
### Documentation
- [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html)
- [Nuxt 3 Documentation](https://nuxt.com/docs)
- [VueUse Motion](https://motion.vueuse.org/)
- [GSAP Documentation](https://greensock.com/docs/)
- [Lucide Icons](https://lucide.dev/)
### Design Inspiration
- [Glass Morphism Examples](https://glassmorphism.com/)
- [Monaco Brand Guidelines](internal-link)
- [Material Design 3](https://m3.material.io/)
### Tools
- [Contrast Checker](https://webaim.org/resources/contrastchecker/)
- [Bundle Analyzer](https://github.com/nuxt/devtools)
- [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci)
## Support
For questions or assistance with implementation:
- Technical Lead: [Contact Info]
- Design Team: [Contact Info]
- Documentation: This guide and `/Design` folder
---
*Last Updated: December 2024*
*Version: 1.0.0*

View File

@@ -0,0 +1,510 @@
<template>
<div class="animated-select" ref="selectRef">
<button
@click="toggleSelect"
class="animated-select__trigger"
:class="{ 'animated-select__trigger--open': isOpen }"
>
<div class="animated-select__display">
<Transition name="slide-fade" mode="out-in">
<span
v-if="!selectedOption"
key="placeholder"
class="animated-select__placeholder"
>
{{ placeholder }}
</span>
<div
v-else
key="selected"
class="animated-select__selected"
>
<Icon
v-if="selectedOption.icon"
:name="selectedOption.icon"
class="animated-select__icon"
/>
<span>{{ selectedOption.label }}</span>
</div>
</Transition>
</div>
<div class="animated-select__arrow">
<svg
class="animated-select__arrow-icon"
:class="{ 'animated-select__arrow-icon--rotate': isOpen }"
width="20"
height="20"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
<Teleport to="body">
<Transition name="select-dropdown">
<div
v-if="isOpen"
class="animated-select__dropdown"
:style="dropdownStyle"
@click.stop
>
<div class="animated-select__options">
<TransitionGroup name="option-list">
<div
v-for="(option, index) in options"
:key="option.value"
class="animated-select__option"
:class="{
'animated-select__option--selected': modelValue === option.value,
'animated-select__option--highlighted': highlightedIndex === index,
'animated-select__option--disabled': option.disabled
}"
:style="{ '--delay': `${index * 30}ms` }"
@click="!option.disabled && selectOption(option)"
@mouseenter="highlightedIndex = index"
@mouseleave="highlightedIndex = -1"
>
<div class="animated-select__option-content">
<Icon
v-if="option.icon"
:name="option.icon"
class="animated-select__option-icon"
/>
<span class="animated-select__option-label">
{{ option.label }}
</span>
<span
v-if="option.description"
class="animated-select__option-description"
>
{{ option.description }}
</span>
</div>
<Transition name="check">
<Icon
v-if="modelValue === option.value"
name="check"
class="animated-select__option-check"
/>
</Transition>
</div>
</TransitionGroup>
</div>
</div>
</Transition>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import Icon from '~/components/ui/Icon.vue'
interface SelectOption {
label: string
value: string | number
icon?: string
description?: string
disabled?: boolean
}
interface Props {
modelValue?: string | number | null
options: SelectOption[]
placeholder?: string
searchable?: boolean
multiple?: boolean
}
const props = withDefaults(defineProps<Props>(), {
placeholder: 'Select an option',
searchable: false,
multiple: false
})
const emit = defineEmits<{
'update:modelValue': [value: string | number | null]
'change': [value: string | number | null]
'open': []
'close': []
}>()
const selectRef = ref<HTMLElement>()
const isOpen = ref(false)
const highlightedIndex = ref(-1)
const dropdownStyle = ref({})
const selectedOption = computed(() => {
return props.options.find(opt => opt.value === props.modelValue)
})
const toggleSelect = () => {
isOpen.value = !isOpen.value
if (isOpen.value) {
emit('open')
nextTick(() => updateDropdownPosition())
} else {
emit('close')
highlightedIndex.value = -1
}
}
const selectOption = (option: SelectOption) => {
emit('update:modelValue', option.value)
emit('change', option.value)
isOpen.value = false
highlightedIndex.value = -1
emit('close')
}
const updateDropdownPosition = () => {
if (!selectRef.value) return
const rect = selectRef.value.getBoundingClientRect()
const spaceBelow = window.innerHeight - rect.bottom
const spaceAbove = rect.top
const dropdownHeight = 300 // Approximate max height
let top = rect.bottom + 8
if (spaceBelow < dropdownHeight && spaceAbove > spaceBelow) {
top = rect.top - dropdownHeight - 8
}
dropdownStyle.value = {
position: 'fixed',
top: `${top}px`,
left: `${rect.left}px`,
width: `${rect.width}px`,
zIndex: 9999
}
}
const handleClickOutside = (event: MouseEvent) => {
if (selectRef.value && !selectRef.value.contains(event.target as Node)) {
isOpen.value = false
highlightedIndex.value = -1
emit('close')
}
}
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen.value) {
isOpen.value = false
highlightedIndex.value = -1
emit('close')
}
}
const handleKeyNavigation = (event: KeyboardEvent) => {
if (!isOpen.value) return
switch (event.key) {
case 'ArrowDown':
event.preventDefault()
highlightedIndex.value = Math.min(
highlightedIndex.value + 1,
props.options.length - 1
)
break
case 'ArrowUp':
event.preventDefault()
highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0)
break
case 'Enter':
event.preventDefault()
if (highlightedIndex.value >= 0) {
const option = props.options[highlightedIndex.value]
if (!option.disabled) {
selectOption(option)
}
}
break
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
document.addEventListener('keydown', handleEscape)
document.addEventListener('keydown', handleKeyNavigation)
window.addEventListener('resize', updateDropdownPosition)
window.addEventListener('scroll', updateDropdownPosition)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
document.removeEventListener('keydown', handleEscape)
document.removeEventListener('keydown', handleKeyNavigation)
window.removeEventListener('resize', updateDropdownPosition)
window.removeEventListener('scroll', updateDropdownPosition)
})
watch(isOpen, (newVal) => {
if (newVal) {
nextTick(() => updateDropdownPosition())
}
})
</script>
<style scoped lang="scss">
.animated-select {
position: relative;
width: 100%;
&__trigger {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0.75rem 1rem;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 2px solid transparent;
border-radius: 16px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
&:hover {
background: rgba(255, 255, 255, 0.8);
border-color: rgba(220, 38, 38, 0.2);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.08);
transform: translateY(-1px);
}
&:focus {
border-color: #dc2626;
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
}
&--open {
border-color: #dc2626;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
}
}
&__display {
flex: 1;
min-height: 1.5rem;
}
&__placeholder {
color: #71717a;
font-size: 0.9375rem;
}
&__selected {
display: flex;
align-items: center;
gap: 0.5rem;
color: #27272a;
font-weight: 500;
}
&__icon {
width: 1.25rem;
height: 1.25rem;
color: #dc2626;
}
&__arrow {
display: flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
}
&__arrow-icon {
color: #dc2626;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&--rotate {
transform: rotate(180deg);
}
}
&__dropdown {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(220, 38, 38, 0.05);
overflow: hidden;
max-height: 300px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(220, 38, 38, 0.2);
border-radius: 4px;
&:hover {
background: rgba(220, 38, 38, 0.3);
}
}
}
&__options {
padding: 0.25rem;
}
&__option {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
margin: 0.125rem 0;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1) both;
animation-delay: var(--delay);
&:hover:not(&--disabled) {
background: linear-gradient(135deg,
rgba(220, 38, 38, 0.05) 0%,
rgba(220, 38, 38, 0.1) 100%);
transform: translateX(4px);
}
&--highlighted {
background: rgba(220, 38, 38, 0.05);
}
&--selected {
background: linear-gradient(135deg,
rgba(220, 38, 38, 0.1) 0%,
rgba(220, 38, 38, 0.15) 100%);
color: #dc2626;
font-weight: 600;
}
&--disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
&__option-content {
display: flex;
align-items: center;
gap: 0.75rem;
flex: 1;
}
&__option-icon {
width: 1.25rem;
height: 1.25rem;
color: #dc2626;
}
&__option-label {
font-size: 0.9375rem;
color: inherit;
}
&__option-description {
font-size: 0.75rem;
color: #71717a;
margin-left: auto;
margin-right: 1rem;
}
&__option-check {
width: 1.25rem;
height: 1.25rem;
color: #dc2626;
}
}
// Animations
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 0.2s ease;
}
.slide-fade-enter-from {
opacity: 0;
transform: translateY(-4px);
}
.slide-fade-leave-to {
opacity: 0;
transform: translateY(4px);
}
.select-dropdown-enter-active,
.select-dropdown-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.select-dropdown-enter-from {
opacity: 0;
transform: scale(0.95) translateY(-10px);
filter: blur(4px);
}
.select-dropdown-leave-to {
opacity: 0;
transform: scale(0.95) translateY(-10px);
filter: blur(4px);
}
.option-list-enter-active,
.option-list-leave-active {
transition: all 0.3s ease;
}
.option-list-enter-from {
opacity: 0;
transform: translateX(-20px);
}
.option-list-leave-to {
opacity: 0;
transform: translateX(20px);
}
.check-enter-active,
.check-leave-active {
transition: all 0.2s ease;
}
.check-enter-from,
.check-leave-to {
opacity: 0;
transform: scale(0.5);
}
</style>

View File

@@ -0,0 +1,713 @@
<template>
<div class="glass-dropdown" ref="dropdownRef">
<div
class="glass-dropdown__trigger"
@click="toggleDropdown"
:class="{ 'glass-dropdown__trigger--active': isOpen }"
>
<div class="glass-dropdown__trigger-content">
<slot name="trigger">
<div class="glass-dropdown__trigger-default">
<Icon
v-if="icon"
:name="icon"
class="glass-dropdown__trigger-icon"
/>
<span class="glass-dropdown__trigger-text">{{ label }}</span>
<div class="glass-dropdown__trigger-arrow">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
class="glass-dropdown__arrow-svg"
:class="{ 'glass-dropdown__arrow-svg--rotate': isOpen }"
>
<path
d="M4 6L8 10L12 6"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
</div>
</slot>
</div>
<div class="glass-dropdown__trigger-glow"></div>
</div>
<Teleport to="body">
<Transition name="glass-fade">
<div
v-if="isOpen"
class="glass-dropdown__backdrop"
@click="closeDropdown"
>
<div
class="glass-dropdown__menu"
:style="menuStyle"
@click.stop
>
<div class="glass-dropdown__menu-inner">
<div
v-if="$slots.header"
class="glass-dropdown__header"
>
<slot name="header"></slot>
</div>
<div class="glass-dropdown__items">
<template v-if="groups.length > 0">
<div
v-for="(group, groupIndex) in groups"
:key="groupIndex"
class="glass-dropdown__group"
>
<div
v-if="group.label"
class="glass-dropdown__group-label"
>
{{ group.label }}
</div>
<div
v-for="(item, itemIndex) in group.items"
:key="`${groupIndex}-${itemIndex}`"
class="glass-dropdown__item"
:class="{
'glass-dropdown__item--active': isItemActive(item),
'glass-dropdown__item--disabled': item.disabled,
'glass-dropdown__item--danger': item.variant === 'danger'
}"
@click="!item.disabled && handleItemClick(item)"
@mouseenter="hoveredItem = item"
@mouseleave="hoveredItem = null"
>
<div class="glass-dropdown__item-content">
<Icon
v-if="item.icon"
:name="item.icon"
class="glass-dropdown__item-icon"
/>
<div class="glass-dropdown__item-text">
<div class="glass-dropdown__item-label">
{{ item.label }}
</div>
<div
v-if="item.description"
class="glass-dropdown__item-description"
>
{{ item.description }}
</div>
</div>
<div
v-if="item.badge"
class="glass-dropdown__item-badge"
>
{{ item.badge }}
</div>
<Icon
v-if="item.submenu"
name="chevron-right"
class="glass-dropdown__item-chevron"
/>
</div>
<Transition name="submenu-slide">
<div
v-if="item.submenu && hoveredItem === item"
class="glass-dropdown__submenu"
>
<div
v-for="(subItem, subIndex) in item.submenu"
:key="subIndex"
class="glass-dropdown__submenu-item"
:class="{
'glass-dropdown__submenu-item--disabled': subItem.disabled
}"
@click.stop="!subItem.disabled && handleSubmenuClick(subItem)"
>
<Icon
v-if="subItem.icon"
:name="subItem.icon"
class="glass-dropdown__submenu-icon"
/>
<span>{{ subItem.label }}</span>
</div>
</div>
</Transition>
</div>
<div
v-if="groupIndex < groups.length - 1"
class="glass-dropdown__divider"
></div>
</div>
</template>
<div v-else class="glass-dropdown__empty">
<slot name="empty">
<p>No items available</p>
</slot>
</div>
</div>
<div
v-if="$slots.footer"
class="glass-dropdown__footer"
>
<slot name="footer"></slot>
</div>
</div>
<div class="glass-dropdown__menu-glow"></div>
</div>
</div>
</Transition>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import Icon from '~/components/ui/Icon.vue'
interface DropdownItem {
label: string
value?: string | number
icon?: string
description?: string
badge?: string | number
variant?: 'default' | 'danger'
disabled?: boolean
action?: () => void
submenu?: DropdownItem[]
}
interface DropdownGroup {
label?: string
items: DropdownItem[]
}
interface Props {
label?: string
icon?: string
groups?: DropdownGroup[]
items?: DropdownItem[]
modelValue?: string | number | null
closeOnSelect?: boolean
}
const props = withDefaults(defineProps<Props>(), {
label: 'Menu',
closeOnSelect: true
})
const emit = defineEmits<{
'update:modelValue': [value: string | number | null]
'select': [item: DropdownItem]
'open': []
'close': []
}>()
const dropdownRef = ref<HTMLElement>()
const isOpen = ref(false)
const hoveredItem = ref<DropdownItem | null>(null)
const menuStyle = ref({})
const groups = computed(() => {
if (props.groups && props.groups.length > 0) {
return props.groups
}
if (props.items && props.items.length > 0) {
return [{ items: props.items }]
}
return []
})
const toggleDropdown = () => {
isOpen.value = !isOpen.value
if (isOpen.value) {
emit('open')
nextTick(() => updateMenuPosition())
} else {
emit('close')
hoveredItem.value = null
}
}
const closeDropdown = () => {
isOpen.value = false
hoveredItem.value = null
emit('close')
}
const handleItemClick = (item: DropdownItem) => {
if (item.action) {
item.action()
}
if (item.value !== undefined) {
emit('update:modelValue', item.value)
}
emit('select', item)
if (props.closeOnSelect && !item.submenu) {
closeDropdown()
}
}
const handleSubmenuClick = (item: DropdownItem) => {
if (item.action) {
item.action()
}
emit('select', item)
if (props.closeOnSelect) {
closeDropdown()
}
}
const isItemActive = (item: DropdownItem) => {
return item.value !== undefined && item.value === props.modelValue
}
const updateMenuPosition = () => {
if (!dropdownRef.value) return
const rect = dropdownRef.value.getBoundingClientRect()
const windowWidth = window.innerWidth
const windowHeight = window.innerHeight
let top = rect.bottom + 8
let left = rect.left
// Adjust if menu would go off screen
const menuWidth = 320 // Approximate menu width
const menuHeight = 400 // Approximate max menu height
if (left + menuWidth > windowWidth) {
left = windowWidth - menuWidth - 16
}
if (top + menuHeight > windowHeight && rect.top > menuHeight) {
top = rect.top - menuHeight - 8
}
menuStyle.value = {
position: 'fixed',
top: `${top}px`,
left: `${left}px`,
minWidth: `${rect.width}px`,
zIndex: 10001
}
}
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen.value) {
closeDropdown()
}
}
onMounted(() => {
document.addEventListener('keydown', handleEscape)
window.addEventListener('resize', updateMenuPosition)
window.addEventListener('scroll', updateMenuPosition)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleEscape)
window.removeEventListener('resize', updateMenuPosition)
window.removeEventListener('scroll', updateMenuPosition)
})
</script>
<style scoped lang="scss">
.glass-dropdown {
position: relative;
display: inline-block;
&__trigger {
position: relative;
display: inline-flex;
align-items: center;
padding: 0.625rem 1rem;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(30px) saturate(180%);
-webkit-backdrop-filter: blur(30px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 14px;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.04),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
&:hover {
background: rgba(255, 255, 255, 0.7);
border-color: rgba(220, 38, 38, 0.3);
transform: translateY(-2px);
box-shadow:
0 8px 30px rgba(220, 38, 38, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
.glass-dropdown__trigger-glow {
opacity: 1;
}
}
&--active {
background: rgba(255, 255, 255, 0.8);
border-color: rgba(220, 38, 38, 0.4);
box-shadow:
0 0 0 4px rgba(220, 38, 38, 0.1),
0 8px 30px rgba(220, 38, 38, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 1);
}
}
&__trigger-content {
position: relative;
z-index: 1;
}
&__trigger-default {
display: flex;
align-items: center;
gap: 0.5rem;
}
&__trigger-icon {
width: 1.25rem;
height: 1.25rem;
color: #dc2626;
}
&__trigger-text {
font-weight: 500;
color: #27272a;
font-size: 0.9375rem;
}
&__trigger-arrow {
margin-left: 0.5rem;
}
&__arrow-svg {
color: #dc2626;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&--rotate {
transform: rotate(180deg);
}
}
&__trigger-glow {
position: absolute;
inset: -1px;
background: linear-gradient(135deg,
rgba(220, 38, 38, 0.2) 0%,
rgba(220, 38, 38, 0) 100%);
border-radius: 14px;
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
}
&__backdrop {
position: fixed;
inset: 0;
z-index: 10000;
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(4px);
}
&__menu {
position: absolute;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(40px) saturate(200%);
-webkit-backdrop-filter: blur(40px) saturate(200%);
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 20px;
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.15),
0 0 0 1px rgba(220, 38, 38, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
overflow: hidden;
min-width: 280px;
max-width: 400px;
max-height: 70vh;
overflow-y: auto;
&::-webkit-scrollbar {
width: 10px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: linear-gradient(180deg,
rgba(220, 38, 38, 0.3) 0%,
rgba(220, 38, 38, 0.1) 100%);
border-radius: 10px;
border: 2px solid rgba(255, 255, 255, 0.9);
&:hover {
background: linear-gradient(180deg,
rgba(220, 38, 38, 0.4) 0%,
rgba(220, 38, 38, 0.2) 100%);
}
}
}
&__menu-inner {
position: relative;
z-index: 1;
padding: 0.5rem;
}
&__menu-glow {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(
ellipse at center,
rgba(220, 38, 38, 0.1) 0%,
transparent 70%
);
pointer-events: none;
}
&__header {
padding: 1rem;
border-bottom: 1px solid rgba(220, 38, 38, 0.1);
margin-bottom: 0.5rem;
}
&__footer {
padding: 1rem;
border-top: 1px solid rgba(220, 38, 38, 0.1);
margin-top: 0.5rem;
}
&__group {
margin: 0.25rem 0;
}
&__group-label {
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
color: #dc2626;
text-transform: uppercase;
letter-spacing: 0.05em;
}
&__divider {
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(220, 38, 38, 0.2) 50%,
transparent 100%);
margin: 0.5rem 0;
}
&__item {
position: relative;
padding: 0.625rem 0.75rem;
margin: 0.125rem 0;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover:not(&--disabled) {
background: linear-gradient(135deg,
rgba(220, 38, 38, 0.08) 0%,
rgba(220, 38, 38, 0.04) 100%);
transform: translateX(4px);
.glass-dropdown__item-icon {
transform: scale(1.1);
}
}
&--active {
background: linear-gradient(135deg,
rgba(220, 38, 38, 0.15) 0%,
rgba(220, 38, 38, 0.08) 100%);
color: #dc2626;
font-weight: 600;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 60%;
background: #dc2626;
border-radius: 0 3px 3px 0;
}
}
&--danger {
color: #ef4444;
&:hover {
background: rgba(239, 68, 68, 0.1);
}
}
&--disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
&__item-content {
display: flex;
align-items: center;
gap: 0.75rem;
}
&__item-icon {
width: 1.25rem;
height: 1.25rem;
color: #dc2626;
flex-shrink: 0;
transition: transform 0.3s ease;
}
&__item-text {
flex: 1;
}
&__item-label {
font-size: 0.9375rem;
color: inherit;
}
&__item-description {
font-size: 0.75rem;
color: #71717a;
margin-top: 0.125rem;
}
&__item-badge {
padding: 0.125rem 0.5rem;
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
color: white;
font-size: 0.75rem;
font-weight: 600;
border-radius: 999px;
}
&__item-chevron {
width: 1rem;
height: 1rem;
color: #71717a;
margin-left: auto;
}
&__submenu {
position: absolute;
left: calc(100% + 0.5rem);
top: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(30px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 0.25rem;
min-width: 200px;
z-index: 10;
}
&__submenu-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
&:hover:not(&--disabled) {
background: rgba(220, 38, 38, 0.1);
color: #dc2626;
}
&--disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
&__submenu-icon {
width: 1rem;
height: 1rem;
color: #dc2626;
}
&__empty {
padding: 2rem;
text-align: center;
color: #71717a;
font-size: 0.875rem;
}
}
// Animations
.glass-fade-enter-active,
.glass-fade-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glass-fade-enter-from {
opacity: 0;
.glass-dropdown__menu {
transform: scale(0.9) translateY(-20px);
filter: blur(10px);
}
}
.glass-fade-leave-to {
opacity: 0;
.glass-dropdown__menu {
transform: scale(0.9) translateY(-20px);
filter: blur(10px);
}
}
.submenu-slide-enter-active,
.submenu-slide-leave-active {
transition: all 0.2s ease;
}
.submenu-slide-enter-from {
opacity: 0;
transform: translateX(-10px);
}
.submenu-slide-leave-to {
opacity: 0;
transform: translateX(-10px);
}
</style>

View File

@@ -0,0 +1,391 @@
<template>
<div class="monaco-dropdown" ref="dropdownRef">
<button
@click="toggleDropdown"
class="monaco-dropdown__trigger"
:class="[
`monaco-dropdown__trigger--${variant}`,
`monaco-dropdown__trigger--${size}`,
{ 'monaco-dropdown__trigger--open': isOpen }
]"
:aria-expanded="isOpen"
:aria-haspopup="true"
>
<slot name="trigger">
<span class="monaco-dropdown__trigger-text">{{ label }}</span>
</slot>
<Icon
:name="isOpen ? 'chevron-up' : 'chevron-down'"
class="monaco-dropdown__trigger-icon"
:class="{ 'monaco-dropdown__trigger-icon--rotate': isOpen }"
/>
</button>
<Transition name="dropdown">
<div
v-if="isOpen"
class="monaco-dropdown__content"
:class="[
`monaco-dropdown__content--${variant}`,
`monaco-dropdown__content--${position}`
]"
>
<div class="monaco-dropdown__content-inner">
<div
v-for="(option, index) in options"
:key="option.value || index"
class="monaco-dropdown__item"
:class="{
'monaco-dropdown__item--active': activeIndex === index,
'monaco-dropdown__item--selected': modelValue === option.value,
'monaco-dropdown__item--disabled': option.disabled
}"
@click="!option.disabled && selectOption(option)"
@mouseenter="activeIndex = index"
@mouseleave="activeIndex = -1"
>
<Icon
v-if="option.icon"
:name="option.icon"
class="monaco-dropdown__item-icon"
/>
<span class="monaco-dropdown__item-label">{{ option.label }}</span>
<span v-if="option.shortcut" class="monaco-dropdown__item-shortcut">
{{ option.shortcut }}
</span>
<Icon
v-if="modelValue === option.value"
name="check"
class="monaco-dropdown__item-check"
/>
</div>
</div>
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import Icon from '~/components/ui/Icon.vue'
interface DropdownOption {
label: string
value: string | number
icon?: string
shortcut?: string
disabled?: boolean
}
interface Props {
modelValue?: string | number | null
options: DropdownOption[]
label?: string
variant?: 'glass' | 'solid' | 'gradient' | 'outline'
size?: 'sm' | 'md' | 'lg'
position?: 'bottom' | 'top' | 'left' | 'right'
closeOnSelect?: boolean
}
const props = withDefaults(defineProps<Props>(), {
label: 'Select option',
variant: 'glass',
size: 'md',
position: 'bottom',
closeOnSelect: true
})
const emit = defineEmits<{
'update:modelValue': [value: string | number]
'change': [value: string | number]
'open': []
'close': []
}>()
const dropdownRef = ref<HTMLElement>()
const isOpen = ref(false)
const activeIndex = ref(-1)
const toggleDropdown = () => {
isOpen.value = !isOpen.value
if (isOpen.value) {
emit('open')
} else {
emit('close')
activeIndex.value = -1
}
}
const selectOption = (option: DropdownOption) => {
emit('update:modelValue', option.value)
emit('change', option.value)
if (props.closeOnSelect) {
isOpen.value = false
activeIndex.value = -1
emit('close')
}
}
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
isOpen.value = false
activeIndex.value = -1
emit('close')
}
}
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen.value) {
isOpen.value = false
activeIndex.value = -1
emit('close')
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
document.addEventListener('keydown', handleEscape)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
document.removeEventListener('keydown', handleEscape)
})
</script>
<style scoped lang="scss">
.monaco-dropdown {
position: relative;
display: inline-block;
&__trigger {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
font-weight: 500;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
border: none;
&--glass {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #dc2626;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
&:hover {
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
&--solid {
background: #dc2626;
color: white;
box-shadow: 0 4px 16px rgba(220, 38, 38, 0.2);
&:hover {
background: #b91c1c;
box-shadow: 0 6px 20px rgba(220, 38, 38, 0.3);
transform: translateY(-1px);
}
}
&--gradient {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
color: white;
box-shadow: 0 4px 16px rgba(220, 38, 38, 0.2);
&:hover {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow: 0 6px 20px rgba(220, 38, 38, 0.3);
transform: translateY(-1px);
}
}
&--outline {
background: transparent;
color: #dc2626;
border: 2px solid #dc2626;
&:hover {
background: rgba(220, 38, 38, 0.1);
border-color: #b91c1c;
}
}
&--sm {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
&--md {
padding: 0.5rem 1rem;
font-size: 1rem;
}
&--lg {
padding: 0.75rem 1.25rem;
font-size: 1.125rem;
}
&--open {
z-index: 10;
}
}
&__trigger-icon {
width: 1.25rem;
height: 1.25rem;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&--rotate {
transform: rotate(180deg);
}
}
&__content {
position: absolute;
z-index: 50;
min-width: 200px;
margin-top: 0.5rem;
padding: 0.25rem;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
&--glass {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
&--solid {
background: white;
border: 1px solid #e5e5e5;
}
&--gradient {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.95) 0%,
rgba(255, 255, 255, 0.85) 100%);
backdrop-filter: blur(20px);
border: 1px solid rgba(220, 38, 38, 0.1);
}
&--outline {
background: white;
border: 2px solid #dc2626;
}
&--bottom {
top: 100%;
left: 0;
right: 0;
}
&--top {
bottom: 100%;
left: 0;
right: 0;
}
&--left {
right: 100%;
top: 0;
margin-right: 0.5rem;
margin-top: 0;
}
&--right {
left: 100%;
top: 0;
margin-left: 0.5rem;
margin-top: 0;
}
}
&__item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
color: #27272a;
&:hover:not(&--disabled) {
background: rgba(220, 38, 38, 0.1);
color: #dc2626;
}
&--active:not(&--disabled) {
background: rgba(220, 38, 38, 0.05);
}
&--selected {
color: #dc2626;
font-weight: 600;
}
&--disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
&__item-icon {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
}
&__item-label {
flex: 1;
font-size: 0.875rem;
}
&__item-shortcut {
font-size: 0.75rem;
color: #71717a;
margin-left: auto;
}
&__item-check {
width: 1rem;
height: 1rem;
color: #dc2626;
margin-left: auto;
}
}
// Transition animations
.dropdown-enter-active,
.dropdown-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.dropdown-enter-from {
opacity: 0;
transform: translateY(-10px) scale(0.95);
filter: blur(4px);
}
.dropdown-leave-to {
opacity: 0;
transform: translateY(-10px) scale(0.95);
filter: blur(4px);
}
</style>

753
Design/design-system.md Normal file
View File

@@ -0,0 +1,753 @@
# MonacoUSA Portal Design System
## 🎨 Visual Identity
### Brand Colors
#### Primary Palette
```scss
// Monaco Red Spectrum
$monaco-red-50: #fef2f2;
$monaco-red-100: #fee2e2;
$monaco-red-200: #fecaca;
$monaco-red-300: #fca5a5;
$monaco-red-400: #f87171;
$monaco-red-500: #ef4444;
$monaco-red-600: #dc2626; // Primary Brand Color
$monaco-red-700: #b91c1c;
$monaco-red-800: #991b1b;
$monaco-red-900: #7f1d1d;
// Neutral Palette
$gray-50: #fafafa;
$gray-100: #f4f4f5;
$gray-200: #e4e4e7;
$gray-300: #d4d4d8;
$gray-400: #a1a1aa;
$gray-500: #71717a;
$gray-600: #52525b;
$gray-700: #3f3f46;
$gray-800: #27272a;
$gray-900: #18181b;
```
#### Gradient Definitions
```scss
// Primary Gradients
$gradient-monaco: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
$gradient-monaco-light: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
$gradient-monaco-dark: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
// Accent Gradients
$gradient-sunset: linear-gradient(135deg, #dc2626 0%, #f59e0b 100%);
$gradient-wine: linear-gradient(135deg, #991b1b 0%, #4c1d95 100%);
$gradient-royal: linear-gradient(135deg, #dc2626 0%, #1e40af 100%);
// Glass Gradients
$gradient-glass-light: linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.4) 100%);
$gradient-glass-dark: linear-gradient(135deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 100%);
```
### Typography
#### Font Stack
```scss
// Primary Font Family
$font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
// Monospace Font Family
$font-mono: 'Fira Code', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace;
// Display Font (for headers)
$font-display: 'Poppins', $font-sans;
```
#### Type Scale
```scss
// Font Sizes
$text-xs: 0.75rem; // 12px
$text-sm: 0.875rem; // 14px
$text-base: 1rem; // 16px
$text-lg: 1.125rem; // 18px
$text-xl: 1.25rem; // 20px
$text-2xl: 1.5rem; // 24px
$text-3xl: 1.875rem; // 30px
$text-4xl: 2.25rem; // 36px
$text-5xl: 3rem; // 48px
$text-6xl: 3.75rem; // 60px
$text-7xl: 4.5rem; // 72px
// Line Heights
$leading-none: 1;
$leading-tight: 1.25;
$leading-snug: 1.375;
$leading-normal: 1.5;
$leading-relaxed: 1.625;
$leading-loose: 2;
// Font Weights
$font-thin: 100;
$font-light: 300;
$font-normal: 400;
$font-medium: 500;
$font-semibold: 600;
$font-bold: 700;
$font-extrabold: 800;
$font-black: 900;
```
### Spacing System
```scss
// Based on 4px grid
$space-0: 0; // 0px
$space-px: 1px; // 1px
$space-0_5: 0.125rem; // 2px
$space-1: 0.25rem; // 4px
$space-1_5: 0.375rem; // 6px
$space-2: 0.5rem; // 8px
$space-2_5: 0.625rem; // 10px
$space-3: 0.75rem; // 12px
$space-3_5: 0.875rem; // 14px
$space-4: 1rem; // 16px
$space-5: 1.25rem; // 20px
$space-6: 1.5rem; // 24px
$space-7: 1.75rem; // 28px
$space-8: 2rem; // 32px
$space-9: 2.25rem; // 36px
$space-10: 2.5rem; // 40px
$space-12: 3rem; // 48px
$space-14: 3.5rem; // 56px
$space-16: 4rem; // 64px
$space-20: 5rem; // 80px
$space-24: 6rem; // 96px
$space-28: 7rem; // 112px
$space-32: 8rem; // 128px
```
### Border Radius
```scss
$radius-none: 0;
$radius-sm: 0.125rem; // 2px
$radius-base: 0.25rem; // 4px
$radius-md: 0.375rem; // 6px
$radius-lg: 0.5rem; // 8px
$radius-xl: 0.75rem; // 12px
$radius-2xl: 1rem; // 16px
$radius-3xl: 1.5rem; // 24px
$radius-full: 9999px; // Pill shape
```
### Shadows
```scss
// Elevation Shadows
$shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
$shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
$shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
$shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
// Glass Shadows
$shadow-glass: 0 8px 32px rgba(0, 0, 0, 0.1);
$shadow-glass-hover: 0 12px 40px rgba(0, 0, 0, 0.15);
// Monaco Brand Shadows
$shadow-monaco: 0 10px 40px rgba(220, 38, 38, 0.15);
$shadow-monaco-intense: 0 20px 60px rgba(220, 38, 38, 0.25);
```
## 🎯 Component Patterns
### Glass Morphism
```scss
@mixin glass-effect($bg-opacity: 0.7, $blur: 20px) {
background: rgba(255, 255, 255, $bg-opacity);
backdrop-filter: blur($blur);
-webkit-backdrop-filter: blur($blur);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: $shadow-glass;
}
@mixin glass-dark($bg-opacity: 0.7, $blur: 20px) {
background: rgba(0, 0, 0, $bg-opacity);
backdrop-filter: blur($blur);
-webkit-backdrop-filter: blur($blur);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: $shadow-glass;
}
@mixin glass-colored($color: $monaco-red-600, $opacity: 0.1, $blur: 20px) {
background: rgba($color, $opacity);
backdrop-filter: blur($blur);
-webkit-backdrop-filter: blur($blur);
border: 1px solid rgba($color, 0.2);
box-shadow: 0 8px 32px rgba($color, 0.1);
}
```
### Animation Patterns
```scss
// Timing Functions
$ease-in-out-smooth: cubic-bezier(0.4, 0, 0.2, 1);
$ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
$ease-in-out-elastic: cubic-bezier(0.68, -0.55, 0.265, 1.55);
// Duration Scale
$duration-instant: 0ms;
$duration-fast: 150ms;
$duration-base: 300ms;
$duration-slow: 500ms;
$duration-slower: 700ms;
$duration-slowest: 1000ms;
// Animation Mixins
@mixin hover-lift($distance: -2px, $duration: $duration-base) {
transition: transform $duration $ease-in-out-smooth;
&:hover {
transform: translateY($distance);
}
}
@mixin hover-scale($scale: 1.05, $duration: $duration-base) {
transition: transform $duration $ease-in-out-smooth;
&:hover {
transform: scale($scale);
}
}
@mixin pulse-animation($scale: 1.05, $duration: 2s) {
animation: pulse $duration infinite;
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale($scale);
opacity: 0.8;
}
}
}
@mixin float-animation($distance: 10px, $duration: 3s) {
animation: float $duration ease-in-out infinite;
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-$distance);
}
}
}
```
### Button Variants
```scss
// Base Button
@mixin button-base {
display: inline-flex;
align-items: center;
justify-content: center;
padding: $space-2_5 $space-5;
font-weight: $font-medium;
font-size: $text-sm;
line-height: $leading-tight;
border-radius: $radius-xl;
transition: all $duration-base $ease-in-out-smooth;
cursor: pointer;
user-select: none;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
// Primary Button
@mixin button-primary {
@include button-base;
background: $gradient-monaco;
color: white;
border: none;
box-shadow: $shadow-md;
&:hover:not(:disabled) {
box-shadow: $shadow-monaco;
transform: translateY(-1px);
}
&:active:not(:disabled) {
transform: translateY(0);
}
}
// Glass Button
@mixin button-glass {
@include button-base;
@include glass-effect(0.8, 10px);
color: $monaco-red-600;
&:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.9);
box-shadow: $shadow-glass-hover;
transform: translateY(-1px);
}
}
// Ghost Button
@mixin button-ghost {
@include button-base;
background: transparent;
color: $monaco-red-600;
border: 2px solid $monaco-red-600;
&:hover:not(:disabled) {
background: rgba($monaco-red-600, 0.1);
border-color: $monaco-red-700;
}
}
```
### Card Patterns
```scss
// Base Card
@mixin card-base {
border-radius: $radius-2xl;
padding: $space-6;
transition: all $duration-base $ease-in-out-smooth;
}
// Glass Card
@mixin card-glass {
@include card-base;
@include glass-effect;
&:hover {
@include hover-lift(-4px);
box-shadow: $shadow-glass-hover;
}
}
// Gradient Card
@mixin card-gradient {
@include card-base;
background: $gradient-monaco;
color: white;
box-shadow: $shadow-monaco;
&:hover {
@include hover-lift(-4px);
box-shadow: $shadow-monaco-intense;
}
}
// Floating Card
@mixin card-floating {
@include card-base;
background: white;
box-shadow: $shadow-lg;
@include float-animation(5px, 4s);
&:hover {
animation-play-state: paused;
box-shadow: $shadow-xl;
}
}
```
### Form Elements
```scss
// Input Base
@mixin input-base {
width: 100%;
padding: $space-3 $space-4;
font-size: $text-base;
border-radius: $radius-xl;
transition: all $duration-base $ease-in-out-smooth;
&:focus {
outline: none;
}
&::placeholder {
color: $gray-400;
}
}
// Glass Input
@mixin input-glass {
@include input-base;
@include glass-effect(0.6, 10px);
border: 2px solid transparent;
&:focus {
border-color: $monaco-red-600;
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 0 3px rgba($monaco-red-600, 0.1);
}
}
// Floating Label
@mixin floating-label {
position: relative;
label {
position: absolute;
left: $space-4;
top: 50%;
transform: translateY(-50%);
transition: all $duration-base $ease-in-out-smooth;
pointer-events: none;
color: $gray-500;
font-size: $text-base;
}
input:focus + label,
input:not(:placeholder-shown) + label {
top: 0;
transform: translateY(-50%) scale(0.8);
background: white;
padding: 0 $space-2;
color: $monaco-red-600;
}
}
```
## 🎭 Animation Library
### Entrance Animations
```scss
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes rotate-in {
from {
opacity: 0;
transform: rotate(-10deg) scale(0.9);
}
to {
opacity: 1;
transform: rotate(0) scale(1);
}
}
```
### Interaction Animations
```scss
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
25% {
transform: translateX(-5px);
}
75% {
transform: translateX(5px);
}
}
@keyframes wiggle {
0%, 100% {
transform: rotate(0deg);
}
25% {
transform: rotate(-3deg);
}
75% {
transform: rotate(3deg);
}
}
```
### Loading Animations
```scss
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes pulse-ring {
0% {
transform: scale(0.5);
opacity: 1;
}
80%, 100% {
transform: scale(1.5);
opacity: 0;
}
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
.shimmer-effect {
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0) 100%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
```
## 📱 Responsive Design
### Breakpoints
```scss
// Mobile First Breakpoints
$screen-sm: 640px; // Small devices
$screen-md: 768px; // Medium devices
$screen-lg: 1024px; // Large devices
$screen-xl: 1280px; // Extra large devices
$screen-2xl: 1536px; // 2X Extra large devices
// Mixins
@mixin sm {
@media (min-width: $screen-sm) {
@content;
}
}
@mixin md {
@media (min-width: $screen-md) {
@content;
}
}
@mixin lg {
@media (min-width: $screen-lg) {
@content;
}
}
@mixin xl {
@media (min-width: $screen-xl) {
@content;
}
}
@mixin 2xl {
@media (min-width: $screen-2xl) {
@content;
}
}
```
### Container Widths
```scss
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: $space-4;
padding-right: $space-4;
@include sm {
max-width: $screen-sm;
}
@include md {
max-width: $screen-md;
}
@include lg {
max-width: $screen-lg;
}
@include xl {
max-width: $screen-xl;
}
@include 2xl {
max-width: $screen-2xl;
}
}
```
## 🎪 Motion Preferences
```scss
// Respect user's motion preferences
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
// Dark mode support
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #{$gray-900};
--text-primary: #{$gray-50};
--glass-bg: rgba(0, 0, 0, 0.7);
--glass-border: rgba(255, 255, 255, 0.1);
}
}
```
## 🔧 Utility Classes
### Display Utilities
```scss
.hidden { display: none; }
.block { display: block; }
.inline-block { display: inline-block; }
.inline { display: inline; }
.flex { display: flex; }
.inline-flex { display: inline-flex; }
.grid { display: grid; }
// Flexbox Utilities
.items-start { align-items: flex-start; }
.items-center { align-items: center; }
.items-end { align-items: flex-end; }
.justify-start { justify-content: flex-start; }
.justify-center { justify-content: center; }
.justify-end { justify-content: flex-end; }
.justify-between { justify-content: space-between; }
.flex-row { flex-direction: row; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.flex-1 { flex: 1 1 0%; }
.flex-auto { flex: 1 1 auto; }
```
### Spacing Utilities
```scss
// Margin
@each $name, $size in (
'0': $space-0,
'1': $space-1,
'2': $space-2,
'3': $space-3,
'4': $space-4,
'5': $space-5,
'6': $space-6,
'8': $space-8,
'10': $space-10,
'12': $space-12,
'16': $space-16
) {
.m-#{$name} { margin: $size; }
.mt-#{$name} { margin-top: $size; }
.mr-#{$name} { margin-right: $size; }
.mb-#{$name} { margin-bottom: $size; }
.ml-#{$name} { margin-left: $size; }
.mx-#{$name} {
margin-left: $size;
margin-right: $size;
}
.my-#{$name} {
margin-top: $size;
margin-bottom: $size;
}
}
// Padding (same pattern as margin)
```
### Text Utilities
```scss
// Font Size
.text-xs { font-size: $text-xs; }
.text-sm { font-size: $text-sm; }
.text-base { font-size: $text-base; }
.text-lg { font-size: $text-lg; }
.text-xl { font-size: $text-xl; }
.text-2xl { font-size: $text-2xl; }
.text-3xl { font-size: $text-3xl; }
.text-4xl { font-size: $text-4xl; }
// Font Weight
.font-thin { font-weight: $font-thin; }
.font-light { font-weight: $font-light; }
.font-normal { font-weight: $font-normal; }
.font-medium { font-weight: $font-medium; }
.font-semibold { font-weight: $font-semibold; }
.font-bold { font-weight: $font-bold; }
// Text Alignment
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-justify { text-align: justify; }
```
---
*This design system provides the foundation for creating consistent, beautiful, and performant user interfaces across the MonacoUSA Portal.*

View File

@@ -0,0 +1,651 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MonacoUSA Portal - Board Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #fff5f5 0%, #ffffff 25%, #fef2f2 50%, #ffffff 75%, #fff5f5 100%);
}
/* Monaco Gradients */
.monaco-gradient {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
.monaco-gradient-dark {
background: linear-gradient(135deg, #991b1b 0%, #7f1d1d 100%);
}
.monaco-gradient-vibrant {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 50%, #991b1b 100%);
}
/* Glass Effects */
.glass {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.4);
box-shadow: 0 8px 32px 0 rgba(220, 38, 38, 0.08);
}
.glass-red {
background: linear-gradient(135deg, rgba(254, 202, 202, 0.3) 0%, rgba(252, 165, 165, 0.2) 100%);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(220, 38, 38, 0.15);
box-shadow: 0 8px 32px 0 rgba(220, 38, 38, 0.12);
}
.glass-dark {
background: rgba(0, 0, 0, 0.03);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* Animated Elements */
@keyframes slideInRight {
from { transform: translateX(100px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideInUp {
from { transform: translateY(30px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.slide-in-right {
animation: slideInRight 0.8s ease-out;
}
.slide-in-up {
animation: slideInUp 0.6s ease-out;
}
/* Status Indicators */
@keyframes pulse-green {
0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(34, 197, 94, 0); }
}
@keyframes pulse-red {
0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(220, 38, 38, 0); }
}
@keyframes pulse-amber {
0%, 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); }
}
.pulse-green { animation: pulse-green 2s infinite; }
.pulse-red { animation: pulse-red 2s infinite; }
.pulse-amber { animation: pulse-amber 2s infinite; }
/* Card Effects */
.card-3d {
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
transform-style: preserve-3d;
}
.card-3d:hover {
transform: translateY(-10px) rotateX(5deg) scale(1.02);
box-shadow: 0 30px 60px rgba(220, 38, 38, 0.2);
}
/* Gradient Text */
.gradient-text {
background: linear-gradient(135deg, #dc2626 0%, #7f1d1d 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Floating Elements */
@keyframes float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-20px) rotate(2deg); }
}
.floating {
animation: float 6s ease-in-out infinite;
}
/* Shimmer Effect */
@keyframes shimmer {
0% { background-position: -1000px 0; }
100% { background-position: 1000px 0; }
}
.shimmer {
background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100%);
background-size: 1000px 100%;
animation: shimmer 3s infinite;
}
/* Data Grid Styles */
.data-row {
transition: all 0.3s ease;
}
.data-row:hover {
background: linear-gradient(90deg, rgba(220, 38, 38, 0.05) 0%, rgba(220, 38, 38, 0.02) 100%);
transform: translateX(5px);
}
/* Chart Container */
.chart-container {
position: relative;
height: 300px;
}
</style>
</head>
<body class="min-h-screen overflow-x-hidden">
<!-- Animated Background -->
<div class="fixed inset-0 overflow-hidden pointer-events-none">
<div class="absolute -top-40 -right-40 w-80 h-80 bg-red-200 rounded-full opacity-20 blur-3xl floating"></div>
<div class="absolute -bottom-40 -left-40 w-80 h-80 bg-red-100 rounded-full opacity-20 blur-3xl floating" style="animation-delay: 2s;"></div>
<div class="absolute top-1/2 left-1/3 w-60 h-60 bg-red-300 rounded-full opacity-10 blur-3xl floating" style="animation-delay: 4s;"></div>
</div>
<!-- Executive Header -->
<header class="fixed top-0 left-0 right-0 z-50 glass border-b border-red-100">
<div class="px-6 py-4">
<div class="flex items-center justify-between">
<!-- Brand -->
<div class="flex items-center space-x-4">
<div class="relative group">
<div class="absolute inset-0 monaco-gradient rounded-xl blur opacity-50 group-hover:opacity-75 transition-opacity"></div>
<div class="relative w-12 h-12 bg-white rounded-xl overflow-hidden shadow-xl">
<div class="h-1/2 bg-red-600"></div>
<div class="h-1/2 bg-white"></div>
</div>
</div>
<div>
<h1 class="text-2xl font-bold gradient-text">MonacoUSA Portal</h1>
<p class="text-xs text-gray-600 font-medium">Board Executive Dashboard</p>
</div>
</div>
<!-- Center Controls -->
<div class="hidden lg:flex items-center space-x-4">
<div class="flex items-center space-x-2 px-4 py-2 glass-red rounded-full">
<div class="w-2 h-2 bg-green-500 rounded-full pulse-green"></div>
<span class="text-sm font-medium text-gray-700">System Status: Operational</span>
</div>
<div class="flex items-center space-x-2 px-4 py-2 glass rounded-full">
<i data-lucide="users" class="w-4 h-4 text-gray-500"></i>
<span class="text-sm font-medium text-gray-700">287 Active Members</span>
</div>
</div>
<!-- Actions -->
<div class="flex items-center space-x-3">
<button class="px-4 py-2 monaco-gradient text-white rounded-lg font-semibold hover:shadow-lg transition-all flex items-center space-x-2">
<i data-lucide="user-plus" class="w-4 h-4"></i>
<span>Add Member</span>
</button>
<button class="px-4 py-2 glass hover:bg-red-50 rounded-lg font-semibold transition-all flex items-center space-x-2">
<i data-lucide="download" class="w-4 h-4"></i>
<span>Export</span>
</button>
<!-- Profile -->
<div class="flex items-center space-x-3 pl-3 ml-3 border-l border-gray-200">
<div class="text-right">
<p class="text-sm font-semibold text-gray-900">Board Executive</p>
<p class="text-xs text-red-600">Administrative Access</p>
</div>
<div class="relative">
<img src="https://ui-avatars.com/api/?name=Board+Executive&background=dc2626&color=fff&bold=true"
alt="Profile" class="w-11 h-11 rounded-full ring-2 ring-red-200">
<span class="absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-white rounded-full"></span>
</div>
</div>
</div>
</div>
</div>
</header>
<!-- Executive Sidebar -->
<aside class="fixed left-0 top-20 bottom-0 w-72 glass-dark border-r border-red-100/20 overflow-y-auto">
<!-- Quick Stats Panel -->
<div class="p-6 border-b border-red-100/20">
<div class="glass-red rounded-2xl p-4">
<h3 class="text-sm font-semibold text-gray-700 mb-3">Today's Overview</h3>
<div class="grid grid-cols-2 gap-3">
<div class="text-center">
<p class="text-2xl font-bold gradient-text">43</p>
<p class="text-xs text-gray-600">Overdue</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-green-600">$43K</p>
<p class="text-xs text-gray-600">Revenue</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-blue-600">89%</p>
<p class="text-xs text-gray-600">Attendance</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-purple-600">15</p>
<p class="text-xs text-gray-600">New</p>
</div>
</div>
</div>
</div>
<!-- Navigation -->
<nav class="p-4">
<div class="space-y-1">
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-xl monaco-gradient text-white font-medium shadow-lg">
<i data-lucide="shield" class="w-5 h-5"></i>
<span>Executive Overview</span>
</a>
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-red-50 hover:text-red-600 font-medium transition-all">
<i data-lucide="users" class="w-5 h-5"></i>
<span>Member Management</span>
<span class="ml-auto text-xs bg-red-100 text-red-600 px-2 py-1 rounded-full">43</span>
</a>
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-red-50 hover:text-red-600 font-medium transition-all">
<i data-lucide="credit-card" class="w-5 h-5"></i>
<span>Dues & Revenue</span>
</a>
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-red-50 hover:text-red-600 font-medium transition-all">
<i data-lucide="calendar" class="w-5 h-5"></i>
<span>Event Planning</span>
</a>
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-red-50 hover:text-red-600 font-medium transition-all">
<i data-lucide="bar-chart-3" class="w-5 h-5"></i>
<span>Analytics</span>
</a>
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-red-50 hover:text-red-600 font-medium transition-all">
<i data-lucide="mail" class="w-5 h-5"></i>
<span>Communications</span>
<span class="ml-auto w-2 h-2 bg-red-500 rounded-full pulse-red"></span>
</a>
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-red-50 hover:text-red-600 font-medium transition-all">
<i data-lucide="settings" class="w-5 h-5"></i>
<span>Settings</span>
</a>
</div>
</nav>
<!-- Board Access Badge -->
<div class="absolute bottom-4 left-4 right-4">
<div class="monaco-gradient rounded-xl p-4 text-white text-center">
<i data-lucide="shield-check" class="w-8 h-8 mx-auto mb-2"></i>
<p class="text-xs font-semibold">BOARD EXECUTIVE</p>
<p class="text-xs opacity-80">Full Administrative Access</p>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="ml-72 mt-20 p-8">
<!-- Executive Header -->
<div class="mb-8 slide-in-right">
<div class="flex items-center justify-between">
<div>
<h2 class="text-4xl font-bold gradient-text mb-2">Board Executive Dashboard</h2>
<p class="text-gray-600">Real-time organization management and insights</p>
</div>
<div class="flex items-center space-x-4">
<div class="text-right">
<p class="text-sm text-gray-500">Last Updated</p>
<p class="text-lg font-semibold text-gray-900">2 min ago</p>
</div>
<button class="p-3 glass rounded-xl hover:bg-red-50 transition-colors">
<i data-lucide="refresh-cw" class="w-5 h-5 text-gray-700"></i>
</button>
</div>
</div>
</div>
<!-- KPI Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<!-- Total Members KPI -->
<div class="glass rounded-2xl p-6 card-3d slide-in-up">
<div class="flex items-center justify-between mb-4">
<div class="w-14 h-14 monaco-gradient rounded-xl flex items-center justify-center shadow-lg">
<i data-lucide="users" class="w-7 h-7 text-white"></i>
</div>
<div class="flex items-center space-x-1 text-green-600">
<i data-lucide="trending-up" class="w-4 h-4"></i>
<span class="text-sm font-bold">+12%</span>
</div>
</div>
<p class="text-sm text-gray-500 mb-1">Total Members</p>
<p class="text-3xl font-bold text-gray-900">287</p>
<div class="mt-4 flex items-center justify-between text-xs">
<span class="text-gray-500">Target: 300</span>
<div class="w-24 bg-gray-200 rounded-full h-2">
<div class="monaco-gradient h-2 rounded-full" style="width: 95.7%"></div>
</div>
</div>
</div>
<!-- Revenue KPI -->
<div class="glass rounded-2xl p-6 card-3d slide-in-up" style="animation-delay: 0.1s">
<div class="flex items-center justify-between mb-4">
<div class="w-14 h-14 bg-gradient-to-br from-green-500 to-emerald-600 rounded-xl flex items-center justify-center shadow-lg">
<i data-lucide="dollar-sign" class="w-7 h-7 text-white"></i>
</div>
<div class="flex items-center space-x-1 text-green-600">
<i data-lucide="trending-up" class="w-4 h-4"></i>
<span class="text-sm font-bold">+8%</span>
</div>
</div>
<p class="text-sm text-gray-500 mb-1">YTD Revenue</p>
<p class="text-3xl font-bold text-gray-900">$43,050</p>
<div class="mt-4 flex items-center justify-between text-xs">
<span class="text-gray-500">Goal: $50,000</span>
<div class="w-24 bg-gray-200 rounded-full h-2">
<div class="bg-gradient-to-r from-green-500 to-emerald-600 h-2 rounded-full" style="width: 86%"></div>
</div>
</div>
</div>
<!-- Overdue Dues KPI -->
<div class="glass rounded-2xl p-6 card-3d slide-in-up" style="animation-delay: 0.2s">
<div class="flex items-center justify-between mb-4">
<div class="w-14 h-14 bg-gradient-to-br from-amber-500 to-orange-600 rounded-xl flex items-center justify-center shadow-lg">
<i data-lucide="alert-triangle" class="w-7 h-7 text-white"></i>
</div>
<span class="px-2 py-1 bg-amber-100 text-amber-700 text-xs font-bold rounded-full pulse-amber">ACTION</span>
</div>
<p class="text-sm text-gray-500 mb-1">Overdue Dues</p>
<p class="text-3xl font-bold text-gray-900">43</p>
<div class="mt-4 flex items-center justify-between text-xs">
<span class="text-gray-500">Amount: $6,450</span>
<button class="text-amber-600 font-semibold hover:text-amber-700">View All →</button>
</div>
</div>
<!-- Event Success KPI -->
<div class="glass rounded-2xl p-6 card-3d slide-in-up" style="animation-delay: 0.3s">
<div class="flex items-center justify-between mb-4">
<div class="w-14 h-14 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg">
<i data-lucide="calendar-check" class="w-7 h-7 text-white"></i>
</div>
<div class="flex items-center space-x-1 text-green-600">
<i data-lucide="trending-up" class="w-4 h-4"></i>
<span class="text-sm font-bold">+25%</span>
</div>
</div>
<p class="text-sm text-gray-500 mb-1">Avg Attendance</p>
<p class="text-3xl font-bold text-gray-900">89%</p>
<div class="mt-4 flex items-center justify-between text-xs">
<span class="text-gray-500">Last 3 events</span>
<div class="flex -space-x-2">
<div class="w-6 h-6 bg-green-500 rounded-full border-2 border-white"></div>
<div class="w-6 h-6 bg-green-500 rounded-full border-2 border-white"></div>
<div class="w-6 h-6 bg-amber-500 rounded-full border-2 border-white"></div>
</div>
</div>
</div>
</div>
<!-- Priority Actions Banner -->
<div class="mb-8 slide-in-right">
<div class="monaco-gradient-vibrant rounded-2xl p-6 text-white relative overflow-hidden">
<div class="absolute top-0 right-0 w-64 h-64 bg-white rounded-full blur-3xl opacity-10"></div>
<div class="relative z-10 flex items-center justify-between">
<div class="flex items-center space-x-6">
<div class="w-16 h-16 bg-white/20 backdrop-blur rounded-xl flex items-center justify-center">
<i data-lucide="alert-circle" class="w-8 h-8"></i>
</div>
<div>
<h3 class="text-xl font-bold mb-1">43 Members with Overdue Dues</h3>
<p class="text-white/90">Total outstanding amount: $6,450 • Average days overdue: 67</p>
</div>
</div>
<div class="flex space-x-3">
<button class="px-5 py-2.5 bg-white/20 backdrop-blur rounded-lg font-semibold hover:bg-white/30 transition-all">
View Details
</button>
<button class="px-5 py-2.5 bg-white text-red-600 rounded-lg font-semibold hover:shadow-xl transition-all flex items-center space-x-2">
<i data-lucide="send" class="w-4 h-4"></i>
<span>Send Reminders</span>
</button>
</div>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Member Management Panel -->
<div class="lg:col-span-2">
<div class="glass rounded-2xl p-6">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-900">Dues Management</h3>
<div class="flex space-x-2">
<button class="px-3 py-1.5 text-sm font-medium text-white monaco-gradient rounded-lg">Overdue (43)</button>
<button class="px-3 py-1.5 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg">Due Soon (5)</button>
<button class="px-3 py-1.5 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg">Paid (239)</button>
</div>
</div>
<!-- Member Cards -->
<div class="space-y-4">
<!-- Member Card 1 -->
<div class="glass-red rounded-xl p-4 data-row">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<img src="https://ui-avatars.com/api/?name=Annette+Anderson&background=dc2626&color=fff"
alt="Member" class="w-12 h-12 rounded-full ring-2 ring-red-200">
<div>
<h4 class="font-semibold text-gray-900">Annette Anderson</h4>
<div class="flex items-center space-x-3 text-sm text-gray-500">
<span>ID: MUSA-16</span>
<span></span>
<span class="text-red-600 font-medium">85 days overdue</span>
</div>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="text-right">
<p class="text-lg font-bold text-gray-900">$150</p>
<p class="text-xs text-gray-500">Due Jun 6, 2024</p>
</div>
<div class="flex space-x-2">
<button class="p-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
<i data-lucide="check" class="w-4 h-4"></i>
</button>
<button class="p-2 glass hover:bg-gray-50 rounded-lg transition-colors">
<i data-lucide="mail" class="w-4 h-4"></i>
</button>
<button class="p-2 glass hover:bg-gray-50 rounded-lg transition-colors">
<i data-lucide="more-vertical" class="w-4 h-4"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Member Card 2 -->
<div class="glass-red rounded-xl p-4 data-row">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<img src="https://ui-avatars.com/api/?name=Danilo+Copiz&background=dc2626&color=fff"
alt="Member" class="w-12 h-12 rounded-full ring-2 ring-red-200">
<div>
<h4 class="font-semibold text-gray-900">Danilo Copiz</h4>
<div class="flex items-center space-x-3 text-sm text-gray-500">
<span>ID: MUSA-19</span>
<span></span>
<span class="text-red-600 font-medium">85 days overdue</span>
</div>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="text-right">
<p class="text-lg font-bold text-gray-900">$150</p>
<p class="text-xs text-gray-500">Due Jun 6, 2024</p>
</div>
<div class="flex space-x-2">
<button class="p-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
<i data-lucide="check" class="w-4 h-4"></i>
</button>
<button class="p-2 glass hover:bg-gray-50 rounded-lg transition-colors">
<i data-lucide="mail" class="w-4 h-4"></i>
</button>
<button class="p-2 glass hover:bg-gray-50 rounded-lg transition-colors">
<i data-lucide="more-vertical" class="w-4 h-4"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Member Card 3 -->
<div class="glass-red rounded-xl p-4 data-row">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<img src="https://ui-avatars.com/api/?name=Ian+Sosso&background=dc2626&color=fff"
alt="Member" class="w-12 h-12 rounded-full ring-2 ring-red-200">
<div>
<h4 class="font-semibold text-gray-900">Ian Sosso</h4>
<div class="flex items-center space-x-3 text-sm text-gray-500">
<span>ID: MUSA-78</span>
<span></span>
<span class="text-red-600 font-medium">81 days overdue</span>
</div>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="text-right">
<p class="text-lg font-bold text-gray-900">$150</p>
<p class="text-xs text-gray-500">Due Jun 10, 2024</p>
</div>
<div class="flex space-x-2">
<button class="p-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
<i data-lucide="check" class="w-4 h-4"></i>
</button>
<button class="p-2 glass hover:bg-gray-50 rounded-lg transition-colors">
<i data-lucide="mail" class="w-4 h-4"></i>
</button>
<button class="p-2 glass hover:bg-gray-50 rounded-lg transition-colors">
<i data-lucide="more-vertical" class="w-4 h-4"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="mt-6 text-center">
<button class="text-red-600 hover:text-red-700 font-semibold">
View All 43 Overdue Members →
</button>
</div>
</div>
</div>
<!-- Analytics Panel -->
<div class="space-y-6">
<!-- Revenue Chart -->
<div class="glass rounded-2xl p-6">
<h3 class="text-lg font-bold text-gray-900 mb-4">Revenue Trend</h3>
<div class="chart-container">
<canvas id="revenueChart"></canvas>
</div>
</div>
<!-- Quick Actions -->
<div class="glass rounded-2xl p-6">
<h3 class="text-lg font-bold text-gray-900 mb-4">Quick Actions</h3>
<div class="space-y-3">
<button class="w-full flex items-center justify-between p-3 glass-red rounded-xl hover:shadow-md transition-all group">
<div class="flex items-center space-x-3">
<i data-lucide="user-plus" class="w-5 h-5 text-red-600"></i>
<span class="font-medium text-gray-700">Add New Member</span>
</div>
<i data-lucide="arrow-right" class="w-4 h-4 text-gray-400 group-hover:translate-x-1 transition-transform"></i>
</button>
<button class="w-full flex items-center justify-between p-3 glass-red rounded-xl hover:shadow-md transition-all group">
<div class="flex items-center space-x-3">
<i data-lucide="calendar-plus" class="w-5 h-5 text-red-600"></i>
<span class="font-medium text-gray-700">Create Event</span>
</div>
<i data-lucide="arrow-right" class="w-4 h-4 text-gray-400 group-hover:translate-x-1 transition-transform"></i>
</button>
<button class="w-full flex items-center justify-between p-3 glass-red rounded-xl hover:shadow-md transition-all group">
<div class="flex items-center space-x-3">
<i data-lucide="file-text" class="w-5 h-5 text-red-600"></i>
<span class="font-medium text-gray-700">Generate Report</span>
</div>
<i data-lucide="arrow-right" class="w-4 h-4 text-gray-400 group-hover:translate-x-1 transition-transform"></i>
</button>
</div>
</div>
</div>
</div>
</main>
<script>
lucide.createIcons();
// Revenue Chart
const ctx = document.getElementById('revenueChart').getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, 0, 250);
gradient.addColorStop(0, 'rgba(220, 38, 38, 0.3)');
gradient.addColorStop(1, 'rgba(220, 38, 38, 0)');
new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Revenue',
data: [32000, 34000, 35500, 38000, 41000, 43050],
borderColor: '#dc2626',
backgroundColor: gradient,
tension: 0.4,
fill: true,
pointBackgroundColor: '#fff',
pointBorderColor: '#dc2626',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: false,
grid: {
display: false
},
ticks: {
callback: function(value) {
return '$' + (value / 1000) + 'k';
}
}
},
x: {
grid: {
display: false
}
}
}
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,601 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MonacoUSA Portal - Member Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #fef3f3 0%, #ffffff 50%, #fef3f3 100%);
}
/* Monaco Red Gradient Variations */
.monaco-gradient {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
.monaco-gradient-soft {
background: linear-gradient(135deg, #fecaca 0%, #fca5a5 100%);
}
.monaco-gradient-vibrant {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 50%, #991b1b 100%);
}
.monaco-gradient-subtle {
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
}
/* Glass Morphism Effects */
.glass {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px 0 rgba(220, 38, 38, 0.1);
}
.glass-red {
background: rgba(220, 38, 38, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(220, 38, 38, 0.2);
box-shadow: 0 8px 32px 0 rgba(220, 38, 38, 0.15);
}
.glass-dark {
background: rgba(0, 0, 0, 0.05);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Animated Gradients */
.animated-gradient {
background: linear-gradient(270deg, #dc2626, #ef4444, #dc2626);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
}
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
/* Glow Effects */
.glow-red {
box-shadow: 0 0 40px rgba(220, 38, 38, 0.3);
}
.glow-red-intense {
box-shadow: 0 0 60px rgba(220, 38, 38, 0.5),
0 0 100px rgba(220, 38, 38, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.card-hover:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 20px 60px rgba(220, 38, 38, 0.2);
}
/* Floating Animation */
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.floating {
animation: float 6s ease-in-out infinite;
}
/* Pulse Animation */
@keyframes pulse-red {
0%, 100% {
box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7);
}
70% {
box-shadow: 0 0 0 20px rgba(220, 38, 38, 0);
}
}
.pulse {
animation: pulse-red 2s infinite;
}
/* Background Pattern */
.pattern-monaco {
background-color: #ffffff;
background-image:
radial-gradient(circle at 20% 80%, rgba(220, 38, 38, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(220, 38, 38, 0.08) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(220, 38, 38, 0.05) 0%, transparent 50%);
}
/* Sidebar Styles */
.sidebar-item {
position: relative;
transition: all 0.3s ease;
overflow: hidden;
}
.sidebar-item::before {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 0;
background: linear-gradient(90deg, rgba(220, 38, 38, 0.1) 0%, transparent 100%);
transition: width 0.3s ease;
}
.sidebar-item:hover::before {
width: 100%;
}
.sidebar-item.active {
background: linear-gradient(90deg, rgba(220, 38, 38, 0.15) 0%, rgba(220, 38, 38, 0.05) 100%);
border-left: 4px solid #dc2626;
}
/* Number Counter Animation */
@keyframes countUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.count-up {
animation: countUp 0.8s ease-out;
}
/* Shimmer Effect */
@keyframes shimmer {
0% { background-position: -1000px 0; }
100% { background-position: 1000px 0; }
}
.shimmer {
background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%);
background-size: 1000px 100%;
animation: shimmer 3s infinite;
}
</style>
</head>
<body class="pattern-monaco min-h-screen">
<!-- Animated Background Elements -->
<div class="fixed inset-0 overflow-hidden pointer-events-none">
<div class="absolute top-20 left-20 w-72 h-72 bg-red-200 rounded-full opacity-20 blur-3xl floating"></div>
<div class="absolute bottom-20 right-20 w-96 h-96 bg-red-300 rounded-full opacity-15 blur-3xl floating" style="animation-delay: 3s;"></div>
<div class="absolute top-1/2 left-1/2 w-64 h-64 bg-red-100 rounded-full opacity-20 blur-3xl floating" style="animation-delay: 1.5s;"></div>
</div>
<!-- Premium Header -->
<header class="fixed top-0 left-0 right-0 z-50 glass border-b border-white/20">
<div class="px-6 py-4">
<div class="flex items-center justify-between">
<!-- Left Section -->
<div class="flex items-center space-x-4">
<!-- Monaco Flag Logo Animation -->
<div class="relative group">
<div class="absolute inset-0 monaco-gradient rounded-xl blur-lg opacity-50 group-hover:opacity-75 transition-opacity"></div>
<div class="relative w-12 h-12 bg-white rounded-xl overflow-hidden shadow-xl">
<div class="h-1/2 bg-red-600"></div>
<div class="h-1/2 bg-white"></div>
</div>
</div>
<div>
<h1 class="text-2xl font-bold bg-gradient-to-r from-red-600 to-red-800 bg-clip-text text-transparent">
MonacoUSA Portal
</h1>
<p class="text-xs text-gray-600 font-medium">Excellence in Community</p>
</div>
</div>
<!-- Center Search -->
<div class="hidden lg:flex items-center">
<div class="relative">
<input type="text" placeholder="Search members, events, documents..."
class="w-96 px-5 py-2.5 pl-12 glass rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-red-500/30 transition-all">
<i data-lucide="search" class="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400"></i>
<kbd class="absolute right-4 top-1/2 -translate-y-1/2 text-xs bg-gray-100 px-2 py-1 rounded">⌘K</kbd>
</div>
</div>
<!-- Right Section -->
<div class="flex items-center space-x-3">
<!-- Quick Actions -->
<button class="p-2.5 glass rounded-xl hover:bg-red-50 transition-colors group">
<i data-lucide="plus" class="w-5 h-5 text-gray-700 group-hover:text-red-600 transition-colors"></i>
</button>
<!-- Notifications with Badge -->
<button class="relative p-2.5 glass rounded-xl hover:bg-red-50 transition-colors group">
<i data-lucide="bell" class="w-5 h-5 text-gray-700 group-hover:text-red-600 transition-colors"></i>
<span class="absolute top-1 right-1 w-2.5 h-2.5 bg-red-500 rounded-full pulse"></span>
</button>
<!-- Premium User Profile -->
<div class="flex items-center space-x-3 pl-3 ml-3 border-l border-gray-200">
<div class="text-right">
<p class="text-sm font-semibold text-gray-900">Matthew Ciaccio</p>
<p class="text-xs text-gray-500">Gold Member</p>
</div>
<div class="relative group cursor-pointer">
<div class="absolute inset-0 monaco-gradient rounded-full blur opacity-0 group-hover:opacity-50 transition-opacity"></div>
<img src="https://ui-avatars.com/api/?name=Matthew+Ciaccio&background=dc2626&color=fff&bold=true"
alt="Profile" class="relative w-11 h-11 rounded-full ring-2 ring-red-100">
</div>
</div>
</div>
</div>
</div>
</header>
<!-- Modern Sidebar -->
<aside class="fixed left-0 top-20 bottom-0 w-72 glass-dark border-r border-white/10 overflow-y-auto">
<!-- User Status Card -->
<div class="p-6">
<div class="glass-red rounded-2xl p-4 relative overflow-hidden">
<div class="absolute top-0 right-0 w-32 h-32 monaco-gradient rounded-full blur-2xl opacity-30"></div>
<div class="relative">
<div class="flex items-center space-x-3 mb-3">
<div class="w-12 h-12 rounded-xl monaco-gradient flex items-center justify-center">
<i data-lucide="crown" class="w-6 h-6 text-white"></i>
</div>
<div>
<p class="text-sm font-semibold text-gray-900">Gold Member</p>
<p class="text-xs text-gray-600">Since 2019</p>
</div>
</div>
<div class="space-y-2">
<div class="flex justify-between text-xs">
<span class="text-gray-600">Status</span>
<span class="font-semibold text-green-600">Active</span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-600">Points</span>
<span class="font-semibold text-gray-900">2,450</span>
</div>
</div>
</div>
</div>
</div>
<!-- Navigation -->
<nav class="px-4 pb-6">
<div class="space-y-1">
<a href="#" class="sidebar-item active flex items-center space-x-3 px-4 py-3 rounded-xl text-red-600 font-medium">
<i data-lucide="layout-dashboard" class="w-5 h-5"></i>
<span>Dashboard</span>
<span class="ml-auto text-xs bg-red-100 text-red-600 px-2 py-1 rounded-full">New</span>
</a>
<a href="#" class="sidebar-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:text-red-600 font-medium">
<i data-lucide="calendar-days" class="w-5 h-5"></i>
<span>Events</span>
<span class="ml-auto text-xs text-gray-400">12</span>
</a>
<a href="#" class="sidebar-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:text-red-600 font-medium">
<i data-lucide="users" class="w-5 h-5"></i>
<span>Members</span>
</a>
<a href="#" class="sidebar-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:text-red-600 font-medium">
<i data-lucide="wallet" class="w-5 h-5"></i>
<span>Dues & Payments</span>
</a>
<a href="#" class="sidebar-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:text-red-600 font-medium">
<i data-lucide="file-text" class="w-5 h-5"></i>
<span>Documents</span>
</a>
<a href="#" class="sidebar-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:text-red-600 font-medium">
<i data-lucide="message-circle" class="w-5 h-5"></i>
<span>Messages</span>
<span class="ml-auto w-2 h-2 bg-red-500 rounded-full pulse"></span>
</a>
</div>
<div class="mt-6 pt-6 border-t border-gray-200">
<p class="px-4 text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">Quick Actions</p>
<div class="space-y-1">
<button class="w-full sidebar-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:text-red-600 font-medium">
<i data-lucide="download" class="w-5 h-5"></i>
<span>Download ID Card</span>
</button>
<button class="w-full sidebar-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:text-red-600 font-medium">
<i data-lucide="headphones" class="w-5 h-5"></i>
<span>Support</span>
</button>
</div>
</div>
</nav>
</aside>
<!-- Main Content Area -->
<main class="ml-72 mt-20 p-8">
<!-- Hero Welcome Section -->
<div class="mb-8">
<div class="glass rounded-3xl p-8 relative overflow-hidden">
<div class="absolute top-0 right-0 w-96 h-96 monaco-gradient rounded-full blur-3xl opacity-10"></div>
<div class="relative z-10">
<h2 class="text-4xl font-bold text-gray-900 mb-2">Welcome back, Matthew! 👋</h2>
<p class="text-gray-600 text-lg">Your membership is active and you have 2 upcoming events this week.</p>
<!-- Quick Stats Bar -->
<div class="flex items-center space-x-8 mt-6">
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-green-500 rounded-full pulse"></div>
<span class="text-sm text-gray-600">Portal Status: <strong class="text-green-600">Online</strong></span>
</div>
<div class="flex items-center space-x-2">
<i data-lucide="users" class="w-4 h-4 text-gray-400"></i>
<span class="text-sm text-gray-600">Members Online: <strong>34</strong></span>
</div>
<div class="flex items-center space-x-2">
<i data-lucide="calendar" class="w-4 h-4 text-gray-400"></i>
<span class="text-sm text-gray-600">Next Event: <strong>Dec 15</strong></span>
</div>
</div>
</div>
</div>
</div>
<!-- Premium Stats Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<!-- Membership Card -->
<div class="glass rounded-2xl p-6 card-hover relative overflow-hidden group">
<div class="absolute inset-0 monaco-gradient opacity-0 group-hover:opacity-10 transition-opacity"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-green-400 to-green-600 flex items-center justify-center shadow-lg">
<i data-lucide="check-circle" class="w-6 h-6 text-white"></i>
</div>
<span class="text-xs font-bold text-green-600 bg-green-100 px-3 py-1 rounded-full">ACTIVE</span>
</div>
<p class="text-sm text-gray-500 mb-1">Membership Status</p>
<p class="text-3xl font-bold text-gray-900 count-up">Active</p>
<div class="mt-4 pt-4 border-t border-gray-100">
<div class="flex items-center justify-between text-xs">
<span class="text-gray-500">Expires</span>
<span class="font-semibold text-gray-700">Dec 31, 2024</span>
</div>
</div>
</div>
</div>
<!-- Dues Card -->
<div class="glass rounded-2xl p-6 card-hover relative overflow-hidden group">
<div class="absolute inset-0 animated-gradient opacity-0 group-hover:opacity-10 transition-opacity"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 rounded-xl monaco-gradient flex items-center justify-center shadow-lg">
<i data-lucide="credit-card" class="w-6 h-6 text-white"></i>
</div>
<span class="text-xs font-bold text-amber-600 bg-amber-100 px-3 py-1 rounded-full shimmer">DUE SOON</span>
</div>
<p class="text-sm text-gray-500 mb-1">Annual Dues</p>
<p class="text-3xl font-bold text-gray-900 count-up">$150</p>
<div class="mt-4 pt-4 border-t border-gray-100">
<button class="w-full py-2 monaco-gradient text-white rounded-lg font-semibold hover:shadow-lg transition-all">
Pay Now
</button>
</div>
</div>
</div>
<!-- Events Card -->
<div class="glass rounded-2xl p-6 card-hover relative overflow-hidden group">
<div class="absolute inset-0 monaco-gradient opacity-0 group-hover:opacity-10 transition-opacity"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-indigo-400 to-indigo-600 flex items-center justify-center shadow-lg">
<i data-lucide="calendar-check" class="w-6 h-6 text-white"></i>
</div>
<span class="text-xs font-bold text-indigo-600 bg-indigo-100 px-3 py-1 rounded-full">THIS YEAR</span>
</div>
<p class="text-sm text-gray-500 mb-1">Events Attended</p>
<p class="text-3xl font-bold text-gray-900 count-up">12</p>
<div class="mt-4 pt-4 border-t border-gray-100">
<div class="flex items-center justify-between text-xs">
<span class="text-gray-500">Upcoming</span>
<span class="font-semibold text-gray-700">3 events</span>
</div>
</div>
</div>
</div>
<!-- Points Card -->
<div class="glass rounded-2xl p-6 card-hover relative overflow-hidden group">
<div class="absolute inset-0 monaco-gradient opacity-0 group-hover:opacity-10 transition-opacity"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-400 to-purple-600 flex items-center justify-center shadow-lg">
<i data-lucide="trophy" class="w-6 h-6 text-white"></i>
</div>
<span class="text-xs font-bold text-purple-600 bg-purple-100 px-3 py-1 rounded-full">TOP 10%</span>
</div>
<p class="text-sm text-gray-500 mb-1">Member Points</p>
<p class="text-3xl font-bold text-gray-900 count-up">2,450</p>
<div class="mt-4 pt-4 border-t border-gray-100">
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="monaco-gradient h-2 rounded-full" style="width: 78%"></div>
</div>
<p class="text-xs text-gray-500 mt-2">550 points to Platinum</p>
</div>
</div>
</div>
</div>
<!-- Featured Event Banner -->
<div class="mb-8">
<div class="monaco-gradient rounded-3xl p-8 relative overflow-hidden glow-red">
<div class="absolute top-0 right-0 w-96 h-96 bg-white rounded-full blur-3xl opacity-10"></div>
<div class="relative z-10 flex items-center justify-between">
<div class="text-white">
<div class="flex items-center space-x-3 mb-3">
<div class="w-10 h-10 bg-white/20 backdrop-blur rounded-lg flex items-center justify-center">
<i data-lucide="sparkles" class="w-5 h-5"></i>
</div>
<span class="text-sm font-semibold bg-white/20 px-3 py-1 rounded-full">FEATURED EVENT</span>
</div>
<h3 class="text-3xl font-bold mb-2">Annual Monaco Gala 2024</h3>
<p class="text-white/90 mb-4 max-w-xl">Join us for an elegant evening celebrating Monaco's heritage and culture. Black tie event with dinner, entertainment, and networking.</p>
<div class="flex items-center space-x-6 text-sm">
<div class="flex items-center space-x-2">
<i data-lucide="calendar" class="w-4 h-4"></i>
<span>December 15, 2024</span>
</div>
<div class="flex items-center space-x-2">
<i data-lucide="clock" class="w-4 h-4"></i>
<span>7:00 PM - 11:00 PM</span>
</div>
<div class="flex items-center space-x-2">
<i data-lucide="map-pin" class="w-4 h-4"></i>
<span>Monaco Embassy, Washington DC</span>
</div>
</div>
</div>
<div class="flex flex-col space-y-3">
<button class="px-6 py-3 bg-white text-red-600 rounded-xl font-semibold hover:shadow-xl transition-all">
RSVP Now
</button>
<button class="px-6 py-3 bg-white/20 backdrop-blur text-white rounded-xl font-semibold hover:bg-white/30 transition-all">
View Details
</button>
</div>
</div>
</div>
</div>
<!-- Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Recent Activity Feed -->
<div class="lg:col-span-2">
<div class="glass rounded-2xl p-6">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-900">Recent Activity</h3>
<button class="text-sm text-red-600 hover:text-red-700 font-medium">View All</button>
</div>
<div class="space-y-4">
<!-- Activity Item -->
<div class="flex items-start space-x-4 p-4 hover:bg-gray-50 rounded-xl transition-colors">
<div class="w-10 h-10 rounded-xl bg-green-100 flex items-center justify-center flex-shrink-0">
<i data-lucide="user-plus" class="w-5 h-5 text-green-600"></i>
</div>
<div class="flex-1">
<p class="text-sm font-medium text-gray-900">New member joined</p>
<p class="text-sm text-gray-600 mt-1">Sarah Johnson joined as a Silver member</p>
<p class="text-xs text-gray-400 mt-2">2 hours ago</p>
</div>
</div>
<!-- Activity Item -->
<div class="flex items-start space-x-4 p-4 hover:bg-gray-50 rounded-xl transition-colors">
<div class="w-10 h-10 rounded-xl bg-blue-100 flex items-center justify-center flex-shrink-0">
<i data-lucide="calendar" class="w-5 h-5 text-blue-600"></i>
</div>
<div class="flex-1">
<p class="text-sm font-medium text-gray-900">Event reminder</p>
<p class="text-sm text-gray-600 mt-1">Wine Tasting Event is coming up in 3 days</p>
<p class="text-xs text-gray-400 mt-2">5 hours ago</p>
</div>
</div>
<!-- Activity Item -->
<div class="flex items-start space-x-4 p-4 hover:bg-gray-50 rounded-xl transition-colors">
<div class="w-10 h-10 rounded-xl monaco-gradient flex items-center justify-center flex-shrink-0">
<i data-lucide="trophy" class="w-5 h-5 text-white"></i>
</div>
<div class="flex-1">
<p class="text-sm font-medium text-gray-900">Achievement unlocked!</p>
<p class="text-sm text-gray-600 mt-1">You've attended 10+ events this year</p>
<p class="text-xs text-gray-400 mt-2">1 day ago</p>
</div>
</div>
</div>
</div>
</div>
<!-- Member Spotlight -->
<div>
<div class="glass rounded-2xl p-6">
<h3 class="text-xl font-bold text-gray-900 mb-6">Member Spotlight</h3>
<div class="text-center">
<div class="relative inline-block mb-4">
<img src="https://ui-avatars.com/api/?name=John+Smith&background=dc2626&color=fff&size=128"
alt="Member" class="w-24 h-24 rounded-full ring-4 ring-red-100">
<div class="absolute bottom-0 right-0 w-7 h-7 bg-green-500 rounded-full border-3 border-white flex items-center justify-center">
<i data-lucide="check" class="w-4 h-4 text-white"></i>
</div>
</div>
<h4 class="text-lg font-semibold text-gray-900">John Smith</h4>
<p class="text-sm text-gray-500 mb-1">Board Member</p>
<div class="inline-flex items-center space-x-2 px-3 py-1 bg-gradient-to-r from-amber-400 to-orange-500 text-white rounded-full text-xs font-semibold mt-2">
<i data-lucide="award" class="w-3 h-3"></i>
<span>PLATINUM MEMBER</span>
</div>
<div class="mt-6 pt-6 border-t border-gray-100">
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<p class="text-2xl font-bold text-gray-900">15</p>
<p class="text-xs text-gray-500">Years</p>
</div>
<div>
<p class="text-2xl font-bold text-gray-900">89</p>
<p class="text-xs text-gray-500">Events</p>
</div>
<div>
<p class="text-2xl font-bold text-gray-900">5.2k</p>
<p class="text-xs text-gray-500">Points</p>
</div>
</div>
</div>
<button class="w-full mt-6 px-4 py-2 monaco-gradient text-white rounded-lg font-semibold hover:shadow-lg transition-all">
View Profile
</button>
</div>
</div>
</div>
</div>
</main>
<!-- Floating Action Button -->
<div class="fixed bottom-8 right-8">
<button class="w-14 h-14 monaco-gradient rounded-full shadow-2xl hover:shadow-3xl transition-all glow-red-intense flex items-center justify-center group">
<i data-lucide="plus" class="w-6 h-6 text-white group-hover:rotate-45 transition-transform"></i>
</button>
</div>
<script>
lucide.createIcons();
// Add number animation on scroll
const observerOptions = {
threshold: 0.5
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('count-up');
}
});
}, observerOptions);
document.querySelectorAll('.count-up').forEach(el => {
observer.observe(el);
});
</script>
</body>
</html>