Implement comprehensive design system improvements
Build And Push Image / docker (push) Failing after 1m14s
Details
- Created new design-system-v2.scss with modern design tokens
- Enhanced Vuetify theme configuration with refined colors
- Added professional dashboard styles component
- Improved typography, spacing, and visual hierarchy
- Implemented glass morphism effects with better contrast
- Added smooth animations and micro-interactions
- Improved responsive design for mobile devices
- Enhanced stat cards, data tables, and navigation
- Fixed color contrast issues identified in audit
- Added professional gradients and shadows
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
@ -44,7 +44,18 @@
|
|||
"mcp__playwright__browser_fill_form",
|
||||
"mcp__zen__debug",
|
||||
"Bash(Copy-Item -Path \"Z:\\Repos\\monacousa-portal\\design-mockups\\pages\\auth\\ProfessionalLogin.vue\" -Destination \"Z:\\Repos\\monacousa-portal\\pages\\mockups\\login.vue\")",
|
||||
"Bash(Remove-Item -Path \"Z:\\Repos\\monacousa-portal\\pages\\mockups\" -Recurse -Force)"
|
||||
"Bash(Remove-Item -Path \"Z:\\Repos\\monacousa-portal\\pages\\mockups\" -Recurse -Force)",
|
||||
"mcp__zen__analyze",
|
||||
"Read(/Z:\\Repos\\monacousa-portal\\.playwright-mcp/**)",
|
||||
"Read(/Z:\\Repos\\monacousa-portal\\.playwright-mcp/**)",
|
||||
"Read(/Z:\\Repos\\monacousa-portal\\assets\\scss/**)",
|
||||
"Bash(New-Item -Path \"Z:\\Repos\\monacousa-portal\\assets\\scss\\design-system-v2.scss\" -ItemType File -Force)",
|
||||
"Read(/Z:\\Repos\\monacousa-portal\\assets\\scss/**)",
|
||||
"Read(/Z:\\Repos\\monacousa-portal/**)",
|
||||
"Read(/Z:\\Repos\\monacousa-portal/**)",
|
||||
"Read(/Z:\\Repos\\monacousa-portal\\components\\ui/**)",
|
||||
"Read(/Z:\\Repos\\monacousa-portal\\components\\ui/**)",
|
||||
"Read(/Z:\\Repos\\monacousa-portal\\pages\\admin\\dashboard/**)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 539 KiB |
|
After Width: | Height: | Size: 558 KiB |
|
After Width: | Height: | Size: 567 KiB |
|
After Width: | Height: | Size: 522 KiB |
|
After Width: | Height: | Size: 527 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 527 KiB |
|
After Width: | Height: | Size: 540 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 526 KiB |
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 602 KiB |
|
After Width: | Height: | Size: 563 KiB |
|
After Width: | Height: | Size: 539 KiB |
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 527 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 565 KiB |
|
After Width: | Height: | Size: 657 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
480
Design/README.md
|
|
@ -1,480 +0,0 @@
|
|||
# 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*
|
||||
|
|
@ -1,510 +0,0 @@
|
|||
<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>
|
||||
|
|
@ -1,713 +0,0 @@
|
|||
<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>
|
||||
|
|
@ -1,391 +0,0 @@
|
|||
<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>
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
# Glass Morphism Implementation Guide
|
||||
|
||||
## Overview
|
||||
This document details the glass morphism design patterns implemented in the MonacoUSA Portal, following the design philosophy outlined in design-system.md.
|
||||
|
||||
## Implementation Date
|
||||
December 2024
|
||||
|
||||
## Latest Update
|
||||
January 2025 - Enhanced Sidebar Design with Fixed Width
|
||||
|
||||
## Key Changes
|
||||
|
||||
### 1. Global SCSS Architecture
|
||||
Created `assets/scss/main.scss` with comprehensive design system implementation:
|
||||
- Monaco red color spectrum variables
|
||||
- Glass morphism mixins
|
||||
- Animation utilities
|
||||
- Component classes
|
||||
- Responsive breakpoints
|
||||
|
||||
### 2. Layout Transformations
|
||||
|
||||
#### Enhanced Sidebar Design (All Layouts)
|
||||
- **Fixed Width**: 280px permanent sidebar (non-collapsible)
|
||||
- **Enhanced Glass Effects**: 30px blur with improved opacity
|
||||
- **No Hamburger Menu**: Removed collapse/expand functionality entirely
|
||||
- **Vertical Profile Card**: Small avatar stacked above user info
|
||||
- **Animated Navigation**: Hover effects with smooth transitions
|
||||
- **Glass Badges**: Consistent styling across all access levels
|
||||
- **Shimmer Animation**: Logo with subtle shimmer effect
|
||||
|
||||
#### Admin Layout (`layouts/admin.vue`)
|
||||
- **Enhanced Glass Navigation**: Fixed 280px width with enhanced blur
|
||||
- **Gradient App Bar**: Dark gradient from monaco-red-600 to monaco-red-900
|
||||
- **Glass Icon Buttons**: Semi-transparent with hover effects
|
||||
- **Profile Card**: Vertical layout with admin-specific menu options
|
||||
|
||||
#### Board Layout (`layouts/board.vue`)
|
||||
- **Enhanced Glass Navigation**: Consistent 280px fixed width
|
||||
- **Gradient App Bar**: Softer gradient from monaco-red to brown tones
|
||||
- **Glass Search Dialog**: Search overlay with glass card styling
|
||||
- **Profile Card**: Board member profile with vertical layout
|
||||
|
||||
#### Member Layout (`layouts/member.vue`)
|
||||
- **Enhanced Glass Navigation**: Same 280px fixed width design
|
||||
- **Gradient App Bar**: Lightest gradient for member access level
|
||||
- **Streamlined Navigation**: Fewer menu items with clear hierarchy
|
||||
- **Profile Card**: Member profile with consistent vertical layout
|
||||
|
||||
## Glass Morphism Patterns
|
||||
|
||||
### Core Mixins
|
||||
|
||||
```scss
|
||||
// Enhanced glass effect with stronger blur
|
||||
@mixin enhanced-glass($opacity: 0.9, $blur: 30px) {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(255, 255, 255, $opacity * 0.95),
|
||||
rgba(255, 255, 255, $opacity * 0.85),
|
||||
rgba(255, 255, 255, $opacity * 0.75)
|
||||
);
|
||||
backdrop-filter: blur($blur) saturate(180%);
|
||||
-webkit-backdrop-filter: blur($blur) saturate(180%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
box-shadow:
|
||||
0 8px 32px 0 rgba(31, 38, 135, 0.37),
|
||||
inset 0 1px 2px rgba(255, 255, 255, 0.6),
|
||||
inset 0 -1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
```
|
||||
|
||||
### Component Classes
|
||||
|
||||
#### Glass Cards
|
||||
- `.glass-card`: Base glass effect card
|
||||
- `.glass-card--dark`: Dark variant for dark backgrounds
|
||||
- `.glass-card--colored`: Monaco red tinted glass
|
||||
|
||||
#### Navigation Items
|
||||
- `.glass-nav-item`: Main navigation items with hover effects
|
||||
- `.glass-nav-item-sub`: Sub-navigation items with reduced opacity
|
||||
- Active states include gradient backgrounds and left border accent
|
||||
|
||||
#### Buttons
|
||||
- `.glass-icon-btn`: Icon buttons with glass effect
|
||||
- `.monaco-btn--primary`: Primary buttons with Monaco gradient
|
||||
- `.monaco-btn--glass`: Glass variant buttons
|
||||
|
||||
## Visual Hierarchy
|
||||
|
||||
### Access Level Differentiation
|
||||
1. **Admin**: Darkest gradients (monaco-red-900 range)
|
||||
2. **Board**: Medium gradients (monaco-red-700 range)
|
||||
3. **Member**: Lightest gradients (monaco-red-500 range)
|
||||
|
||||
### Depth & Layering
|
||||
- Navigation drawer: Fixed 280px width, 90% opacity, 30px blur
|
||||
- Profile card: Vertical layout with small avatar
|
||||
- Dropdowns: 95% opacity, 20px blur
|
||||
- Cards: 80% opacity, 15px blur
|
||||
- Buttons: 10-20% opacity, 10px blur
|
||||
- No collapsible functionality - permanent fixed sidebar
|
||||
|
||||
## Animation Patterns
|
||||
|
||||
### Float Animation
|
||||
```scss
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
```
|
||||
Applied to logo for subtle movement.
|
||||
|
||||
### Hover Effects
|
||||
- Navigation items: translateX(2px) on hover
|
||||
- Buttons: translateY(-1px) with shadow enhancement
|
||||
- Cards: translateY(-2px) with increased shadow
|
||||
|
||||
## Color Usage
|
||||
|
||||
### Primary Monaco Red Spectrum
|
||||
- `#dc2626` - Primary brand color
|
||||
- `#b91c1c` - Dark accent
|
||||
- `#ef4444` - Light accent
|
||||
- `#991b1b` - Deep red for admin
|
||||
- `#7f1d1d` - Darkest tone
|
||||
|
||||
### Gradients
|
||||
- **Monaco Gradient**: `linear-gradient(135deg, #dc2626 0%, #b91c1c 100%)`
|
||||
- **Admin Gradient**: Darker tones for authority
|
||||
- **Board Gradient**: Balanced mid-tones
|
||||
- **Member Gradient**: Lighter, welcoming tones
|
||||
|
||||
## Responsive Considerations
|
||||
|
||||
### Breakpoints
|
||||
- Mobile: < 640px
|
||||
- Tablet: 640px - 1024px
|
||||
- Desktop: > 1024px
|
||||
|
||||
### Mobile Optimizations
|
||||
- Reduced blur effects for performance
|
||||
- Simplified gradients
|
||||
- Adjusted spacing and margins
|
||||
- Auto-closing navigation drawer
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### CSS Optimizations
|
||||
- Hardware acceleration with `transform: translateZ(0)`
|
||||
- Will-change property for animated elements
|
||||
- Reduced motion preferences respected
|
||||
|
||||
### Blur Performance
|
||||
- Limited blur radius to maximum 30px
|
||||
- Selective application on key elements
|
||||
- Fallback styles for non-supporting browsers
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
### Supported Browsers
|
||||
- Chrome 90+
|
||||
- Firefox 88+
|
||||
- Safari 14+
|
||||
- Edge 90+
|
||||
|
||||
### Fallbacks
|
||||
- Solid backgrounds for browsers without backdrop-filter
|
||||
- Standard box-shadows without blur
|
||||
- Graceful degradation for older browsers
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Completed
|
||||
- [x] Global SCSS architecture
|
||||
- [x] Admin layout glass morphism
|
||||
- [x] Board layout glass morphism
|
||||
- [x] Member layout glass morphism
|
||||
- [x] Monaco color system integration
|
||||
- [x] Animation patterns
|
||||
- [x] Responsive design
|
||||
- [x] Browser compatibility
|
||||
|
||||
### Future Enhancements
|
||||
- [ ] Component library with glass variants
|
||||
- [ ] Dark mode glass effects
|
||||
- [ ] Advanced animation sequences
|
||||
- [ ] Performance monitoring
|
||||
- [ ] A/B testing for user preference
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
### When to Use Glass Effects
|
||||
1. **Primary UI Elements**: Navigation, headers, cards
|
||||
2. **Overlays**: Modals, dialogs, dropdowns
|
||||
3. **Interactive Elements**: Buttons, form fields
|
||||
4. **Status Indicators**: Badges, chips, alerts
|
||||
|
||||
### When to Avoid
|
||||
1. **Body Text Areas**: Maintain readability
|
||||
2. **High-Frequency Updates**: Performance consideration
|
||||
3. **Critical Actions**: Ensure clarity over aesthetics
|
||||
4. **Accessibility Concerns**: Provide alternatives
|
||||
|
||||
## Maintenance Notes
|
||||
|
||||
### File Locations
|
||||
- **SCSS**: `assets/scss/main.scss`
|
||||
- **Layouts**: `layouts/admin.vue`, `layouts/board.vue`, `layouts/member.vue`
|
||||
- **Config**: `nuxt.config.ts` (theme configuration)
|
||||
|
||||
### Update Process
|
||||
1. Modify SCSS variables in main.scss
|
||||
2. Test across all layouts
|
||||
3. Verify responsive behavior
|
||||
4. Check browser compatibility
|
||||
5. Update this documentation
|
||||
|
||||
## Related Documentation
|
||||
- [Design System](./design-system.md)
|
||||
- [Implementation Guide](./README.md)
|
||||
- [Component Architecture](../components/README.md)
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: December 2024*
|
||||
*Implementation Version: 1.0.0*
|
||||
|
|
@ -1,651 +0,0 @@
|
|||
<!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>
|
||||
|
|
@ -1,601 +0,0 @@
|
|||
<!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>
|
||||
|
|
@ -0,0 +1,472 @@
|
|||
// ============================================
|
||||
// Dashboard Component Styles
|
||||
// Professional enhancements for all dashboards
|
||||
// ============================================
|
||||
|
||||
// Dashboard Container
|
||||
.admin-dashboard,
|
||||
.board-dashboard,
|
||||
.member-dashboard {
|
||||
padding: 2rem;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #fafafa 0%, #f4f4f5 100%);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Dashboard Header
|
||||
.dashboard-header {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&.glass-header {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 24px;
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(220, 38, 38, 0.03) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: -0.02em;
|
||||
|
||||
&.text-gradient {
|
||||
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-subtitle {
|
||||
font-size: 1.125rem;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Stat Cards
|
||||
.stat-card {
|
||||
height: 100%;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
.stat-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
line-height: 1.2;
|
||||
margin: 0.5rem 0;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-6px) scale(1.02);
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.15),
|
||||
0 10px 30px rgba(220, 38, 38, 0.1);
|
||||
}
|
||||
|
||||
.v-avatar {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(var(--v-theme-on-surface), 0.05) 0%,
|
||||
rgba(var(--v-theme-on-surface), 0.02) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Glass Cards
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.88) !important;
|
||||
backdrop-filter: blur(16px) saturate(180%) !important;
|
||||
-webkit-backdrop-filter: blur(16px) saturate(180%) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||
box-shadow:
|
||||
0 10px 40px rgba(0, 0, 0, 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.6) !important;
|
||||
|
||||
&:hover {
|
||||
box-shadow:
|
||||
0 20px 60px rgba(0, 0, 0, 0.12),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Bento Grid
|
||||
.bento-grid {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(12, 1fr) !important;
|
||||
gap: 1.5rem !important;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.bento-item {
|
||||
position: relative;
|
||||
|
||||
&--small {
|
||||
grid-column: span 3 !important;
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
grid-column: span 6 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-column: span 12 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--medium {
|
||||
grid-column: span 4 !important;
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
grid-column: span 6 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-column: span 12 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--large {
|
||||
grid-column: span 6 !important;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-column: span 12 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--xlarge {
|
||||
grid-column: span 8 !important;
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
grid-column: span 12 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--full {
|
||||
grid-column: span 12 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Data Tables
|
||||
.v-data-table {
|
||||
background: transparent !important;
|
||||
|
||||
.v-data-table__wrapper {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(220, 38, 38, 0.03) 0%,
|
||||
rgba(185, 28, 28, 0.01) 100%);
|
||||
|
||||
th {
|
||||
font-weight: 600 !important;
|
||||
font-size: 0.75rem !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #64748b !important;
|
||||
padding: 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(220, 38, 38, 0.02) !important;
|
||||
|
||||
td {
|
||||
color: #1f2937 !important;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 1rem !important;
|
||||
font-size: 0.875rem;
|
||||
color: #475569;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Buttons in Dashboards
|
||||
.dashboard-action-btn {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
transform: translate(-50%, -50%);
|
||||
transition: width 0.6s, height 0.6s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 30px rgba(220, 38, 38, 0.2);
|
||||
|
||||
&::before {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Activity Timeline Enhancement
|
||||
.activity-timeline {
|
||||
.v-timeline-item {
|
||||
&::before {
|
||||
background: linear-gradient(180deg,
|
||||
rgba(220, 38, 38, 0.1) 0%,
|
||||
transparent 100%);
|
||||
}
|
||||
|
||||
.v-timeline-item__dot {
|
||||
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Quick Actions Enhancement
|
||||
.quick-actions-card {
|
||||
.v-btn {
|
||||
margin: 0.25rem;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Loading States
|
||||
.skeleton-loader {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0.5) 25%,
|
||||
rgba(255, 255, 255, 0.8) 50%,
|
||||
rgba(255, 255, 255, 0.5) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) rotate(0deg);
|
||||
}
|
||||
33% {
|
||||
transform: translate(30px, -30px) rotate(120deg);
|
||||
}
|
||||
66% {
|
||||
transform: translate(-20px, 20px) rotate(240deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Animated Entrance
|
||||
.animated-entrance {
|
||||
animation: slideInUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Professional Typography in Dashboards
|
||||
.dashboard-section-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin-bottom: 1.5rem;
|
||||
position: relative;
|
||||
padding-left: 1rem;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 24px;
|
||||
background: linear-gradient(180deg, #dc2626 0%, #b91c1c 100%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Status Badges Enhancement
|
||||
.status-badge {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
&--active {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(34, 197, 94, 0.1) 0%,
|
||||
rgba(34, 197, 94, 0.05) 100%);
|
||||
color: #16a34a;
|
||||
border: 1px solid rgba(34, 197, 94, 0.2);
|
||||
}
|
||||
|
||||
&--pending {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(245, 158, 11, 0.1) 0%,
|
||||
rgba(245, 158, 11, 0.05) 100%);
|
||||
color: #ca8a04;
|
||||
border: 1px solid rgba(245, 158, 11, 0.2);
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(107, 114, 128, 0.1) 0%,
|
||||
rgba(107, 114, 128, 0.05) 100%);
|
||||
color: #6b7280;
|
||||
border: 1px solid rgba(107, 114, 128, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// Chart Card Enhancement
|
||||
.chart-card {
|
||||
.chart-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
|
||||
.chart-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.chart-subtitle {
|
||||
font-size: 0.875rem;
|
||||
color: #64748b;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
padding: 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive Improvements
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-header {
|
||||
padding: 2rem 1rem;
|
||||
|
||||
.dashboard-title {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.dashboard-subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bento-grid {
|
||||
gap: 1rem !important;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
.stat-value {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dark Mode Support
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.admin-dashboard,
|
||||
.board-dashboard,
|
||||
.member-dashboard {
|
||||
background: linear-gradient(135deg, #18181b 0%, #27272a 100%);
|
||||
}
|
||||
|
||||
.dashboard-header.glass-header {
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(30, 30, 30, 0.88) !important;
|
||||
border-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.dashboard-title.text-gradient {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.stat-value,
|
||||
.dashboard-section-title {
|
||||
color: #f4f4f5;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,548 @@
|
|||
// Monaco USA Portal - Design System v2.0
|
||||
// Addressing critical issues from visual audit
|
||||
|
||||
// ============================================
|
||||
// 1. COLOR PALETTE - Standardized
|
||||
// ============================================
|
||||
|
||||
// Brand Colors
|
||||
$monaco-red: #DC143C;
|
||||
$monaco-red-dark: #B91C3C;
|
||||
$monaco-red-light: #FF6B8A;
|
||||
$monaco-white: #FFFFFF;
|
||||
$monaco-gold: #FFD700;
|
||||
|
||||
// Semantic Colors
|
||||
$color-success: #10B981;
|
||||
$color-warning: #F59E0B;
|
||||
$color-error: #EF4444;
|
||||
$color-info: #3B82F6;
|
||||
|
||||
// Neutral Palette
|
||||
$neutral-900: #0F172A;
|
||||
$neutral-800: #1E293B;
|
||||
$neutral-700: #334155;
|
||||
$neutral-600: #475569;
|
||||
$neutral-500: #64748B;
|
||||
$neutral-400: #94A3B8;
|
||||
$neutral-300: #CBD5E1;
|
||||
$neutral-200: #E2E8F0;
|
||||
$neutral-100: #F1F5F9;
|
||||
$neutral-50: #F8FAFC;
|
||||
|
||||
// Glass Morphism
|
||||
$glass-white: rgba(255, 255, 255, 0.1);
|
||||
$glass-white-hover: rgba(255, 255, 255, 0.15);
|
||||
$glass-border: rgba(255, 255, 255, 0.2);
|
||||
$glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
|
||||
// ============================================
|
||||
// 2. TYPOGRAPHY - Consistent Hierarchy
|
||||
// ============================================
|
||||
|
||||
// Font Family
|
||||
$font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
$font-mono: 'Fira Code', 'Monaco', monospace;
|
||||
|
||||
// Font Sizes - Using rem for accessibility
|
||||
$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
|
||||
|
||||
// Line Heights
|
||||
$leading-none: 1;
|
||||
$leading-tight: 1.2;
|
||||
$leading-snug: 1.375;
|
||||
$leading-normal: 1.6;
|
||||
$leading-relaxed: 1.75;
|
||||
$leading-loose: 2;
|
||||
|
||||
// Font Weights
|
||||
$font-light: 300;
|
||||
$font-regular: 400;
|
||||
$font-medium: 500;
|
||||
$font-semibold: 600;
|
||||
$font-bold: 700;
|
||||
$font-extrabold: 800;
|
||||
|
||||
// ============================================
|
||||
// 3. SPACING SYSTEM - 8px Grid
|
||||
// ============================================
|
||||
|
||||
$space-px: 1px;
|
||||
$space-0: 0;
|
||||
$space-1: 0.25rem; // 4px
|
||||
$space-2: 0.5rem; // 8px
|
||||
$space-3: 0.75rem; // 12px
|
||||
$space-4: 1rem; // 16px
|
||||
$space-5: 1.25rem; // 20px
|
||||
$space-6: 1.5rem; // 24px
|
||||
$space-7: 1.75rem; // 28px
|
||||
$space-8: 2rem; // 32px
|
||||
$space-10: 2.5rem; // 40px
|
||||
$space-12: 3rem; // 48px
|
||||
$space-16: 4rem; // 64px
|
||||
$space-20: 5rem; // 80px
|
||||
$space-24: 6rem; // 96px
|
||||
|
||||
// ============================================
|
||||
// 4. BORDER RADIUS - Consistent Curves
|
||||
// ============================================
|
||||
|
||||
$radius-none: 0;
|
||||
$radius-sm: 0.25rem; // 4px
|
||||
$radius-md: 0.5rem; // 8px
|
||||
$radius-lg: 0.75rem; // 12px
|
||||
$radius-xl: 1rem; // 16px
|
||||
$radius-2xl: 1.5rem; // 24px
|
||||
$radius-full: 9999px;
|
||||
|
||||
// ============================================
|
||||
// 5. SHADOWS - Depth System
|
||||
// ============================================
|
||||
|
||||
$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
$shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
$shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
$shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
$shadow-2xl: 0 25px 50px rgba(0, 0, 0, 0.25);
|
||||
$shadow-inner: inset 0 2px 4px rgba(0, 0, 0, 0.06);
|
||||
$shadow-glass: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
|
||||
// ============================================
|
||||
// 6. BREAKPOINTS - Mobile First
|
||||
// ============================================
|
||||
|
||||
$breakpoint-sm: 640px;
|
||||
$breakpoint-md: 768px;
|
||||
$breakpoint-lg: 1024px;
|
||||
$breakpoint-xl: 1280px;
|
||||
$breakpoint-2xl: 1536px;
|
||||
|
||||
@mixin sm {
|
||||
@media (min-width: $breakpoint-sm) { @content; }
|
||||
}
|
||||
|
||||
@mixin md {
|
||||
@media (min-width: $breakpoint-md) { @content; }
|
||||
}
|
||||
|
||||
@mixin lg {
|
||||
@media (min-width: $breakpoint-lg) { @content; }
|
||||
}
|
||||
|
||||
@mixin xl {
|
||||
@media (min-width: $breakpoint-xl) { @content; }
|
||||
}
|
||||
|
||||
@mixin xxl {
|
||||
@media (min-width: $breakpoint-2xl) { @content; }
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 7. TRANSITIONS - Smooth Interactions
|
||||
// ============================================
|
||||
|
||||
$ease-linear: linear;
|
||||
$ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
$ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
$ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
$ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
$duration-fast: 150ms;
|
||||
$duration-normal: 250ms;
|
||||
$duration-slow: 350ms;
|
||||
$duration-slower: 500ms;
|
||||
|
||||
// ============================================
|
||||
// 8. Z-INDEX SCALE - Layering System
|
||||
// ============================================
|
||||
|
||||
$z-negative: -1;
|
||||
$z-0: 0;
|
||||
$z-10: 10;
|
||||
$z-20: 20;
|
||||
$z-30: 30;
|
||||
$z-40: 40;
|
||||
$z-50: 50;
|
||||
$z-dropdown: 1000;
|
||||
$z-sticky: 1020;
|
||||
$z-fixed: 1030;
|
||||
$z-modal-backdrop: 1040;
|
||||
$z-modal: 1050;
|
||||
$z-popover: 1060;
|
||||
$z-tooltip: 1070;
|
||||
$z-notification: 1080;
|
||||
|
||||
// ============================================
|
||||
// 9. IMPROVED GLASS EFFECT MIXIN
|
||||
// ============================================
|
||||
|
||||
@mixin glass-effect(
|
||||
$blur: 10px,
|
||||
$opacity: 0.1,
|
||||
$border-opacity: 0.2,
|
||||
$shadow: true
|
||||
) {
|
||||
background: rgba(255, 255, 255, $opacity);
|
||||
|
||||
@supports (backdrop-filter: blur($blur)) or (-webkit-backdrop-filter: blur($blur)) {
|
||||
backdrop-filter: blur($blur);
|
||||
-webkit-backdrop-filter: blur($blur);
|
||||
}
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, $border-opacity);
|
||||
|
||||
@if $shadow {
|
||||
box-shadow: $shadow-glass;
|
||||
}
|
||||
|
||||
transition: all $duration-normal $ease-out;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, $opacity + 0.05);
|
||||
border-color: rgba(255, 255, 255, $border-opacity + 0.1);
|
||||
|
||||
@if $shadow {
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 10. COMPONENT CLASSES - Reusable Styles
|
||||
// ============================================
|
||||
|
||||
// Cards
|
||||
.card-base {
|
||||
@include glass-effect(12px, 0.08, 0.18, true);
|
||||
border-radius: $radius-xl;
|
||||
padding: $space-6;
|
||||
margin-bottom: $space-4;
|
||||
|
||||
@include md {
|
||||
padding: $space-8;
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
@mixin button-base {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $space-2;
|
||||
padding: $space-3 $space-6;
|
||||
border-radius: $radius-lg;
|
||||
font-weight: $font-medium;
|
||||
transition: all $duration-normal $ease-out;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $monaco-red;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@include button-base;
|
||||
background: linear-gradient(135deg, $monaco-red 0%, $monaco-red-dark 100%);
|
||||
color: white;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba($monaco-red, 0.3);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@include button-base;
|
||||
background: $neutral-100;
|
||||
color: $neutral-800;
|
||||
border-color: $neutral-300;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: $neutral-200;
|
||||
border-color: $neutral-400;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
@include button-base;
|
||||
background: transparent;
|
||||
color: $neutral-600;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: $neutral-100;
|
||||
color: $neutral-800;
|
||||
}
|
||||
}
|
||||
|
||||
// Typography Classes
|
||||
.heading-1 {
|
||||
font-size: $text-4xl;
|
||||
font-weight: $font-bold;
|
||||
line-height: $leading-tight;
|
||||
color: $neutral-900;
|
||||
|
||||
@include md {
|
||||
font-size: $text-5xl;
|
||||
}
|
||||
}
|
||||
|
||||
.heading-2 {
|
||||
font-size: $text-3xl;
|
||||
font-weight: $font-semibold;
|
||||
line-height: $leading-tight;
|
||||
color: $neutral-900;
|
||||
|
||||
@include md {
|
||||
font-size: $text-4xl;
|
||||
}
|
||||
}
|
||||
|
||||
.heading-3 {
|
||||
font-size: $text-2xl;
|
||||
font-weight: $font-semibold;
|
||||
line-height: $leading-snug;
|
||||
color: $neutral-800;
|
||||
}
|
||||
|
||||
.heading-4 {
|
||||
font-size: $text-xl;
|
||||
font-weight: $font-medium;
|
||||
line-height: $leading-snug;
|
||||
color: $neutral-800;
|
||||
}
|
||||
|
||||
.body-text {
|
||||
font-size: $text-base;
|
||||
line-height: $leading-normal;
|
||||
color: $neutral-700;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
font-size: $text-sm;
|
||||
line-height: $leading-normal;
|
||||
color: $neutral-600;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 11. LAYOUT UTILITIES
|
||||
// ============================================
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0 $space-4;
|
||||
|
||||
@include md {
|
||||
padding: 0 $space-6;
|
||||
}
|
||||
|
||||
@include lg {
|
||||
padding: 0 $space-8;
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: $space-4;
|
||||
|
||||
&.grid-cols-1 {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
@include md {
|
||||
&.md\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@include lg {
|
||||
&.lg\:grid-cols-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
&.lg\:grid-cols-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
|
||||
&.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&.gap-2 {
|
||||
gap: $space-2;
|
||||
}
|
||||
|
||||
&.gap-4 {
|
||||
gap: $space-4;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 12. ANIMATION CLASSES
|
||||
// ============================================
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn $duration-normal $ease-out;
|
||||
}
|
||||
|
||||
.animate-slideIn {
|
||||
animation: slideIn $duration-slow $ease-out;
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s $ease-in-out infinite;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 13. ACCESSIBILITY UTILITIES
|
||||
// ============================================
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.focus-visible {
|
||||
&:focus-visible {
|
||||
outline: 2px solid $monaco-red;
|
||||
outline-offset: 2px;
|
||||
border-radius: $radius-sm;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 14. STATUS INDICATORS
|
||||
// ============================================
|
||||
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: $space-1 $space-3;
|
||||
border-radius: $radius-full;
|
||||
font-size: $text-xs;
|
||||
font-weight: $font-semibold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
|
||||
&.status-overdue {
|
||||
background: rgba($color-error, 0.1);
|
||||
color: $color-error;
|
||||
border: 1px solid rgba($color-error, 0.2);
|
||||
}
|
||||
|
||||
&.status-pending {
|
||||
background: rgba($color-warning, 0.1);
|
||||
color: $color-warning;
|
||||
border: 1px solid rgba($color-warning, 0.2);
|
||||
}
|
||||
|
||||
&.status-paid {
|
||||
background: rgba($color-success, 0.1);
|
||||
color: $color-success;
|
||||
border: 1px solid rgba($color-success, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 15. LOADING STATES
|
||||
// ============================================
|
||||
|
||||
.skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
$neutral-200 25%,
|
||||
$neutral-100 50%,
|
||||
$neutral-200 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.5s infinite;
|
||||
border-radius: $radius-md;
|
||||
|
||||
&.skeleton-text {
|
||||
height: $space-4;
|
||||
margin-bottom: $space-2;
|
||||
}
|
||||
|
||||
&.skeleton-card {
|
||||
height: 120px;
|
||||
margin-bottom: $space-4;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
// MonacoUSA Portal - Main Stylesheet
|
||||
// Based on design-system.md specifications
|
||||
|
||||
// Import component styles
|
||||
@import 'components/dashboards';
|
||||
|
||||
// ============================================
|
||||
// 1. Variables & Design Tokens
|
||||
// ============================================
|
||||
|
|
|
|||
|
|
@ -1,690 +0,0 @@
|
|||
# MonacoUSA Portal Design System V2
|
||||
## Professional Neumorphic & Glassmorphic Design Language
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Design Philosophy](#design-philosophy)
|
||||
2. [Color System](#color-system)
|
||||
3. [Typography](#typography)
|
||||
4. [Spacing & Layout](#spacing--layout)
|
||||
5. [Shadow System](#shadow-system)
|
||||
6. [Component Library](#component-library)
|
||||
7. [Morphing Dropdown Specification](#morphing-dropdown-specification)
|
||||
8. [Animation Guidelines](#animation-guidelines)
|
||||
9. [Responsive Design](#responsive-design)
|
||||
10. [Implementation Examples](#implementation-examples)
|
||||
|
||||
---
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
### Core Principles
|
||||
- **Professional & Inviting**: Balancing corporate credibility with approachable design
|
||||
- **Neumorphic Foundation**: Soft UI with subtle depth and tactile feedback
|
||||
- **Glassmorphic Accents**: Strategic use of transparency and blur for interactive elements
|
||||
- **Monaco Heritage**: Incorporating national colors while maintaining modern aesthetics
|
||||
|
||||
### Visual Hierarchy
|
||||
1. **Primary Actions**: Monaco Red gradient buttons with strong shadows
|
||||
2. **Interactive Elements**: Blue glassmorphic dropdowns and selects
|
||||
3. **Content Cards**: Neumorphic white/light gray surfaces
|
||||
4. **Navigation**: Subtle depth with hover transformations
|
||||
|
||||
---
|
||||
|
||||
## Color System
|
||||
|
||||
### Primary Palette - Monaco Red
|
||||
```scss
|
||||
$primary-50: #FEF2F2; // Lightest tint
|
||||
$primary-100: #FEE2E2;
|
||||
$primary-200: #FECACA;
|
||||
$primary-300: #FCA5A5;
|
||||
$primary-400: #F87171;
|
||||
$primary-500: #EF4444;
|
||||
$primary-600: #CC0000; // Monaco Red (Main)
|
||||
$primary-700: #990000; // Monaco Dark Red
|
||||
$primary-800: #660000;
|
||||
$primary-900: #450A0A; // Darkest shade
|
||||
```
|
||||
|
||||
### Secondary Palette - Interactive Blue
|
||||
Used exclusively for dropdowns, selects, and interactive overlays:
|
||||
```scss
|
||||
$blue-50: #EFF6FF;
|
||||
$blue-100: #DBEAFE;
|
||||
$blue-200: #BFDBFE;
|
||||
$blue-300: #93C5FD;
|
||||
$blue-400: #60A5FA;
|
||||
$blue-500: #3B82F6;
|
||||
$blue-600: #2563EB;
|
||||
$blue-700: #1D4ED8;
|
||||
$blue-800: #1E40AF;
|
||||
$blue-900: #1E3A8A;
|
||||
```
|
||||
|
||||
### Neutral Palette
|
||||
```scss
|
||||
$neutral-50: #F9FAFB;
|
||||
$neutral-100: #F3F4F6;
|
||||
$neutral-200: #E5E7EB;
|
||||
$neutral-300: #D1D5DB;
|
||||
$neutral-400: #9CA3AF;
|
||||
$neutral-500: #6B7280;
|
||||
$neutral-600: #4B5563;
|
||||
$neutral-700: #374151;
|
||||
$neutral-800: #1F2937;
|
||||
$neutral-900: #111827;
|
||||
```
|
||||
|
||||
### Semantic Colors
|
||||
```scss
|
||||
$success: #10B981; // Green
|
||||
$warning: #F59E0B; // Amber
|
||||
$error: #EF4444; // Red
|
||||
$info: #3B82F6; // Blue
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
### Font Stack
|
||||
```css
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto,
|
||||
'Helvetica Neue', Arial, sans-serif;
|
||||
```
|
||||
|
||||
### Type Scale
|
||||
| Name | Size | Line Height | Usage |
|
||||
|------|------|-------------|-------|
|
||||
| `text-xs` | 0.75rem (12px) | 1.5 | Labels, captions |
|
||||
| `text-sm` | 0.875rem (14px) | 1.5 | Body small, form labels |
|
||||
| `text-base` | 1rem (16px) | 1.5 | Body default |
|
||||
| `text-lg` | 1.125rem (18px) | 1.625 | Body large |
|
||||
| `text-xl` | 1.25rem (20px) | 1.625 | H4 |
|
||||
| `text-2xl` | 1.5rem (24px) | 1.375 | H3 |
|
||||
| `text-3xl` | 1.875rem (30px) | 1.375 | H2 |
|
||||
| `text-4xl` | 2.25rem (36px) | 1.25 | H1 |
|
||||
|
||||
### Font Weights
|
||||
- `font-normal`: 400 - Body text
|
||||
- `font-medium`: 500 - Emphasis
|
||||
- `font-semibold`: 600 - Subheadings
|
||||
- `font-bold`: 700 - Headings
|
||||
|
||||
---
|
||||
|
||||
## Spacing & Layout
|
||||
|
||||
### Spacing Scale
|
||||
```scss
|
||||
$space-0: 0;
|
||||
$space-1: 0.25rem; // 4px
|
||||
$space-2: 0.5rem; // 8px
|
||||
$space-3: 0.75rem; // 12px
|
||||
$space-4: 1rem; // 16px
|
||||
$space-5: 1.25rem; // 20px
|
||||
$space-6: 1.5rem; // 24px
|
||||
$space-8: 2rem; // 32px
|
||||
$space-10: 2.5rem; // 40px
|
||||
$space-12: 3rem; // 48px
|
||||
$space-16: 4rem; // 64px
|
||||
$space-20: 5rem; // 80px
|
||||
```
|
||||
|
||||
### Border Radius
|
||||
```scss
|
||||
$radius-sm: 0.375rem; // 6px - Small elements
|
||||
$radius-md: 0.5rem; // 8px - Buttons
|
||||
$radius-lg: 0.75rem; // 12px - Cards, dropdowns
|
||||
$radius-xl: 1rem; // 16px - Large cards
|
||||
$radius-2xl: 1.5rem; // 24px - Hero sections
|
||||
$radius-full: 9999px; // Pills, avatars
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shadow System
|
||||
|
||||
### Neumorphic Shadows
|
||||
For cards and static elements:
|
||||
```scss
|
||||
// Soft elevation shadows
|
||||
$shadow-soft-sm:
|
||||
4px 4px 8px rgba(0, 0, 0, 0.08),
|
||||
-4px -4px 8px rgba(255, 255, 255, 0.95);
|
||||
|
||||
$shadow-soft-md:
|
||||
8px 8px 16px rgba(0, 0, 0, 0.1),
|
||||
-8px -8px 16px rgba(255, 255, 255, 0.95);
|
||||
|
||||
$shadow-soft-lg:
|
||||
12px 12px 24px rgba(0, 0, 0, 0.12),
|
||||
-12px -12px 24px rgba(255, 255, 255, 0.95);
|
||||
|
||||
// Inset shadows for pressed states
|
||||
$shadow-inset-sm:
|
||||
inset 4px 4px 8px rgba(0, 0, 0, 0.1),
|
||||
inset -4px -4px 8px rgba(255, 255, 255, 0.95);
|
||||
```
|
||||
|
||||
### Glassmorphic Shadows
|
||||
For dropdowns and overlays:
|
||||
```scss
|
||||
$shadow-morphing:
|
||||
0 10px 40px rgba(0, 0, 0, 0.12),
|
||||
0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
|
||||
$shadow-glass:
|
||||
0 8px 32px rgba(0, 0, 0, 0.1),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.3);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Library
|
||||
|
||||
### 1. Buttons
|
||||
|
||||
#### Primary Button
|
||||
```scss
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, $primary-600, $primary-700);
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow:
|
||||
4px 4px 8px rgba(204, 0, 0, 0.3),
|
||||
-4px -4px 8px rgba(255, 255, 255, 0.95);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
6px 6px 12px rgba(204, 0, 0, 0.4),
|
||||
-6px -6px 12px rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: $shadow-inset-sm;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Neumorphic Button
|
||||
```scss
|
||||
.btn-neumorphic {
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
color: $neutral-700;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-soft-md;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Cards
|
||||
|
||||
#### Neumorphic Card
|
||||
```scss
|
||||
.card-neumorphic {
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
border-radius: $radius-xl;
|
||||
padding: $space-6;
|
||||
box-shadow: $shadow-soft-md;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: $shadow-soft-lg;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Form Inputs
|
||||
|
||||
#### Neumorphic Input
|
||||
```scss
|
||||
.input-neumorphic {
|
||||
background: $neutral-50;
|
||||
border: none;
|
||||
border-radius: $radius-lg;
|
||||
padding: $space-3 $space-4;
|
||||
box-shadow: $shadow-inset-sm;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow:
|
||||
$shadow-inset-md,
|
||||
0 0 0 3px rgba($primary-500, 0.1);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Morphing Dropdown Specification
|
||||
|
||||
### Overview
|
||||
The morphing dropdown is a signature component that combines glassmorphism with smooth spring animations. It features a blue color scheme distinct from the primary Monaco red, creating clear visual hierarchy for interactive elements.
|
||||
|
||||
### Visual Characteristics
|
||||
|
||||
#### Trigger State
|
||||
```scss
|
||||
.morphing-select-trigger {
|
||||
// Base styling
|
||||
height: 48px;
|
||||
padding: 0 1rem;
|
||||
border-radius: 12px;
|
||||
|
||||
// Glassmorphic background
|
||||
background: rgba(239, 246, 255, 0.3); // blue-50 with 30% opacity
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(191, 219, 254, 0.3); // blue-200 with 30% opacity
|
||||
|
||||
// Typography
|
||||
color: #1E3A8A; // blue-900
|
||||
font-size: 0.875rem;
|
||||
|
||||
// Transition
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(219, 234, 254, 0.4); // blue-100 with 40% opacity
|
||||
border-color: rgba(147, 197, 253, 0.4); // blue-300 with 40% opacity
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
&.open {
|
||||
background: rgba(219, 234, 254, 0.5);
|
||||
border-color: rgba(96, 165, 250, 0.5); // blue-400 with 50% opacity
|
||||
box-shadow:
|
||||
0 0 0 3px rgba(59, 130, 246, 0.1),
|
||||
0 4px 12px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Dropdown Panel
|
||||
```scss
|
||||
.morphing-select-dropdown {
|
||||
// Glassmorphic container
|
||||
background: rgba(239, 246, 255, 0.95); // blue-50 with 95% opacity
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(191, 219, 254, 0.3);
|
||||
border-radius: 12px;
|
||||
|
||||
// Shadow
|
||||
box-shadow:
|
||||
0 10px 40px rgba(0, 0, 0, 0.12),
|
||||
0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
|
||||
// Animation
|
||||
animation: morphIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
// Layout
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
```
|
||||
|
||||
#### Option Items
|
||||
```scss
|
||||
.morphing-select-option {
|
||||
padding: 0.625rem 0.75rem;
|
||||
border-radius: 8px;
|
||||
color: #1E3A8A; // blue-900
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(96, 165, 250, 0.2); // blue-400 with 20% opacity
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: rgba(59, 130, 246, 0.25); // blue-500 with 25% opacity
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Option Groups
|
||||
```scss
|
||||
.morphing-select-optgroup-label {
|
||||
padding: 0.5rem 0.75rem 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #1D4ED8; // blue-700
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
```
|
||||
|
||||
### Animation Specifications
|
||||
|
||||
#### Spring Configuration
|
||||
```javascript
|
||||
const TRANSITION = {
|
||||
type: 'spring',
|
||||
bounce: 0.05, // Minimal bounce for professional feel
|
||||
duration: 0.3, // Quick but smooth
|
||||
};
|
||||
```
|
||||
|
||||
#### Animation States
|
||||
```javascript
|
||||
// Opening animation
|
||||
@keyframes morphIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Closing animation (reversed)
|
||||
@keyframes morphOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(-10px);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Interaction Patterns
|
||||
|
||||
#### Click Outside
|
||||
- Clicking anywhere outside the dropdown closes it
|
||||
- Uses `mousedown` event for immediate response
|
||||
|
||||
#### Keyboard Navigation
|
||||
- `Escape` key closes the dropdown
|
||||
- `Tab` key navigates through options
|
||||
- `Enter` selects the focused option
|
||||
- `Space` toggles selection in multi-select
|
||||
|
||||
#### Touch Support
|
||||
- Touch events are handled identically to mouse events
|
||||
- Larger touch targets on mobile (min 44px)
|
||||
|
||||
### Multi-Select Variant
|
||||
|
||||
#### Selected Tags
|
||||
```scss
|
||||
.morphing-select-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: rgba(191, 219, 254, 0.6); // blue-200 with 60% opacity
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
|
||||
// Remove button
|
||||
.tag-remove {
|
||||
color: #1D4ED8; // blue-700
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #1E3A8A; // blue-900
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Accessibility Features
|
||||
- ARIA attributes: `aria-expanded`, `aria-controls`, `aria-modal`
|
||||
- Role attributes: `role="dialog"` for dropdown panel
|
||||
- Keyboard navigation support
|
||||
- Focus management
|
||||
- Screen reader announcements
|
||||
|
||||
### Implementation Guidelines
|
||||
|
||||
#### Vue/React Component Structure
|
||||
```typescript
|
||||
interface SelectOption {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface OptionGroup {
|
||||
label: string;
|
||||
options: SelectOption[];
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
options?: SelectOption[];
|
||||
optgroups?: OptionGroup[];
|
||||
value?: string | string[]; // string[] for multi-select
|
||||
onValueChange?: (value: string | string[]) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
maxSelected?: number; // For multi-select
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### State Management
|
||||
```javascript
|
||||
// Single Select
|
||||
const [selectedValue, setSelectedValue] = useState('');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
// Multi-Select
|
||||
const [selectedValues, setSelectedValues] = useState([]);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Animation Guidelines
|
||||
|
||||
### Timing Functions
|
||||
```scss
|
||||
$ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
$ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
$ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
$ease-in-out: cubic-bezier(0.4, 0, 0.6, 1);
|
||||
```
|
||||
|
||||
### Duration Scale
|
||||
- `fast`: 150ms - Micro interactions
|
||||
- `base`: 300ms - Standard transitions
|
||||
- `slow`: 500ms - Complex animations
|
||||
- `slower`: 700ms - Page transitions
|
||||
|
||||
### Animation Principles
|
||||
1. **Purpose**: Every animation should have a clear purpose
|
||||
2. **Performance**: Use `transform` and `opacity` for GPU acceleration
|
||||
3. **Consistency**: Maintain consistent timing across similar interactions
|
||||
4. **Subtlety**: Animations should enhance, not distract
|
||||
|
||||
---
|
||||
|
||||
## Responsive Design
|
||||
|
||||
### Breakpoints
|
||||
```scss
|
||||
$breakpoint-sm: 640px; // Mobile landscape
|
||||
$breakpoint-md: 768px; // Tablet portrait
|
||||
$breakpoint-lg: 1024px; // Tablet landscape
|
||||
$breakpoint-xl: 1280px; // Desktop
|
||||
$breakpoint-2xl: 1536px; // Large desktop
|
||||
```
|
||||
|
||||
### Mobile Adaptations
|
||||
|
||||
#### Morphing Dropdown
|
||||
- Full-width on mobile
|
||||
- Bottom sheet presentation option
|
||||
- Larger touch targets (min 44px)
|
||||
- Reduced blur for performance
|
||||
|
||||
#### Navigation
|
||||
- Hamburger menu below tablet
|
||||
- Full-screen overlay navigation
|
||||
- Swipe gestures for sidebar
|
||||
|
||||
#### Cards
|
||||
- Single column layout on mobile
|
||||
- Reduced padding and margins
|
||||
- Simplified shadows for performance
|
||||
|
||||
---
|
||||
|
||||
## Implementation Examples
|
||||
|
||||
### Example 1: Login Form with Morphing Select
|
||||
```html
|
||||
<div class="login-form">
|
||||
<!-- Email Input (Neumorphic) -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" class="input-neumorphic" placeholder="you@example.com">
|
||||
</div>
|
||||
|
||||
<!-- Role Selector (Morphing Dropdown) -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">Select Role</label>
|
||||
<div class="morphing-select">
|
||||
<div class="morphing-select-trigger">
|
||||
<span>Choose your role...</span>
|
||||
<svg class="chevron">...</svg>
|
||||
</div>
|
||||
<div class="morphing-select-dropdown">
|
||||
<div class="morphing-select-option">Member</div>
|
||||
<div class="morphing-select-option">Admin</div>
|
||||
<div class="morphing-select-option">Board Member</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button (Primary) -->
|
||||
<button class="btn-primary">Sign In</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Example 2: Dashboard Stats with Dropdown Filter
|
||||
```html
|
||||
<div class="dashboard-header">
|
||||
<h1 class="page-title">Dashboard</h1>
|
||||
|
||||
<!-- Time Period Filter (Morphing Dropdown) -->
|
||||
<div class="morphing-select">
|
||||
<div class="morphing-select-trigger">
|
||||
<span>Last 30 Days</span>
|
||||
<svg class="chevron">...</svg>
|
||||
</div>
|
||||
<div class="morphing-select-dropdown">
|
||||
<div class="morphing-select-optgroup">
|
||||
<div class="morphing-select-optgroup-label">Quick Ranges</div>
|
||||
<div class="morphing-select-option">Today</div>
|
||||
<div class="morphing-select-option">Last 7 Days</div>
|
||||
<div class="morphing-select-option selected">Last 30 Days</div>
|
||||
</div>
|
||||
<div class="morphing-select-optgroup">
|
||||
<div class="morphing-select-optgroup-label">Custom</div>
|
||||
<div class="morphing-select-option">Last Quarter</div>
|
||||
<div class="morphing-select-option">Last Year</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards (Neumorphic) -->
|
||||
<div class="stats-grid">
|
||||
<div class="card-neumorphic stat-card">
|
||||
<div class="stat-value">2,847</div>
|
||||
<div class="stat-label">Total Members</div>
|
||||
<div class="stat-change positive">+12.5%</div>
|
||||
</div>
|
||||
<!-- More cards... -->
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Tokens
|
||||
|
||||
For implementation in code, use these design tokens:
|
||||
|
||||
```javascript
|
||||
const designTokens = {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#FEF2F2',
|
||||
100: '#FEE2E2',
|
||||
200: '#FECACA',
|
||||
300: '#FCA5A5',
|
||||
400: '#F87171',
|
||||
500: '#EF4444',
|
||||
600: '#CC0000', // Main brand color
|
||||
700: '#990000',
|
||||
800: '#660000',
|
||||
900: '#450A0A',
|
||||
},
|
||||
blue: {
|
||||
50: '#EFF6FF',
|
||||
100: '#DBEAFE',
|
||||
200: '#BFDBFE',
|
||||
300: '#93C5FD',
|
||||
400: '#60A5FA',
|
||||
500: '#3B82F6',
|
||||
600: '#2563EB',
|
||||
700: '#1D4ED8',
|
||||
800: '#1E40AF',
|
||||
900: '#1E3A8A',
|
||||
},
|
||||
// ... other colors
|
||||
},
|
||||
shadows: {
|
||||
'soft-sm': '4px 4px 8px rgba(0, 0, 0, 0.08), -4px -4px 8px rgba(255, 255, 255, 0.95)',
|
||||
'soft-md': '8px 8px 16px rgba(0, 0, 0, 0.1), -8px -8px 16px rgba(255, 255, 255, 0.95)',
|
||||
'soft-lg': '12px 12px 24px rgba(0, 0, 0, 0.12), -12px -12px 24px rgba(255, 255, 255, 0.95)',
|
||||
'morphing': '0 10px 40px rgba(0, 0, 0, 0.12), 0 2px 10px rgba(0, 0, 0, 0.08)',
|
||||
},
|
||||
animation: {
|
||||
duration: {
|
||||
fast: '150ms',
|
||||
base: '300ms',
|
||||
slow: '500ms',
|
||||
},
|
||||
ease: {
|
||||
smooth: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
- **v2.0.0** - Initial release with Neumorphic & Glassmorphic design system
|
||||
- **v2.0.1** - Added morphing dropdown specifications
|
||||
- **v2.0.2** - Enhanced mobile responsive guidelines
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
For questions about implementing this design system, please contact the MonacoUSA Portal development team.
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'neumorphic-card',
|
||||
sizeClasses,
|
||||
elevationClasses,
|
||||
{
|
||||
'cursor-pointer': clickable,
|
||||
'transition-all duration-300': animated
|
||||
}
|
||||
]"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div v-if="$slots.header || title" class="neumorphic-card__header">
|
||||
<slot name="header">
|
||||
<h3 v-if="title" class="neumorphic-card__title">{{ title }}</h3>
|
||||
<p v-if="subtitle" class="neumorphic-card__subtitle">{{ subtitle }}</p>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="neumorphic-card__content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.footer" class="neumorphic-card__footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
elevation?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
clickable?: boolean;
|
||||
animated?: boolean;
|
||||
pressed?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 'md',
|
||||
elevation: 'md',
|
||||
clickable: false,
|
||||
animated: true,
|
||||
pressed: false
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [event: MouseEvent];
|
||||
}>();
|
||||
|
||||
const sizeClasses = computed(() => {
|
||||
const sizes = {
|
||||
sm: 'p-4',
|
||||
md: 'p-6',
|
||||
lg: 'p-8',
|
||||
xl: 'p-10'
|
||||
};
|
||||
return sizes[props.size];
|
||||
});
|
||||
|
||||
const elevationClasses = computed(() => {
|
||||
if (props.pressed) return 'neumorphic-pressed';
|
||||
|
||||
const elevations = {
|
||||
none: '',
|
||||
sm: 'neumorphic-elevation-sm',
|
||||
md: 'neumorphic-elevation-md',
|
||||
lg: 'neumorphic-elevation-lg',
|
||||
xl: 'neumorphic-elevation-xl'
|
||||
};
|
||||
return elevations[props.elevation];
|
||||
});
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (props.clickable) {
|
||||
emit('click', event);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/neumorphic-system.scss';
|
||||
|
||||
.neumorphic-card {
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
border-radius: $radius-xl;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// Base elevation styles
|
||||
&.neumorphic-elevation-sm {
|
||||
box-shadow: $shadow-soft-sm;
|
||||
}
|
||||
|
||||
&.neumorphic-elevation-md {
|
||||
box-shadow: $shadow-soft-md;
|
||||
}
|
||||
|
||||
&.neumorphic-elevation-lg {
|
||||
box-shadow: $shadow-soft-lg;
|
||||
}
|
||||
|
||||
&.neumorphic-elevation-xl {
|
||||
box-shadow: $shadow-soft-xl;
|
||||
}
|
||||
|
||||
&.neumorphic-pressed {
|
||||
box-shadow: $shadow-inset-md;
|
||||
background: linear-gradient(145deg, #e6e6e6, #ffffff);
|
||||
}
|
||||
|
||||
// Hover effects for clickable cards
|
||||
&.cursor-pointer {
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: $shadow-soft-lg;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: $shadow-inset-sm;
|
||||
}
|
||||
}
|
||||
|
||||
// Header section
|
||||
&__header {
|
||||
padding-bottom: $space-4;
|
||||
border-bottom: 1px solid rgba($neutral-300, 0.5);
|
||||
margin-bottom: $space-4;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: $font-heading;
|
||||
font-size: $text-xl;
|
||||
font-weight: $font-semibold;
|
||||
color: $neutral-800;
|
||||
margin: 0;
|
||||
line-height: $leading-tight;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-family: $font-body;
|
||||
font-size: $text-sm;
|
||||
color: $neutral-600;
|
||||
margin-top: $space-2;
|
||||
line-height: $leading-normal;
|
||||
}
|
||||
|
||||
// Content section
|
||||
&__content {
|
||||
font-family: $font-body;
|
||||
color: $neutral-700;
|
||||
line-height: $leading-relaxed;
|
||||
}
|
||||
|
||||
// Footer section
|
||||
&__footer {
|
||||
padding-top: $space-4;
|
||||
border-top: 1px solid rgba($neutral-300, 0.5);
|
||||
margin-top: $space-4;
|
||||
}
|
||||
|
||||
// Dark mode support
|
||||
@include dark-mode {
|
||||
background: linear-gradient(145deg, $neutral-800, $neutral-700);
|
||||
|
||||
&.neumorphic-elevation-sm {
|
||||
box-shadow: $shadow-dark-soft-sm;
|
||||
}
|
||||
|
||||
&.neumorphic-elevation-md {
|
||||
box-shadow: $shadow-dark-soft-md;
|
||||
}
|
||||
|
||||
&.neumorphic-elevation-lg {
|
||||
box-shadow: $shadow-dark-soft-lg;
|
||||
}
|
||||
|
||||
&.neumorphic-pressed {
|
||||
box-shadow: $shadow-dark-inset-md;
|
||||
background: linear-gradient(145deg, $neutral-900, $neutral-800);
|
||||
}
|
||||
|
||||
.neumorphic-card__title {
|
||||
color: $neutral-100;
|
||||
}
|
||||
|
||||
.neumorphic-card__subtitle {
|
||||
color: $neutral-400;
|
||||
}
|
||||
|
||||
.neumorphic-card__content {
|
||||
color: $neutral-300;
|
||||
}
|
||||
|
||||
.neumorphic-card__header,
|
||||
.neumorphic-card__footer {
|
||||
border-color: rgba($neutral-600, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive adjustments
|
||||
@include responsive('sm') {
|
||||
&.p-4 { padding: $space-5; }
|
||||
&.p-6 { padding: $space-8; }
|
||||
&.p-8 { padding: $space-10; }
|
||||
&.p-10 { padding: $space-12; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,315 +0,0 @@
|
|||
<template>
|
||||
<button
|
||||
:class="[
|
||||
'professional-button',
|
||||
variantClasses,
|
||||
sizeClasses,
|
||||
{
|
||||
'professional-button--loading': loading,
|
||||
'professional-button--disabled': disabled || loading,
|
||||
'professional-button--block': block,
|
||||
'professional-button--icon-only': !$slots.default && icon
|
||||
}
|
||||
]"
|
||||
:disabled="disabled || loading"
|
||||
@click="handleClick"
|
||||
>
|
||||
<span v-if="loading" class="professional-button__spinner">
|
||||
<svg class="animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<span v-if="icon && !loading" class="professional-button__icon">
|
||||
<component :is="icon" />
|
||||
</span>
|
||||
|
||||
<span v-if="$slots.default" class="professional-button__content">
|
||||
<slot></slot>
|
||||
</span>
|
||||
|
||||
<span v-if="endIcon && !loading" class="professional-button__end-icon">
|
||||
<component :is="endIcon" />
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
interface Props {
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
block?: boolean;
|
||||
icon?: Component;
|
||||
endIcon?: Component;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
variant: 'primary',
|
||||
size: 'md',
|
||||
loading: false,
|
||||
disabled: false,
|
||||
block: false
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [event: MouseEvent];
|
||||
}>();
|
||||
|
||||
const variantClasses = computed(() => {
|
||||
const variants = {
|
||||
primary: 'professional-button--primary',
|
||||
secondary: 'professional-button--secondary',
|
||||
outline: 'professional-button--outline',
|
||||
ghost: 'professional-button--ghost',
|
||||
danger: 'professional-button--danger'
|
||||
};
|
||||
return variants[props.variant];
|
||||
});
|
||||
|
||||
const sizeClasses = computed(() => {
|
||||
const sizes = {
|
||||
sm: 'professional-button--sm',
|
||||
md: 'professional-button--md',
|
||||
lg: 'professional-button--lg'
|
||||
};
|
||||
return sizes[props.size];
|
||||
});
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (!props.disabled && !props.loading) {
|
||||
emit('click', event);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/neumorphic-system.scss';
|
||||
|
||||
.professional-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-center: center;
|
||||
gap: $space-2;
|
||||
border: none;
|
||||
border-radius: $radius-lg;
|
||||
font-family: $font-body;
|
||||
font-weight: $font-medium;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base $ease-in-out-soft;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// Size variants
|
||||
&--sm {
|
||||
padding: $space-2 $space-4;
|
||||
font-size: $text-sm;
|
||||
min-height: $button-height-sm;
|
||||
|
||||
&.professional-button--icon-only {
|
||||
width: $button-height-sm;
|
||||
padding: $space-2;
|
||||
}
|
||||
}
|
||||
|
||||
&--md {
|
||||
padding: $space-3 $space-5;
|
||||
font-size: $text-base;
|
||||
min-height: $button-height-md;
|
||||
|
||||
&.professional-button--icon-only {
|
||||
width: $button-height-md;
|
||||
padding: $space-3;
|
||||
}
|
||||
}
|
||||
|
||||
&--lg {
|
||||
padding: $space-4 $space-6;
|
||||
font-size: $text-lg;
|
||||
min-height: $button-height-lg;
|
||||
|
||||
&.professional-button--icon-only {
|
||||
width: $button-height-lg;
|
||||
padding: $space-4;
|
||||
}
|
||||
}
|
||||
|
||||
// Variant styles
|
||||
&--primary {
|
||||
background: linear-gradient(135deg, $primary-500, $primary-600);
|
||||
color: white;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
|
||||
&:hover:not(.professional-button--disabled) {
|
||||
background: linear-gradient(135deg, $primary-600, $primary-700);
|
||||
box-shadow: $shadow-soft-md;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active:not(.professional-button--disabled) {
|
||||
box-shadow: $shadow-inset-sm;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
color: $neutral-800;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
|
||||
&:hover:not(.professional-button--disabled) {
|
||||
box-shadow: $shadow-soft-md;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active:not(.professional-button--disabled) {
|
||||
box-shadow: $shadow-inset-sm;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
&--outline {
|
||||
background: transparent;
|
||||
color: $primary-600;
|
||||
border: 2px solid $primary-200;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover:not(.professional-button--disabled) {
|
||||
background: rgba($primary-500, 0.05);
|
||||
border-color: $primary-300;
|
||||
}
|
||||
|
||||
&:active:not(.professional-button--disabled) {
|
||||
background: rgba($primary-500, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&--ghost {
|
||||
background: transparent;
|
||||
color: $neutral-700;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover:not(.professional-button--disabled) {
|
||||
background: rgba($neutral-500, 0.08);
|
||||
}
|
||||
|
||||
&:active:not(.professional-button--disabled) {
|
||||
background: rgba($neutral-500, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background: linear-gradient(135deg, $error-500, #dc2626);
|
||||
color: white;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
|
||||
&:hover:not(.professional-button--disabled) {
|
||||
background: linear-gradient(135deg, #dc2626, #b91c1c);
|
||||
box-shadow: $shadow-soft-md;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active:not(.professional-button--disabled) {
|
||||
box-shadow: $shadow-inset-sm;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// States
|
||||
&--block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&--loading {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
// Icons
|
||||
&__spinner {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: inherit;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon,
|
||||
&__end-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// Focus state
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba($primary-500, 0.2);
|
||||
}
|
||||
|
||||
// Dark mode support
|
||||
@include dark-mode {
|
||||
&--secondary {
|
||||
background: linear-gradient(145deg, $neutral-700, $neutral-800);
|
||||
color: $neutral-100;
|
||||
|
||||
&:hover:not(.professional-button--disabled) {
|
||||
background: linear-gradient(145deg, $neutral-600, $neutral-700);
|
||||
}
|
||||
}
|
||||
|
||||
&--ghost {
|
||||
color: $neutral-300;
|
||||
|
||||
&:hover:not(.professional-button--disabled) {
|
||||
background: rgba($neutral-400, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&--outline {
|
||||
color: $primary-400;
|
||||
border-color: $primary-800;
|
||||
|
||||
&:hover:not(.professional-button--disabled) {
|
||||
background: rgba($primary-400, 0.1);
|
||||
border-color: $primary-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
<template>
|
||||
<NeumorphicCard :elevation="elevation" :animated="animated" class="stat-card">
|
||||
<div class="stat-card__content">
|
||||
<div class="stat-card__header">
|
||||
<div class="stat-card__icon-wrapper" :class="`stat-card__icon-wrapper--${variant}`">
|
||||
<component :is="icon" v-if="icon" class="stat-card__icon" />
|
||||
<div v-else class="stat-card__icon-placeholder">
|
||||
<svg fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card__trend" v-if="trend">
|
||||
<span :class="['stat-card__trend-badge', trendClass]">
|
||||
<svg v-if="trend > 0" class="stat-card__trend-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
|
||||
</svg>
|
||||
<svg v-else class="stat-card__trend-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6" />
|
||||
</svg>
|
||||
{{ Math.abs(trend) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card__body">
|
||||
<h3 class="stat-card__label">{{ label }}</h3>
|
||||
<div class="stat-card__value-wrapper">
|
||||
<span v-if="prefix" class="stat-card__prefix">{{ prefix }}</span>
|
||||
<span class="stat-card__value">{{ formattedValue }}</span>
|
||||
<span v-if="suffix" class="stat-card__suffix">{{ suffix }}</span>
|
||||
</div>
|
||||
<p v-if="description" class="stat-card__description">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="showProgress" class="stat-card__progress">
|
||||
<div class="stat-card__progress-bar">
|
||||
<div
|
||||
class="stat-card__progress-fill"
|
||||
:class="`stat-card__progress-fill--${variant}`"
|
||||
:style="{ width: `${progress}%` }"
|
||||
></div>
|
||||
</div>
|
||||
<span class="stat-card__progress-text">{{ progress }}% of target</span>
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.footer" class="stat-card__footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</NeumorphicCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import NeumorphicCard from './NeumorphicCard.vue';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
value: number | string;
|
||||
trend?: number;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
description?: string;
|
||||
variant?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
||||
icon?: Component;
|
||||
elevation?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
animated?: boolean;
|
||||
showProgress?: boolean;
|
||||
progress?: number;
|
||||
format?: 'number' | 'currency' | 'percentage';
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
variant: 'primary',
|
||||
elevation: 'md',
|
||||
animated: true,
|
||||
showProgress: false,
|
||||
progress: 0,
|
||||
format: 'number'
|
||||
});
|
||||
|
||||
const formattedValue = computed(() => {
|
||||
const val = props.value;
|
||||
if (typeof val === 'string') return val;
|
||||
|
||||
switch (props.format) {
|
||||
case 'currency':
|
||||
return val.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
|
||||
case 'percentage':
|
||||
return val.toFixed(1);
|
||||
default:
|
||||
return val.toLocaleString('en-US');
|
||||
}
|
||||
});
|
||||
|
||||
const trendClass = computed(() => {
|
||||
if (!props.trend) return '';
|
||||
return props.trend > 0 ? 'stat-card__trend-badge--up' : 'stat-card__trend-badge--down';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/neumorphic-system.scss';
|
||||
|
||||
.stat-card {
|
||||
height: 100%;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $space-4;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__icon-wrapper {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: $radius-lg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
transition: all $transition-base;
|
||||
|
||||
&--primary {
|
||||
background: linear-gradient(135deg, rgba($primary-500, 0.1), rgba($primary-600, 0.15));
|
||||
color: $primary-600;
|
||||
}
|
||||
|
||||
&--success {
|
||||
background: linear-gradient(135deg, rgba($success-500, 0.1), rgba($success-500, 0.15));
|
||||
color: $success-500;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background: linear-gradient(135deg, rgba($warning-500, 0.1), rgba($warning-500, 0.15));
|
||||
color: $warning-500;
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background: linear-gradient(135deg, rgba($error-500, 0.1), rgba($error-500, 0.15));
|
||||
color: $error-500;
|
||||
}
|
||||
|
||||
&--info {
|
||||
background: linear-gradient(135deg, rgba($info-500, 0.1), rgba($info-500, 0.15));
|
||||
color: $info-500;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon,
|
||||
&__icon-placeholder {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&__trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__trend-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $space-1;
|
||||
padding: $space-1 $space-2;
|
||||
border-radius: $radius-full;
|
||||
font-size: $text-sm;
|
||||
font-weight: $font-semibold;
|
||||
|
||||
&--up {
|
||||
background: linear-gradient(135deg, rgba($success-500, 0.1), rgba($success-500, 0.15));
|
||||
color: $success-500;
|
||||
}
|
||||
|
||||
&--down {
|
||||
background: linear-gradient(135deg, rgba($error-500, 0.1), rgba($error-500, 0.15));
|
||||
color: $error-500;
|
||||
}
|
||||
}
|
||||
|
||||
&__trend-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: $text-sm;
|
||||
font-weight: $font-medium;
|
||||
color: $neutral-600;
|
||||
margin-bottom: $space-2;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
&__value-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: $space-1;
|
||||
margin-bottom: $space-2;
|
||||
}
|
||||
|
||||
&__prefix,
|
||||
&__suffix {
|
||||
font-size: $text-lg;
|
||||
color: $neutral-500;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-family: $font-heading;
|
||||
font-size: $text-3xl;
|
||||
font-weight: $font-bold;
|
||||
color: $neutral-800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: $text-sm;
|
||||
color: $neutral-600;
|
||||
line-height: $leading-normal;
|
||||
}
|
||||
|
||||
&__progress {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
&__progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: rgba($neutral-300, 0.3);
|
||||
border-radius: $radius-full;
|
||||
overflow: hidden;
|
||||
box-shadow: $shadow-inset-sm;
|
||||
margin-bottom: $space-2;
|
||||
}
|
||||
|
||||
&__progress-fill {
|
||||
height: 100%;
|
||||
border-radius: $radius-full;
|
||||
transition: width $transition-slow $ease-out-soft;
|
||||
|
||||
&--primary {
|
||||
background: linear-gradient(90deg, $primary-500, $primary-600);
|
||||
}
|
||||
|
||||
&--success {
|
||||
background: linear-gradient(90deg, $success-500, #059669);
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background: linear-gradient(90deg, $warning-500, #D97706);
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background: linear-gradient(90deg, $error-500, #DC2626);
|
||||
}
|
||||
|
||||
&--info {
|
||||
background: linear-gradient(90deg, $info-500, #2563EB);
|
||||
}
|
||||
}
|
||||
|
||||
&__progress-text {
|
||||
font-size: $text-xs;
|
||||
color: $neutral-500;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
padding-top: $space-3;
|
||||
border-top: 1px solid rgba($neutral-300, 0.3);
|
||||
}
|
||||
|
||||
// Hover effect
|
||||
&:hover {
|
||||
.stat-card__icon-wrapper {
|
||||
transform: scale(1.05);
|
||||
box-shadow: $shadow-soft-md;
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode
|
||||
@include dark-mode {
|
||||
.stat-card__label {
|
||||
color: $neutral-400;
|
||||
}
|
||||
|
||||
.stat-card__value {
|
||||
color: $neutral-100;
|
||||
}
|
||||
|
||||
.stat-card__description {
|
||||
color: $neutral-400;
|
||||
}
|
||||
|
||||
.stat-card__prefix,
|
||||
.stat-card__suffix {
|
||||
color: $neutral-500;
|
||||
}
|
||||
|
||||
.stat-card__progress-bar {
|
||||
background: rgba($neutral-600, 0.3);
|
||||
}
|
||||
|
||||
.stat-card__progress-text {
|
||||
color: $neutral-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,319 +0,0 @@
|
|||
<template>
|
||||
<div class="mockup-index">
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<img src="/MONACOUSA-Flags_376x376.png" alt="MonacoUSA" class="logo" />
|
||||
<h1 class="title">MonacoUSA Portal Design Mockups</h1>
|
||||
<p class="subtitle">Professional Neumorphic Design System</p>
|
||||
</header>
|
||||
|
||||
<section class="mockup-grid">
|
||||
<NuxtLink to="/design-mockups/pages/auth/ProfessionalLogin" class="mockup-card">
|
||||
<div class="card-icon card-icon--auth">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="card-title">Login Page</h2>
|
||||
<p class="card-description">Professional split-screen login with branding section and neumorphic form</p>
|
||||
<span class="card-link">View Mockup →</span>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink to="/design-mockups/pages/admin/ProfessionalAdminDashboard" class="mockup-card">
|
||||
<div class="card-icon card-icon--admin">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="card-title">Admin Dashboard</h2>
|
||||
<p class="card-description">Complete admin interface with sidebar navigation, stats, charts, and data tables</p>
|
||||
<span class="card-link">View Mockup →</span>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink to="/design-mockups/pages/board/ProfessionalBoardDashboard" class="mockup-card">
|
||||
<div class="card-icon card-icon--board">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="card-title">Board Dashboard</h2>
|
||||
<p class="card-description">Strategic insights, KPIs, governance features, and executive metrics</p>
|
||||
<span class="card-link">View Mockup →</span>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink to="/design-mockups/pages/member/ProfessionalMemberDashboard" class="mockup-card">
|
||||
<div class="card-icon card-icon--member">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="card-title">Member Dashboard</h2>
|
||||
<p class="card-description">Member portal with events, resources, community features, and benefits</p>
|
||||
<span class="card-link">View Mockup →</span>
|
||||
</NuxtLink>
|
||||
</section>
|
||||
|
||||
<section class="components-section">
|
||||
<h2 class="section-title">Core Components</h2>
|
||||
<div class="component-list">
|
||||
<div class="component-item">
|
||||
<span class="component-name">NeumorphicCard</span>
|
||||
<span class="component-status">✅ Complete</span>
|
||||
</div>
|
||||
<div class="component-item">
|
||||
<span class="component-name">ProfessionalButton</span>
|
||||
<span class="component-status">✅ Complete</span>
|
||||
</div>
|
||||
<div class="component-item">
|
||||
<span class="component-name">StatCard</span>
|
||||
<span class="component-status">✅ Complete</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<p>Neumorphic Design System • Professional & Inviting • Built with Vue 3 & Nuxt 3</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Disable authentication for mockups
|
||||
definePageMeta({
|
||||
auth: false,
|
||||
layout: false
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// Import the neumorphic system
|
||||
@import './styles/neumorphic-system.scss';
|
||||
|
||||
.mockup-index {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, $neutral-50 0%, $neutral-100 100%);
|
||||
padding: $space-8 $space-6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: $space-12;
|
||||
|
||||
.logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto $space-4;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: $font-heading;
|
||||
font-size: $text-4xl;
|
||||
font-weight: $font-bold;
|
||||
color: $neutral-800;
|
||||
margin-bottom: $space-2;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: $text-lg;
|
||||
color: $neutral-600;
|
||||
}
|
||||
}
|
||||
|
||||
.mockup-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: $space-6;
|
||||
margin-bottom: $space-12;
|
||||
}
|
||||
|
||||
.mockup-card {
|
||||
display: block;
|
||||
padding: $space-6;
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
border-radius: $radius-xl;
|
||||
box-shadow: $shadow-soft-md;
|
||||
text-decoration: none;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: $shadow-soft-lg;
|
||||
|
||||
.card-link {
|
||||
color: $primary-700;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: $shadow-inset-sm;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: $radius-lg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: $space-4;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
|
||||
svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
&--auth {
|
||||
background: linear-gradient(135deg, rgba($primary-500, 0.1), rgba($primary-600, 0.15));
|
||||
color: $primary-600;
|
||||
}
|
||||
|
||||
&--admin {
|
||||
background: linear-gradient(135deg, rgba($info-500, 0.1), rgba($info-500, 0.15));
|
||||
color: $info-500;
|
||||
}
|
||||
|
||||
&--board {
|
||||
background: linear-gradient(135deg, rgba($success-500, 0.1), rgba($success-500, 0.15));
|
||||
color: $success-500;
|
||||
}
|
||||
|
||||
&--member {
|
||||
background: linear-gradient(135deg, rgba($warning-500, 0.1), rgba($warning-500, 0.15));
|
||||
color: $warning-500;
|
||||
}
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-family: $font-heading;
|
||||
font-size: $text-xl;
|
||||
font-weight: $font-semibold;
|
||||
color: $neutral-800;
|
||||
margin-bottom: $space-2;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
font-size: $text-sm;
|
||||
color: $neutral-600;
|
||||
line-height: $leading-relaxed;
|
||||
margin-bottom: $space-4;
|
||||
}
|
||||
|
||||
.card-link {
|
||||
font-size: $text-sm;
|
||||
font-weight: $font-medium;
|
||||
color: $primary-600;
|
||||
transition: color $transition-base;
|
||||
}
|
||||
}
|
||||
|
||||
.components-section {
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
border-radius: $radius-xl;
|
||||
padding: $space-8;
|
||||
box-shadow: $shadow-soft-md;
|
||||
margin-bottom: $space-8;
|
||||
|
||||
.section-title {
|
||||
font-family: $font-heading;
|
||||
font-size: $text-2xl;
|
||||
font-weight: $font-semibold;
|
||||
color: $neutral-800;
|
||||
margin-bottom: $space-6;
|
||||
}
|
||||
|
||||
.component-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: $space-4;
|
||||
}
|
||||
|
||||
.component-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $space-3 $space-4;
|
||||
background: rgba($neutral-100, 0.5);
|
||||
border-radius: $radius-lg;
|
||||
|
||||
.component-name {
|
||||
font-family: $font-mono;
|
||||
font-size: $text-sm;
|
||||
color: $neutral-700;
|
||||
}
|
||||
|
||||
.component-status {
|
||||
font-size: $text-sm;
|
||||
color: $success-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: $space-6;
|
||||
|
||||
p {
|
||||
font-size: $text-sm;
|
||||
color: $neutral-600;
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode support
|
||||
@include dark-mode {
|
||||
.mockup-index {
|
||||
background: linear-gradient(135deg, $neutral-900 0%, $neutral-800 100%);
|
||||
}
|
||||
|
||||
.mockup-card {
|
||||
background: linear-gradient(145deg, $neutral-800, $neutral-700);
|
||||
|
||||
.card-title {
|
||||
color: $neutral-100;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: $neutral-400;
|
||||
}
|
||||
}
|
||||
|
||||
.components-section {
|
||||
background: linear-gradient(145deg, $neutral-800, $neutral-700);
|
||||
|
||||
.section-title {
|
||||
color: $neutral-100;
|
||||
}
|
||||
|
||||
.component-item {
|
||||
background: rgba($neutral-700, 0.5);
|
||||
|
||||
.component-name {
|
||||
color: $neutral-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
.title {
|
||||
color: $neutral-100;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: $neutral-400;
|
||||
}
|
||||
}
|
||||
|
||||
.footer p {
|
||||
color: $neutral-400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,820 +0,0 @@
|
|||
<template>
|
||||
<div class="admin-dashboard">
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="sidebar" :class="{ 'sidebar--collapsed': isSidebarCollapsed }">
|
||||
<div class="sidebar-header">
|
||||
<div class="sidebar-logo">
|
||||
<img src="/MONACOUSA-Flags_376x376.png" alt="MonacoUSA" />
|
||||
<span v-if="!isSidebarCollapsed" class="sidebar-title">Admin Portal</span>
|
||||
</div>
|
||||
<button @click="isSidebarCollapsed = !isSidebarCollapsed" class="sidebar-toggle">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<a
|
||||
v-for="item in navItems"
|
||||
:key="item.id"
|
||||
:class="['sidebar-item', { 'sidebar-item--active': activeNav === item.id }]"
|
||||
@click="activeNav = item.id"
|
||||
>
|
||||
<span class="sidebar-item-icon">
|
||||
<component :is="item.icon" />
|
||||
</span>
|
||||
<span v-if="!isSidebarCollapsed" class="sidebar-item-label">{{ item.label }}</span>
|
||||
<span v-if="!isSidebarCollapsed && item.badge" class="sidebar-item-badge">{{ item.badge }}</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar">
|
||||
<img src="https://via.placeholder.com/40" alt="Admin" />
|
||||
</div>
|
||||
<div v-if="!isSidebarCollapsed" class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">John Admin</div>
|
||||
<div class="sidebar-user-role">System Admin</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<!-- Top Bar -->
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<h1 class="topbar-title">Dashboard Overview</h1>
|
||||
<p class="topbar-subtitle">Welcome back, John. Here's what's happening today.</p>
|
||||
</div>
|
||||
|
||||
<div class="topbar-right">
|
||||
<button class="topbar-button">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="topbar-button topbar-button--notification">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span class="notification-badge">3</span>
|
||||
</button>
|
||||
|
||||
<div class="topbar-divider"></div>
|
||||
|
||||
<button class="topbar-user">
|
||||
<img src="https://via.placeholder.com/32" alt="User" class="topbar-user-avatar" />
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="stats-grid">
|
||||
<StatCard
|
||||
v-for="stat in stats"
|
||||
:key="stat.title"
|
||||
:title="stat.title"
|
||||
:value="stat.value"
|
||||
:change="stat.change"
|
||||
:trend="stat.trend"
|
||||
:icon="stat.icon"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Content Grid -->
|
||||
<div class="content-grid">
|
||||
<!-- Chart Card -->
|
||||
<NeumorphicCard class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">Revenue Overview</h2>
|
||||
<div class="card-actions">
|
||||
<button class="card-action">Day</button>
|
||||
<button class="card-action card-action--active">Week</button>
|
||||
<button class="card-action">Month</button>
|
||||
<button class="card-action">Year</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas ref="chartCanvas"></canvas>
|
||||
</div>
|
||||
</NeumorphicCard>
|
||||
|
||||
<!-- Activity Feed -->
|
||||
<NeumorphicCard class="activity-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">Recent Activity</h2>
|
||||
<button class="card-link">View All</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="activity-list">
|
||||
<div v-for="activity in activities" :key="activity.id" class="activity-item">
|
||||
<div class="activity-icon" :class="`activity-icon--${activity.type}`">
|
||||
<component :is="activity.icon" />
|
||||
</div>
|
||||
<div class="activity-content">
|
||||
<p class="activity-description">{{ activity.description }}</p>
|
||||
<span class="activity-time">{{ activity.time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NeumorphicCard>
|
||||
|
||||
<!-- Members Table -->
|
||||
<NeumorphicCard class="table-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">Recent Members</h2>
|
||||
<ProfessionalButton variant="outline" size="sm">
|
||||
View All Members
|
||||
</ProfessionalButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Member</th>
|
||||
<th>Status</th>
|
||||
<th>Joined</th>
|
||||
<th>Last Active</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="member in recentMembers" :key="member.id">
|
||||
<td>
|
||||
<div class="member-cell">
|
||||
<img :src="member.avatar" :alt="member.name" class="member-avatar" />
|
||||
<div>
|
||||
<div class="member-name">{{ member.name }}</div>
|
||||
<div class="member-email">{{ member.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge" :class="`status-badge--${member.status}`">
|
||||
{{ member.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ member.joined }}</td>
|
||||
<td>{{ member.lastActive }}</td>
|
||||
<td>
|
||||
<button class="table-action">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</NeumorphicCard>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import NeumorphicCard from '../../components/core/NeumorphicCard.vue';
|
||||
import ProfessionalButton from '../../components/core/ProfessionalButton.vue';
|
||||
import StatCard from '../../components/core/StatCard.vue';
|
||||
import Chart from 'chart.js/auto';
|
||||
|
||||
// Disable authentication for mockup
|
||||
definePageMeta({
|
||||
auth: false,
|
||||
layout: false
|
||||
});
|
||||
|
||||
// Icons (simplified for demo)
|
||||
const HomeIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/></svg>' };
|
||||
const UsersIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/></svg>' };
|
||||
const CalendarIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/></svg>' };
|
||||
const ChartIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/></svg>' };
|
||||
|
||||
// Data
|
||||
const isSidebarCollapsed = ref(false);
|
||||
const activeNav = ref('dashboard');
|
||||
const chartCanvas = ref<HTMLCanvasElement | null>(null);
|
||||
|
||||
const navItems = [
|
||||
{ id: 'dashboard', label: 'Dashboard', icon: HomeIcon },
|
||||
{ id: 'members', label: 'Members', icon: UsersIcon, badge: '127' },
|
||||
{ id: 'events', label: 'Events', icon: CalendarIcon, badge: '5' },
|
||||
{ id: 'analytics', label: 'Analytics', icon: ChartIcon },
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ title: 'Total Members', value: '1,247', change: '+12%', trend: 'up', icon: UsersIcon },
|
||||
{ title: 'Active Events', value: '18', change: '+3', trend: 'up', icon: CalendarIcon },
|
||||
{ title: 'Monthly Revenue', value: '$48,392', change: '+8%', trend: 'up', icon: ChartIcon },
|
||||
{ title: 'Engagement Rate', value: '87%', change: '-2%', trend: 'down', icon: ChartIcon },
|
||||
];
|
||||
|
||||
const activities = [
|
||||
{ id: 1, type: 'user', icon: UsersIcon, description: 'New member registration: Sarah Johnson', time: '5 minutes ago' },
|
||||
{ id: 2, type: 'event', icon: CalendarIcon, description: 'Annual Gala event updated', time: '1 hour ago' },
|
||||
{ id: 3, type: 'payment', icon: ChartIcon, description: 'Payment received from Michael Brown', time: '2 hours ago' },
|
||||
{ id: 4, type: 'user', icon: UsersIcon, description: 'Member profile updated: Robert Davis', time: '3 hours ago' },
|
||||
];
|
||||
|
||||
const recentMembers = [
|
||||
{ id: 1, name: 'Sarah Johnson', email: 'sarah@example.com', avatar: 'https://via.placeholder.com/40', status: 'active', joined: 'Jan 15, 2024', lastActive: '2 hours ago' },
|
||||
{ id: 2, name: 'Michael Brown', email: 'michael@example.com', avatar: 'https://via.placeholder.com/40', status: 'active', joined: 'Jan 10, 2024', lastActive: '1 day ago' },
|
||||
{ id: 3, name: 'Emma Wilson', email: 'emma@example.com', avatar: 'https://via.placeholder.com/40', status: 'pending', joined: 'Jan 8, 2024', lastActive: '3 days ago' },
|
||||
{ id: 4, name: 'James Taylor', email: 'james@example.com', avatar: 'https://via.placeholder.com/40', status: 'inactive', joined: 'Dec 20, 2023', lastActive: '1 week ago' },
|
||||
];
|
||||
|
||||
// Initialize chart
|
||||
onMounted(() => {
|
||||
if (chartCanvas.value) {
|
||||
new Chart(chartCanvas.value, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
datasets: [{
|
||||
label: 'Revenue',
|
||||
data: [12000, 19000, 15000, 25000, 22000, 30000, 28000],
|
||||
borderColor: '#DC2626',
|
||||
backgroundColor: 'rgba(220, 38, 38, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
display: true,
|
||||
color: 'rgba(0, 0, 0, 0.05)'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/neumorphic-system.scss';
|
||||
|
||||
.admin-dashboard {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background: $neutral-100;
|
||||
}
|
||||
|
||||
// Sidebar
|
||||
.sidebar {
|
||||
width: $sidebar-width;
|
||||
background: white;
|
||||
box-shadow: $shadow-soft-md;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: width $transition-base;
|
||||
|
||||
&--collapsed {
|
||||
width: $sidebar-width-collapsed;
|
||||
}
|
||||
|
||||
&-header {
|
||||
padding: $space-6;
|
||||
border-bottom: 1px solid $neutral-200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-3;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: $radius-lg;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-weight: $font-semibold;
|
||||
color: $neutral-800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: $space-2;
|
||||
cursor: pointer;
|
||||
color: $neutral-600;
|
||||
border-radius: $radius-md;
|
||||
transition: all $transition-base;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $neutral-100;
|
||||
}
|
||||
}
|
||||
|
||||
&-nav {
|
||||
flex: 1;
|
||||
padding: $space-4;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-3;
|
||||
padding: $space-3 $space-4;
|
||||
margin-bottom: $space-2;
|
||||
border-radius: $radius-lg;
|
||||
color: $neutral-600;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: $neutral-100;
|
||||
color: $neutral-800;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: linear-gradient(135deg, $primary-500, $primary-600);
|
||||
color: white;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-label {
|
||||
font-size: $text-sm;
|
||||
font-weight: $font-medium;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-badge {
|
||||
margin-left: auto;
|
||||
padding: 2px 8px;
|
||||
background: $primary-100;
|
||||
color: $primary-700;
|
||||
border-radius: $radius-full;
|
||||
font-size: $text-xs;
|
||||
font-weight: $font-semibold;
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
padding: $space-4;
|
||||
border-top: 1px solid $neutral-200;
|
||||
}
|
||||
|
||||
&-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-3;
|
||||
padding: $space-3;
|
||||
|
||||
&-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: $radius-full;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
&-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: $text-sm;
|
||||
font-weight: $font-semibold;
|
||||
color: $neutral-800;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&-role {
|
||||
font-size: $text-xs;
|
||||
color: $neutral-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main Content
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// Topbar
|
||||
.topbar {
|
||||
background: white;
|
||||
padding: $space-6;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: $text-2xl;
|
||||
font-weight: $font-bold;
|
||||
color: $neutral-800;
|
||||
margin-bottom: $space-1;
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
font-size: $text-sm;
|
||||
color: $neutral-600;
|
||||
}
|
||||
|
||||
&-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-3;
|
||||
}
|
||||
|
||||
&-button {
|
||||
position: relative;
|
||||
padding: $space-2;
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: $radius-lg;
|
||||
cursor: pointer;
|
||||
color: $neutral-600;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
transition: all $transition-base;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-soft-md;
|
||||
color: $neutral-800;
|
||||
}
|
||||
|
||||
&--notification {
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: $error-500;
|
||||
color: white;
|
||||
border-radius: $radius-full;
|
||||
font-size: $text-xs;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: $font-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: $neutral-200;
|
||||
}
|
||||
|
||||
&-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-2;
|
||||
padding: $space-2;
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: $radius-lg;
|
||||
cursor: pointer;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-soft-md;
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: $radius-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats Grid
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: $space-6;
|
||||
padding: $space-6;
|
||||
}
|
||||
|
||||
// Content Grid
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: $space-6;
|
||||
padding: 0 $space-6 $space-6;
|
||||
|
||||
@media (max-width: $breakpoint-lg) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Card styles
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: $text-lg;
|
||||
font-weight: $font-semibold;
|
||||
color: $neutral-800;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: $space-2;
|
||||
}
|
||||
|
||||
.card-action {
|
||||
padding: $space-2 $space-3;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: $radius-md;
|
||||
font-size: $text-sm;
|
||||
color: $neutral-600;
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
background: $neutral-100;
|
||||
color: $neutral-800;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $primary-500;
|
||||
color: white;
|
||||
box-shadow: $shadow-soft-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.card-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $primary-600;
|
||||
font-size: $text-sm;
|
||||
font-weight: $font-medium;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $primary-700;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
// Chart
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
padding: $space-4 0;
|
||||
}
|
||||
|
||||
// Activity List
|
||||
.activity-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $space-4;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
display: flex;
|
||||
gap: $space-3;
|
||||
|
||||
&-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: $radius-lg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&--user {
|
||||
background: $info-100;
|
||||
color: $info-500;
|
||||
}
|
||||
|
||||
&--event {
|
||||
background: $warning-100;
|
||||
color: $warning-500;
|
||||
}
|
||||
|
||||
&--payment {
|
||||
background: $success-100;
|
||||
color: $success-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&-description {
|
||||
font-size: $text-sm;
|
||||
color: $neutral-700;
|
||||
margin-bottom: $space-1;
|
||||
}
|
||||
|
||||
&-time {
|
||||
font-size: $text-xs;
|
||||
color: $neutral-500;
|
||||
}
|
||||
}
|
||||
|
||||
// Table
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
|
||||
thead {
|
||||
tr {
|
||||
border-bottom: 2px solid $neutral-200;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: $space-3;
|
||||
font-size: $text-xs;
|
||||
font-weight: $font-semibold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: $neutral-600;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border-bottom: 1px solid $neutral-100;
|
||||
transition: background $transition-base;
|
||||
|
||||
&:hover {
|
||||
background: $neutral-50;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: $space-3;
|
||||
font-size: $text-sm;
|
||||
color: $neutral-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.member-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-3;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: $radius-full;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-weight: $font-medium;
|
||||
color: $neutral-800;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.member-email {
|
||||
font-size: $text-xs;
|
||||
color: $neutral-500;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: $radius-full;
|
||||
font-size: $text-xs;
|
||||
font-weight: $font-medium;
|
||||
|
||||
&--active {
|
||||
background: $success-100;
|
||||
color: $success-500;
|
||||
}
|
||||
|
||||
&--pending {
|
||||
background: $warning-100;
|
||||
color: $warning-500;
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
background: $neutral-100;
|
||||
color: $neutral-500;
|
||||
}
|
||||
}
|
||||
|
||||
.table-action {
|
||||
padding: $space-2;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: $neutral-500;
|
||||
border-radius: $radius-md;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
background: $neutral-100;
|
||||
color: $neutral-700;
|
||||
}
|
||||
}</style>
|
||||
|
|
@ -1,425 +0,0 @@
|
|||
<template>
|
||||
<div class="login-page">
|
||||
<div class="login-container">
|
||||
<!-- Left Side - Branding -->
|
||||
<div class="login-branding">
|
||||
<div class="branding-content">
|
||||
<div class="logo-container">
|
||||
<img src="/MONACOUSA-Flags_376x376.png" alt="MonacoUSA" class="logo" />
|
||||
</div>
|
||||
<h1 class="brand-title">MonacoUSA Portal</h1>
|
||||
<p class="brand-tagline">Excellence in Partnership</p>
|
||||
|
||||
<div class="feature-list">
|
||||
<div class="feature-item" v-for="feature in features" :key="feature">
|
||||
<svg class="feature-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span>{{ feature }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Background pattern -->
|
||||
<div class="branding-pattern"></div>
|
||||
</div>
|
||||
|
||||
<!-- Right Side - Login Form -->
|
||||
<div class="login-form-section">
|
||||
<NeumorphicCard size="lg" elevation="lg" class="login-card">
|
||||
<template #header>
|
||||
<h2 class="login-title">Welcome Back</h2>
|
||||
<p class="login-subtitle">Sign in to access your account</p>
|
||||
</template>
|
||||
|
||||
<form @submit.prevent="handleLogin" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label">Email Address</label>
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
id="email"
|
||||
v-model="credentials.email"
|
||||
type="email"
|
||||
class="form-input"
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
/>
|
||||
<span class="input-icon">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
id="password"
|
||||
v-model="credentials.password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
class="form-input"
|
||||
placeholder="Enter your password"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@click="showPassword = !showPassword"
|
||||
class="input-icon clickable"
|
||||
>
|
||||
<svg v-if="!showPassword" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
<svg v-else fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-options">
|
||||
<label class="checkbox-label">
|
||||
<input v-model="credentials.rememberMe" type="checkbox" class="checkbox-input" />
|
||||
<span>Remember me</span>
|
||||
</label>
|
||||
|
||||
<button type="button" class="forgot-password-link" @click="showForgotPassword = true">
|
||||
Forgot password?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ProfessionalButton
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
block
|
||||
:loading="loading"
|
||||
class="login-button"
|
||||
>
|
||||
Sign In
|
||||
</ProfessionalButton>
|
||||
</form>
|
||||
|
||||
<template #footer>
|
||||
<div class="login-footer">
|
||||
<p class="signup-prompt">
|
||||
Don't have an account?
|
||||
<a href="/signup" class="signup-link">Create Account</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</NeumorphicCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import NeumorphicCard from '../../components/core/NeumorphicCard.vue';
|
||||
import ProfessionalButton from '../../components/core/ProfessionalButton.vue';
|
||||
|
||||
// Disable authentication for mockup
|
||||
definePageMeta({
|
||||
auth: false,
|
||||
layout: false
|
||||
});
|
||||
|
||||
// Data
|
||||
const credentials = ref({
|
||||
email: '',
|
||||
password: '',
|
||||
rememberMe: false
|
||||
});
|
||||
|
||||
const showPassword = ref(false);
|
||||
const loading = ref(false);
|
||||
const showForgotPassword = ref(false);
|
||||
|
||||
const features = [
|
||||
'Secure Member Access',
|
||||
'Event Management',
|
||||
'Document Library',
|
||||
'Payment Processing'
|
||||
];
|
||||
|
||||
// Methods
|
||||
const handleLogin = async () => {
|
||||
loading.value = true;
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
console.log('Login with:', credentials.value);
|
||||
}, 2000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/neumorphic-system.scss';
|
||||
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, $neutral-50 0%, $neutral-100 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
min-height: 100vh;
|
||||
|
||||
@media (max-width: $breakpoint-md) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Left side - Branding
|
||||
.login-branding {
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, $primary-600, $primary-800);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $space-12;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-width: $breakpoint-md) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.branding-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 0 auto $space-6;
|
||||
background: white;
|
||||
border-radius: $radius-2xl;
|
||||
padding: $space-4;
|
||||
box-shadow: $shadow-soft-lg;
|
||||
|
||||
.logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
font-family: $font-heading;
|
||||
font-size: $text-3xl;
|
||||
font-weight: $font-bold;
|
||||
margin-bottom: $space-2;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.brand-tagline {
|
||||
font-size: $text-lg;
|
||||
opacity: 0.9;
|
||||
margin-bottom: $space-12;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
text-align: left;
|
||||
max-width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-3;
|
||||
margin-bottom: $space-4;
|
||||
font-size: $text-base;
|
||||
|
||||
.feature-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
color: rgba(white, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.branding-pattern {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Right side - Login form
|
||||
.login-form-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $space-8;
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-family: $font-heading;
|
||||
font-size: $text-2xl;
|
||||
font-weight: $font-bold;
|
||||
color: $neutral-800;
|
||||
margin-bottom: $space-2;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: $text-base;
|
||||
color: $neutral-600;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
.form-group {
|
||||
margin-bottom: $space-5;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: $text-sm;
|
||||
font-weight: $font-medium;
|
||||
color: $neutral-700;
|
||||
margin-bottom: $space-2;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: $space-3 $space-4;
|
||||
padding-right: $space-12;
|
||||
background: $neutral-50;
|
||||
border: none;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-inset-sm;
|
||||
font-size: $text-base;
|
||||
color: $neutral-800;
|
||||
transition: all $transition-base;
|
||||
|
||||
&::placeholder {
|
||||
color: $neutral-400;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: $shadow-inset-md, 0 0 0 3px rgba($primary-500, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
right: $space-3;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: $neutral-400;
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
transition: color $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $neutral-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: $space-6;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-2;
|
||||
font-size: $text-sm;
|
||||
color: $neutral-700;
|
||||
cursor: pointer;
|
||||
|
||||
.checkbox-input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: $radius-sm;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-password-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $primary-600;
|
||||
font-size: $text-sm;
|
||||
font-weight: $font-medium;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: color $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $primary-700;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.login-button {
|
||||
margin-top: $space-4;
|
||||
}
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
|
||||
.signup-prompt {
|
||||
font-size: $text-sm;
|
||||
color: $neutral-600;
|
||||
}
|
||||
|
||||
.signup-link {
|
||||
color: $primary-600;
|
||||
font-weight: $font-medium;
|
||||
text-decoration: none;
|
||||
margin-left: $space-1;
|
||||
transition: color $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $primary-700;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode support
|
||||
@include dark-mode {
|
||||
.login-page {
|
||||
background: linear-gradient(135deg, $neutral-900 0%, $neutral-800 100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,361 +0,0 @@
|
|||
// MonacoUSA Portal Design System
|
||||
// Professional Neumorphic Design with Enhanced Components
|
||||
|
||||
// ========================================
|
||||
// 1. COLOR PALETTE
|
||||
// ========================================
|
||||
|
||||
// Brand Colors
|
||||
$monaco-red: #CC0000;
|
||||
$monaco-dark-red: #990000;
|
||||
$monaco-light-red: #FF3333;
|
||||
|
||||
// Primary Colors (Monaco Red)
|
||||
$primary-50: #FEF2F2;
|
||||
$primary-100: #FEE2E2;
|
||||
$primary-200: #FECACA;
|
||||
$primary-300: #FCA5A5;
|
||||
$primary-400: #F87171;
|
||||
$primary-500: #EF4444;
|
||||
$primary-600: $monaco-red;
|
||||
$primary-700: $monaco-dark-red;
|
||||
$primary-800: #660000;
|
||||
$primary-900: #450A0A;
|
||||
|
||||
// Neutral Colors
|
||||
$neutral-50: #F9FAFB;
|
||||
$neutral-100: #F3F4F6;
|
||||
$neutral-200: #E5E7EB;
|
||||
$neutral-300: #D1D5DB;
|
||||
$neutral-400: #9CA3AF;
|
||||
$neutral-500: #6B7280;
|
||||
$neutral-600: #4B5563;
|
||||
$neutral-700: #374151;
|
||||
$neutral-800: #1F2937;
|
||||
$neutral-900: #111827;
|
||||
|
||||
// Blue Colors (for dropdowns and accents)
|
||||
$blue-50: #EFF6FF;
|
||||
$blue-100: #DBEAFE;
|
||||
$blue-200: #BFDBFE;
|
||||
$blue-300: #93C5FD;
|
||||
$blue-400: #60A5FA;
|
||||
$blue-500: #3B82F6;
|
||||
$blue-600: #2563EB;
|
||||
$blue-700: #1D4ED8;
|
||||
$blue-800: #1E40AF;
|
||||
$blue-900: #1E3A8A;
|
||||
|
||||
// Semantic Colors
|
||||
$success-500: #10B981;
|
||||
$warning-500: #F59E0B;
|
||||
$error-500: #EF4444;
|
||||
$info-500: #3B82F6;
|
||||
|
||||
// ========================================
|
||||
// 2. TYPOGRAPHY
|
||||
// ========================================
|
||||
|
||||
$font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
$font-heading: 'Inter', $font-sans;
|
||||
$font-mono: 'Fira Code', 'Courier New', 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
|
||||
$text-5xl: 3rem; // 48px
|
||||
|
||||
// Font Weights
|
||||
$font-light: 300;
|
||||
$font-normal: 400;
|
||||
$font-medium: 500;
|
||||
$font-semibold: 600;
|
||||
$font-bold: 700;
|
||||
|
||||
// Line Heights
|
||||
$leading-tight: 1.25;
|
||||
$leading-normal: 1.5;
|
||||
$leading-relaxed: 1.625;
|
||||
|
||||
// ========================================
|
||||
// 3. SPACING SYSTEM
|
||||
// ========================================
|
||||
|
||||
$space-0: 0;
|
||||
$space-1: 0.25rem; // 4px
|
||||
$space-2: 0.5rem; // 8px
|
||||
$space-3: 0.75rem; // 12px
|
||||
$space-4: 1rem; // 16px
|
||||
$space-5: 1.25rem; // 20px
|
||||
$space-6: 1.5rem; // 24px
|
||||
$space-8: 2rem; // 32px
|
||||
$space-10: 2.5rem; // 40px
|
||||
$space-12: 3rem; // 48px
|
||||
$space-16: 4rem; // 64px
|
||||
$space-20: 5rem; // 80px
|
||||
|
||||
// ========================================
|
||||
// 4. NEUMORPHIC SHADOWS
|
||||
// ========================================
|
||||
|
||||
// Soft Shadows (light theme)
|
||||
$shadow-soft-xs:
|
||||
2px 2px 4px rgba(0, 0, 0, 0.07),
|
||||
-2px -2px 4px rgba(255, 255, 255, 0.95);
|
||||
|
||||
$shadow-soft-sm:
|
||||
4px 4px 8px rgba(0, 0, 0, 0.08),
|
||||
-4px -4px 8px rgba(255, 255, 255, 0.95);
|
||||
|
||||
$shadow-soft-md:
|
||||
8px 8px 16px rgba(0, 0, 0, 0.1),
|
||||
-8px -8px 16px rgba(255, 255, 255, 0.95);
|
||||
|
||||
$shadow-soft-lg:
|
||||
12px 12px 24px rgba(0, 0, 0, 0.12),
|
||||
-12px -12px 24px rgba(255, 255, 255, 0.95);
|
||||
|
||||
$shadow-soft-xl:
|
||||
16px 16px 32px rgba(0, 0, 0, 0.14),
|
||||
-16px -16px 32px rgba(255, 255, 255, 0.95);
|
||||
|
||||
// Inset Shadows (for pressed/input states)
|
||||
$shadow-inset-sm:
|
||||
inset 4px 4px 8px rgba(0, 0, 0, 0.1),
|
||||
inset -4px -4px 8px rgba(255, 255, 255, 0.95);
|
||||
|
||||
$shadow-inset-md:
|
||||
inset 6px 6px 12px rgba(0, 0, 0, 0.15),
|
||||
inset -6px -6px 12px rgba(255, 255, 255, 0.95);
|
||||
|
||||
// Glassmorphic Shadow
|
||||
$shadow-glass:
|
||||
0 8px 32px rgba(0, 0, 0, 0.1),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.3);
|
||||
|
||||
// Morphing Dropdown Shadow
|
||||
$shadow-morphing:
|
||||
0 10px 40px rgba(0, 0, 0, 0.12),
|
||||
0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
|
||||
// ========================================
|
||||
// 5. BORDER RADIUS
|
||||
// ========================================
|
||||
|
||||
$radius-sm: 0.375rem; // 6px
|
||||
$radius-md: 0.5rem; // 8px
|
||||
$radius-lg: 0.75rem; // 12px
|
||||
$radius-xl: 1rem; // 16px
|
||||
$radius-2xl: 1.5rem; // 24px
|
||||
$radius-3xl: 2rem; // 32px
|
||||
$radius-full: 9999px;
|
||||
|
||||
// ========================================
|
||||
// 6. TRANSITIONS
|
||||
// ========================================
|
||||
|
||||
$transition-base: 0.3s ease;
|
||||
$transition-fast: 0.15s ease;
|
||||
$transition-slow: 0.5s ease;
|
||||
|
||||
// Spring Animation (for dropdowns)
|
||||
$spring-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
$spring-smooth: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
// ========================================
|
||||
// 7. BREAKPOINTS
|
||||
// ========================================
|
||||
|
||||
$breakpoint-sm: 640px;
|
||||
$breakpoint-md: 768px;
|
||||
$breakpoint-lg: 1024px;
|
||||
$breakpoint-xl: 1280px;
|
||||
$breakpoint-2xl: 1536px;
|
||||
|
||||
// ========================================
|
||||
// 8. Z-INDEX LAYERS
|
||||
// ========================================
|
||||
|
||||
$z-base: 0;
|
||||
$z-dropdown: 10;
|
||||
$z-sticky: 20;
|
||||
$z-overlay: 30;
|
||||
$z-modal: 40;
|
||||
$z-popover: 50;
|
||||
$z-tooltip: 60;
|
||||
$z-notification: 70;
|
||||
|
||||
// ========================================
|
||||
// 9. GLASSMORPHISM MIXINS
|
||||
// ========================================
|
||||
|
||||
@mixin glassmorphism($bg-opacity: 0.8, $blur: 12px) {
|
||||
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);
|
||||
}
|
||||
|
||||
@mixin glassmorphism-blue($bg-opacity: 0.3, $blur: 12px) {
|
||||
background: rgba($blue-50, $bg-opacity);
|
||||
backdrop-filter: blur($blur);
|
||||
-webkit-backdrop-filter: blur($blur);
|
||||
border: 1px solid rgba($blue-200, 0.3);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 10. COMPONENT MIXINS
|
||||
// ========================================
|
||||
|
||||
// Neumorphic Card
|
||||
@mixin neumorphic-card($elevation: 'md') {
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
border-radius: $radius-xl;
|
||||
|
||||
@if $elevation == 'sm' {
|
||||
box-shadow: $shadow-soft-sm;
|
||||
} @else if $elevation == 'md' {
|
||||
box-shadow: $shadow-soft-md;
|
||||
} @else if $elevation == 'lg' {
|
||||
box-shadow: $shadow-soft-lg;
|
||||
} @else if $elevation == 'xl' {
|
||||
box-shadow: $shadow-soft-xl;
|
||||
}
|
||||
}
|
||||
|
||||
// Neumorphic Button
|
||||
@mixin neumorphic-button() {
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
box-shadow: $shadow-soft-sm;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-soft-md;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: $shadow-inset-sm;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Morphing Dropdown
|
||||
@mixin morphing-dropdown() {
|
||||
@include glassmorphism-blue(0.8, 16px);
|
||||
box-shadow: $shadow-morphing;
|
||||
border-radius: $radius-lg;
|
||||
overflow: hidden;
|
||||
animation: morphIn 0.3s $spring-smooth;
|
||||
}
|
||||
|
||||
// Focus Ring
|
||||
@mixin focus-ring($color: $primary-500) {
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 3px rgba($color, 0.1),
|
||||
$shadow-soft-md;
|
||||
border-color: rgba($color, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 11. ANIMATIONS
|
||||
// ========================================
|
||||
|
||||
@keyframes morphIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 12. UTILITY CLASSES
|
||||
// ========================================
|
||||
|
||||
.backdrop-blur {
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.glass-effect {
|
||||
@include glassmorphism();
|
||||
}
|
||||
|
||||
.glass-blue {
|
||||
@include glassmorphism-blue();
|
||||
}
|
||||
|
||||
.shadow-neumorphic {
|
||||
box-shadow: $shadow-soft-md;
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
background: linear-gradient(135deg, $primary-600, $primary-800);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 13. RESPONSIVE UTILITIES
|
||||
// ========================================
|
||||
|
||||
@mixin responsive($breakpoint) {
|
||||
@media (min-width: $breakpoint) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage: @include responsive($breakpoint-md) { ... }
|
||||
|
||||
// ========================================
|
||||
// 14. DARK MODE SUPPORT
|
||||
// ========================================
|
||||
|
||||
@mixin dark-mode {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@content;
|
||||
}
|
||||
|
||||
[data-theme="dark"] & {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
// MonacoUSA Portal - Neumorphic Design System
|
||||
// Professional, Modern, and Inviting Design Language
|
||||
|
||||
// ============================================
|
||||
// 1. Color Palette
|
||||
// ============================================
|
||||
|
||||
// Primary - Monaco Red (Sophisticated)
|
||||
$primary-50: #FEF2F2;
|
||||
$primary-100: #FEE2E2;
|
||||
$primary-200: #FECACA;
|
||||
$primary-300: #FCA5A5;
|
||||
$primary-400: #F87171;
|
||||
$primary-500: #DC2626; // Main brand
|
||||
$primary-600: #B91C1C; // Hover states
|
||||
$primary-700: #991B1B;
|
||||
$primary-800: #7F1D1D;
|
||||
$primary-900: #450A0A;
|
||||
|
||||
// Neutral Grays (Professional Workspace)
|
||||
$neutral-50: #FAFAFA;
|
||||
$neutral-100: #F5F5F5;
|
||||
$neutral-200: #E5E5E5;
|
||||
$neutral-300: #D4D4D4;
|
||||
$neutral-400: #A3A3A3;
|
||||
$neutral-500: #737373;
|
||||
$neutral-600: #525252;
|
||||
$neutral-700: #404040;
|
||||
$neutral-800: #262626;
|
||||
$neutral-900: #171717;
|
||||
|
||||
// Semantic Colors
|
||||
$success-500: #10B981;
|
||||
$success-100: #D1FAE5;
|
||||
$warning-500: #F59E0B;
|
||||
$warning-100: #FEF3C7;
|
||||
$error-500: #EF4444;
|
||||
$error-100: #FEE2E2;
|
||||
$info-500: #3B82F6;
|
||||
$info-100: #DBEAFE;
|
||||
|
||||
// ============================================
|
||||
// 2. Neumorphic Shadow System
|
||||
// ============================================
|
||||
|
||||
// Light Mode Shadows
|
||||
$shadow-soft-xs: 2px 2px 4px rgba(0, 0, 0, 0.05), -2px -2px 4px rgba(255, 255, 255, 0.8);
|
||||
$shadow-soft-sm: 4px 4px 8px rgba(0, 0, 0, 0.08), -4px -4px 8px rgba(255, 255, 255, 0.9);
|
||||
$shadow-soft-md: 8px 8px 16px rgba(0, 0, 0, 0.1), -8px -8px 16px rgba(255, 255, 255, 0.95);
|
||||
$shadow-soft-lg: 15px 15px 30px rgba(0, 0, 0, 0.12), -15px -15px 30px rgba(255, 255, 255, 1);
|
||||
$shadow-soft-xl: 20px 20px 40px rgba(0, 0, 0, 0.15), -20px -20px 40px rgba(255, 255, 255, 1);
|
||||
|
||||
// Inset Shadows (for pressed states)
|
||||
$shadow-inset-sm: inset 2px 2px 4px rgba(0, 0, 0, 0.08), inset -2px -2px 4px rgba(255, 255, 255, 0.9);
|
||||
$shadow-inset-md: inset 4px 4px 8px rgba(0, 0, 0, 0.1), inset -4px -4px 8px rgba(255, 255, 255, 0.95);
|
||||
$shadow-inset-lg: inset 8px 8px 16px rgba(0, 0, 0, 0.12), inset -8px -8px 16px rgba(255, 255, 255, 1);
|
||||
|
||||
// Dark Mode Shadows
|
||||
$shadow-dark-soft-sm: 4px 4px 8px rgba(0, 0, 0, 0.3), -4px -4px 8px rgba(255, 255, 255, 0.02);
|
||||
$shadow-dark-soft-md: 8px 8px 16px rgba(0, 0, 0, 0.4), -8px -8px 16px rgba(255, 255, 255, 0.03);
|
||||
$shadow-dark-soft-lg: 15px 15px 30px rgba(0, 0, 0, 0.5), -15px -15px 30px rgba(255, 255, 255, 0.04);
|
||||
$shadow-dark-inset-md: inset 4px 4px 8px rgba(0, 0, 0, 0.4), inset -4px -4px 8px rgba(255, 255, 255, 0.03);
|
||||
|
||||
// ============================================
|
||||
// 3. Typography System
|
||||
// ============================================
|
||||
|
||||
$font-heading: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
$font-body: 'Inter', 'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
$font-mono: 'JetBrains Mono', 'Monaco', 'Courier New', monospace;
|
||||
|
||||
// Type Scale (Major Third - 1.25)
|
||||
$text-xs: 0.75rem; // 12px
|
||||
$text-sm: 0.875rem; // 14px
|
||||
$text-base: 1rem; // 16px
|
||||
$text-lg: 1.25rem; // 20px
|
||||
$text-xl: 1.563rem; // 25px
|
||||
$text-2xl: 1.953rem; // 31px
|
||||
$text-3xl: 2.441rem; // 39px
|
||||
$text-4xl: 3.052rem; // 49px
|
||||
|
||||
// Font Weights
|
||||
$font-light: 300;
|
||||
$font-regular: 400;
|
||||
$font-medium: 500;
|
||||
$font-semibold: 600;
|
||||
$font-bold: 700;
|
||||
|
||||
// Line Heights
|
||||
$leading-tight: 1.25;
|
||||
$leading-normal: 1.5;
|
||||
$leading-relaxed: 1.75;
|
||||
|
||||
// ============================================
|
||||
// 4. Spacing System
|
||||
// ============================================
|
||||
|
||||
$space-0: 0;
|
||||
$space-1: 0.25rem; // 4px
|
||||
$space-2: 0.5rem; // 8px
|
||||
$space-3: 0.75rem; // 12px
|
||||
$space-4: 1rem; // 16px
|
||||
$space-5: 1.25rem; // 20px
|
||||
$space-6: 1.5rem; // 24px
|
||||
$space-8: 2rem; // 32px
|
||||
$space-10: 2.5rem; // 40px
|
||||
$space-12: 3rem; // 48px
|
||||
$space-16: 4rem; // 64px
|
||||
$space-20: 5rem; // 80px
|
||||
$space-24: 6rem; // 96px
|
||||
|
||||
// ============================================
|
||||
// 5. Border Radius
|
||||
// ============================================
|
||||
|
||||
$radius-sm: 0.375rem; // 6px
|
||||
$radius-md: 0.5rem; // 8px
|
||||
$radius-lg: 0.75rem; // 12px
|
||||
$radius-xl: 1rem; // 16px
|
||||
$radius-2xl: 1.5rem; // 24px
|
||||
$radius-3xl: 2rem; // 32px
|
||||
$radius-full: 9999px; // Full round
|
||||
|
||||
// ============================================
|
||||
// 6. Transitions
|
||||
// ============================================
|
||||
|
||||
$transition-fast: 150ms ease-in-out;
|
||||
$transition-base: 250ms ease-in-out;
|
||||
$transition-slow: 350ms ease-in-out;
|
||||
$transition-slower: 500ms ease-in-out;
|
||||
|
||||
// Easing Functions
|
||||
$ease-in-out-soft: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
$ease-out-soft: cubic-bezier(0, 0, 0.2, 1);
|
||||
$ease-in-soft: cubic-bezier(0.4, 0, 1, 1);
|
||||
|
||||
// ============================================
|
||||
// 7. Breakpoints
|
||||
// ============================================
|
||||
|
||||
$breakpoint-xs: 475px;
|
||||
$breakpoint-sm: 640px;
|
||||
$breakpoint-md: 768px;
|
||||
$breakpoint-lg: 1024px;
|
||||
$breakpoint-xl: 1280px;
|
||||
$breakpoint-2xl: 1536px;
|
||||
|
||||
// ============================================
|
||||
// 8. Z-Index Scale
|
||||
// ============================================
|
||||
|
||||
$z-base: 0;
|
||||
$z-dropdown: 1000;
|
||||
$z-sticky: 1020;
|
||||
$z-fixed: 1030;
|
||||
$z-modal-backdrop: 1040;
|
||||
$z-modal: 1050;
|
||||
$z-popover: 1060;
|
||||
$z-tooltip: 1070;
|
||||
$z-notification: 1080;
|
||||
|
||||
// ============================================
|
||||
// 9. Component-Specific Variables
|
||||
// ============================================
|
||||
|
||||
// Buttons
|
||||
$button-height-sm: 2rem; // 32px
|
||||
$button-height-md: 2.5rem; // 40px
|
||||
$button-height-lg: 3rem; // 48px
|
||||
$button-padding-x: 1.5rem; // 24px
|
||||
$button-border-width: 0;
|
||||
|
||||
// Cards
|
||||
$card-padding: 1.5rem;
|
||||
$card-background: $neutral-100;
|
||||
$card-border-radius: $radius-xl;
|
||||
|
||||
// Inputs
|
||||
$input-height: 2.75rem; // 44px
|
||||
$input-padding-x: 1rem; // 16px
|
||||
$input-border-width: 0;
|
||||
$input-background: $neutral-100;
|
||||
$input-border-radius: $radius-lg;
|
||||
|
||||
// Sidebar
|
||||
$sidebar-width: 280px;
|
||||
$sidebar-width-collapsed: 80px;
|
||||
$sidebar-background: $neutral-100;
|
||||
|
||||
// ============================================
|
||||
// 10. Mixins
|
||||
// ============================================
|
||||
|
||||
@mixin neumorphic($size: 'md', $type: 'raised', $dark: false) {
|
||||
@if $dark {
|
||||
@if $type == 'raised' {
|
||||
@if $size == 'sm' {
|
||||
box-shadow: $shadow-dark-soft-sm;
|
||||
} @else if $size == 'md' {
|
||||
box-shadow: $shadow-dark-soft-md;
|
||||
} @else if $size == 'lg' {
|
||||
box-shadow: $shadow-dark-soft-lg;
|
||||
}
|
||||
} @else if $type == 'pressed' {
|
||||
box-shadow: $shadow-dark-inset-md;
|
||||
}
|
||||
} @else {
|
||||
@if $type == 'raised' {
|
||||
@if $size == 'sm' {
|
||||
box-shadow: $shadow-soft-sm;
|
||||
} @else if $size == 'md' {
|
||||
box-shadow: $shadow-soft-md;
|
||||
} @else if $size == 'lg' {
|
||||
box-shadow: $shadow-soft-lg;
|
||||
} @else if $size == 'xl' {
|
||||
box-shadow: $shadow-soft-xl;
|
||||
}
|
||||
} @else if $type == 'pressed' {
|
||||
@if $size == 'sm' {
|
||||
box-shadow: $shadow-inset-sm;
|
||||
} @else if $size == 'md' {
|
||||
box-shadow: $shadow-inset-md;
|
||||
} @else if $size == 'lg' {
|
||||
box-shadow: $shadow-inset-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin hover-lift($amount: 2px) {
|
||||
transition: transform $transition-base, box-shadow $transition-base;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-$amount);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin focus-ring($color: $primary-500) {
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba($color, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@mixin responsive($breakpoint) {
|
||||
@if $breakpoint == 'sm' {
|
||||
@media (min-width: $breakpoint-sm) { @content; }
|
||||
} @else if $breakpoint == 'md' {
|
||||
@media (min-width: $breakpoint-md) { @content; }
|
||||
} @else if $breakpoint == 'lg' {
|
||||
@media (min-width: $breakpoint-lg) { @content; }
|
||||
} @else if $breakpoint == 'xl' {
|
||||
@media (min-width: $breakpoint-xl) { @content; }
|
||||
} @else if $breakpoint == '2xl' {
|
||||
@media (min-width: $breakpoint-2xl) { @content; }
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 11. Dark Mode Support
|
||||
// ============================================
|
||||
|
||||
@mixin dark-mode {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@content;
|
||||
}
|
||||
|
||||
.dark & {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 12. Utility Classes
|
||||
// ============================================
|
||||
|
||||
.neumorphic-raised {
|
||||
@include neumorphic('md', 'raised');
|
||||
}
|
||||
|
||||
.neumorphic-pressed {
|
||||
@include neumorphic('md', 'pressed');
|
||||
}
|
||||
|
||||
.neumorphic-card {
|
||||
background: $neutral-100;
|
||||
border-radius: $radius-xl;
|
||||
padding: $card-padding;
|
||||
@include neumorphic('md', 'raised');
|
||||
}
|
||||
|
||||
.neumorphic-button {
|
||||
background: $neutral-100;
|
||||
border-radius: $radius-lg;
|
||||
padding: $space-3 $space-6;
|
||||
@include neumorphic('sm', 'raised');
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
@include neumorphic('md', 'raised');
|
||||
}
|
||||
|
||||
&:active {
|
||||
@include neumorphic('sm', 'pressed');
|
||||
}
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
background: linear-gradient(135deg, $primary-600, $primary-800);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
103
nuxt.config.ts
|
|
@ -162,8 +162,8 @@ export default defineNuxtConfig({
|
|||
monacousa: {
|
||||
dark: false,
|
||||
colors: {
|
||||
// Monaco Red Spectrum (from design-system.md)
|
||||
primary: "#dc2626", // monaco-red-600
|
||||
// Refined Monaco Red Spectrum
|
||||
primary: "#dc2626", // Professional primary
|
||||
'primary-50': "#fef2f2",
|
||||
'primary-100': "#fee2e2",
|
||||
'primary-200': "#fecaca",
|
||||
|
|
@ -175,40 +175,41 @@ export default defineNuxtConfig({
|
|||
'primary-800': "#991b1b",
|
||||
'primary-900': "#7f1d1d",
|
||||
|
||||
// Neutral Palette
|
||||
secondary: "#ffffff",
|
||||
accent: "#f4f4f5",
|
||||
background: "#ffffff",
|
||||
surface: "#fafafa",
|
||||
'on-background': "#18181b",
|
||||
'on-surface': "#18181b",
|
||||
// Improved Neutral Palette
|
||||
secondary: "#64748b", // Neutral gray for secondary
|
||||
accent: "#dc2626", // Monaco red as accent
|
||||
background: "#fafafa", // Light gray background
|
||||
surface: "#ffffff", // Pure white surfaces
|
||||
'on-background': "#1f2937", // Darker text on background
|
||||
'on-surface': "#1f2937", // Darker text on surface
|
||||
|
||||
// Semantic Colors
|
||||
error: "#ef4444",
|
||||
// Semantic Colors - More Professional
|
||||
error: "#dc2626",
|
||||
warning: "#f59e0b",
|
||||
info: "#0ea5e9",
|
||||
success: "#10b981",
|
||||
info: "#3b82f6",
|
||||
success: "#22c55e",
|
||||
|
||||
// Custom Properties for Glass Effects
|
||||
'glass-bg': "rgba(255, 255, 255, 0.7)",
|
||||
'glass-border': "rgba(255, 255, 255, 0.3)",
|
||||
'glass-dark': "rgba(0, 0, 0, 0.7)",
|
||||
'glass-bg': "rgba(255, 255, 255, 0.85)",
|
||||
'glass-border': "rgba(255, 255, 255, 0.18)",
|
||||
'glass-dark': "rgba(17, 24, 39, 0.6)",
|
||||
},
|
||||
variables: {
|
||||
'border-color': '#e4e4e7',
|
||||
'border-opacity': 0.12,
|
||||
'high-emphasis-opacity': 0.87,
|
||||
'medium-emphasis-opacity': 0.60,
|
||||
'disabled-opacity': 0.38,
|
||||
'idle-opacity': 0.04,
|
||||
'hover-opacity': 0.08,
|
||||
'focus-opacity': 0.12,
|
||||
'selected-opacity': 0.12,
|
||||
'activated-opacity': 0.12,
|
||||
'pressed-opacity': 0.16,
|
||||
'dragged-opacity': 0.08,
|
||||
'shadow-glass': '0 8px 32px rgba(0, 0, 0, 0.1)',
|
||||
'shadow-monaco': '0 10px 40px rgba(220, 38, 38, 0.15)',
|
||||
'border-color': '#e5e7eb',
|
||||
'border-opacity': 0.08,
|
||||
'high-emphasis-opacity': 0.95,
|
||||
'medium-emphasis-opacity': 0.70,
|
||||
'disabled-opacity': 0.45,
|
||||
'idle-opacity': 0.02,
|
||||
'hover-opacity': 0.04,
|
||||
'focus-opacity': 0.08,
|
||||
'selected-opacity': 0.08,
|
||||
'activated-opacity': 0.10,
|
||||
'pressed-opacity': 0.12,
|
||||
'dragged-opacity': 0.06,
|
||||
'shadow-glass': '0 8px 32px rgba(31, 41, 55, 0.08)',
|
||||
'shadow-monaco': '0 10px 40px rgba(185, 28, 28, 0.1)',
|
||||
'shadow-elevated': '0 20px 25px -5px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
},
|
||||
monacousa_dark: {
|
||||
|
|
@ -246,26 +247,62 @@ export default defineNuxtConfig({
|
|||
VCard: {
|
||||
elevation: 0,
|
||||
rounded: 'xl',
|
||||
class: 'card-modern',
|
||||
},
|
||||
VBtn: {
|
||||
elevation: 0,
|
||||
rounded: 'xl',
|
||||
rounded: 'lg',
|
||||
class: 'text-none font-medium',
|
||||
size: 'default',
|
||||
density: 'comfortable',
|
||||
},
|
||||
VNavigationDrawer: {
|
||||
elevation: 0,
|
||||
class: 'sidebar-modern',
|
||||
},
|
||||
VAppBar: {
|
||||
elevation: 0,
|
||||
flat: true,
|
||||
class: 'appbar-modern',
|
||||
density: 'comfortable',
|
||||
},
|
||||
VTextField: {
|
||||
variant: 'outlined',
|
||||
rounded: 'xl',
|
||||
rounded: 'lg',
|
||||
density: 'comfortable',
|
||||
class: 'input-modern',
|
||||
},
|
||||
VSelect: {
|
||||
variant: 'outlined',
|
||||
rounded: 'xl',
|
||||
rounded: 'lg',
|
||||
density: 'comfortable',
|
||||
class: 'select-modern',
|
||||
},
|
||||
VDataTable: {
|
||||
class: 'table-modern',
|
||||
fixedHeader: true,
|
||||
hover: true,
|
||||
},
|
||||
VChip: {
|
||||
rounded: 'lg',
|
||||
size: 'default',
|
||||
class: 'chip-modern',
|
||||
},
|
||||
VDialog: {
|
||||
class: 'dialog-modern',
|
||||
maxWidth: '600',
|
||||
},
|
||||
VAlert: {
|
||||
rounded: 'lg',
|
||||
variant: 'tonal',
|
||||
class: 'alert-modern',
|
||||
},
|
||||
VProgressLinear: {
|
||||
rounded: true,
|
||||
height: '6',
|
||||
},
|
||||
VProgressCircular: {
|
||||
width: '3',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
<template>
|
||||
<div class="design-test-page">
|
||||
<div class="container">
|
||||
<h1 class="page-title">New Design System Test Pages</h1>
|
||||
<p class="page-subtitle">Testing implementation of the new Neumorphic design with morphing dropdowns</p>
|
||||
|
||||
<div class="navigation-grid">
|
||||
<NuxtLink to="/admin/dashboard-v2" class="nav-card neumorphic-card">
|
||||
<Icon name="mdi:view-dashboard" class="nav-icon" />
|
||||
<h2>Admin Dashboard V2</h2>
|
||||
<p>New admin dashboard with neumorphic design and morphing dropdowns</p>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink to="/board/dashboard-v2" class="nav-card neumorphic-card">
|
||||
<Icon name="mdi:chart-box" class="nav-icon" />
|
||||
<h2>Board Dashboard V2</h2>
|
||||
<p>Executive dashboard with KPIs and strategic insights</p>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink to="/admin/dashboard" class="nav-card neumorphic-card">
|
||||
<Icon name="mdi:view-dashboard-outline" class="nav-icon" />
|
||||
<h2>Current Admin Dashboard</h2>
|
||||
<p>Compare with the existing admin dashboard</p>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink to="/board/dashboard" class="nav-card neumorphic-card">
|
||||
<Icon name="mdi:chart-box-outline" class="nav-icon" />
|
||||
<h2>Current Board Dashboard</h2>
|
||||
<p>Compare with the existing board dashboard</p>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="design-info">
|
||||
<h3>Design System Features</h3>
|
||||
<ul>
|
||||
<li>✨ Neumorphic cards and buttons with soft shadows</li>
|
||||
<li>💧 Glassmorphic blue morphing dropdowns</li>
|
||||
<li>🎯 Monaco red primary color maintained</li>
|
||||
<li>📱 Responsive grid layouts</li>
|
||||
<li>🎨 Professional and inviting aesthetic</li>
|
||||
<li>⚡ Smooth spring animations</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// No authentication required for testing
|
||||
definePageMeta({
|
||||
auth: false,
|
||||
layout: false
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// Import the new design system
|
||||
@import '@/design-mockups/styles/design-system.scss';
|
||||
|
||||
.design-test-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, $neutral-50 0%, $neutral-100 100%);
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
text-align: center;
|
||||
font-size: $text-4xl;
|
||||
font-weight: $font-bold;
|
||||
background: linear-gradient(135deg, $primary-600, $primary-800);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
text-align: center;
|
||||
color: $neutral-600;
|
||||
font-size: $text-lg;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.navigation-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.nav-card {
|
||||
@include neumorphic-card('md');
|
||||
padding: 2rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all $transition-base;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
@include neumorphic-card('lg');
|
||||
transform: translateY(-4px);
|
||||
|
||||
.nav-icon {
|
||||
transform: scale(1.1);
|
||||
color: $primary-600;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: $neutral-600;
|
||||
margin-bottom: 1rem;
|
||||
transition: all $transition-base;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: $text-xl;
|
||||
font-weight: $font-semibold;
|
||||
color: $neutral-800;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: $text-sm;
|
||||
color: $neutral-600;
|
||||
line-height: $leading-relaxed;
|
||||
}
|
||||
}
|
||||
|
||||
.design-info {
|
||||
@include neumorphic-card('lg');
|
||||
padding: 2rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
|
||||
h3 {
|
||||
font-size: $text-xl;
|
||||
font-weight: $font-semibold;
|
||||
color: $neutral-800;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
padding: 0.75rem 0;
|
||||
color: $neutral-700;
|
||||
font-size: $text-base;
|
||||
border-bottom: 1px solid rgba($neutral-200, 0.5);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Neumorphic Elements
|
||||
.neumorphic-card {
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
border-radius: $radius-xl;
|
||||
box-shadow: $shadow-soft-md;
|
||||
}
|
||||
</style>
|
||||