From 737cd5f07308604b3eb95cc10792f60bf2e554de Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Tue, 24 Feb 2026 11:47:27 +0100 Subject: [PATCH] feat(ui): improve dark mode surfaces and planning event actions --- apps/main/src/components/gantt/GanttChart.tsx | 6 +- apps/main/src/main.css | 1804 +++++++++-------- apps/main/src/pages/planning.tsx | 377 +++- 3 files changed, 1213 insertions(+), 974 deletions(-) diff --git a/apps/main/src/components/gantt/GanttChart.tsx b/apps/main/src/components/gantt/GanttChart.tsx index ea45c4d..ad24ac2 100644 --- a/apps/main/src/components/gantt/GanttChart.tsx +++ b/apps/main/src/components/gantt/GanttChart.tsx @@ -295,7 +295,7 @@ export function GanttChart({ > -
+
{formatDateRange(periodStart, periodEnd)} @@ -334,7 +334,7 @@ export function GanttChart({ {/* Gantt chart */}
@@ -441,7 +441,7 @@ export function GanttChart({ {/* Title */}

diff --git a/apps/main/src/main.css b/apps/main/src/main.css index c3b1f5d..3b3c095 100644 --- a/apps/main/src/main.css +++ b/apps/main/src/main.css @@ -4,1271 +4,1281 @@ @custom-variant dark (&:is(.dark *)); :root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --destructive-foreground: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); - --navbar-background: rgb(249, 250, 251); - --navbar-darker: #e5e7eb; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); + --navbar-background: rgb(249, 250, 251); + --navbar-darker: #e5e7eb; } .dark { - --background: oklch(0.14 0.012 290); - --foreground: oklch(0.985 0.005 290); - --card: oklch(0.16 0.014 290); - --card-foreground: oklch(0.985 0.005 290); - --popover: oklch(0.16 0.014 290); - --popover-foreground: oklch(0.985 0.005 290); - --primary: oklch(0.985 0.005 290); - --primary-foreground: oklch(0.2 0.012 290); - --secondary: oklch(0.22 0.018 290); - --secondary-foreground: oklch(0.985 0.005 290); - --muted: oklch(0.22 0.018 290); - --muted-foreground: oklch(0.65 0.02 290); - --accent: oklch(0.22 0.018 290); - --accent-foreground: oklch(0.985 0.005 290); - --destructive: oklch(0.396 0.141 25.723); - --destructive-foreground: oklch(0.637 0.237 25.331); - --border: oklch(0.26 0.02 290); - --input: oklch(0.26 0.02 290); - --ring: oklch(0.45 0.03 290); - --chart-1: oklch(0.55 0.2 290); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.18 0.016 290); - --sidebar-foreground: oklch(0.985 0.005 290); - --sidebar-primary: oklch(0.55 0.2 290); - --sidebar-primary-foreground: oklch(0.985 0.005 290); - --sidebar-accent: oklch(0.24 0.02 290); - --sidebar-accent-foreground: oklch(0.985 0.005 290); - --sidebar-border: oklch(0.26 0.02 290); - --sidebar-ring: oklch(0.45 0.03 290); - --navbar-background: #1e1b2e; - --navbar-darker: #141121; + --background: oklch(0.17 0.012 290); + --foreground: oklch(0.985 0.005 290); + --card: oklch(0.16 0.014 290); + --card-foreground: oklch(0.985 0.005 290); + --popover: oklch(0.16 0.014 290); + --popover-foreground: oklch(0.985 0.005 290); + --primary: oklch(0.985 0.005 290); + --primary-foreground: oklch(0.2 0.012 290); + --secondary: oklch(0.22 0.018 290); + --secondary-foreground: oklch(0.985 0.005 290); + --muted: oklch(0.22 0.018 290); + --muted-foreground: oklch(0.65 0.02 290); + --accent: oklch(0.22 0.018 290); + --accent-foreground: oklch(0.985 0.005 290); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.26 0.02 290); + --input: oklch(0.26 0.02 290); + --ring: oklch(0.45 0.03 290); + --chart-1: oklch(0.55 0.2 290); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.18 0.016 290); + --sidebar-foreground: oklch(0.985 0.005 290); + --sidebar-primary: oklch(0.55 0.2 290); + --sidebar-primary-foreground: oklch(0.985 0.005 290); + --sidebar-accent: oklch(0.24 0.02 290); + --sidebar-accent-foreground: oklch(0.985 0.005 290); + --sidebar-border: oklch(0.26 0.02 290); + --sidebar-ring: oklch(0.45 0.03 290); + --navbar-background: #1e1b2e; + --navbar-darker: #141121; } @theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); - --color-navbar-background: var(--navbar-background); - --color-navbar-darker: var(--navbar-darker); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + --color-navbar-background: var(--navbar-background); + --color-navbar-darker: var(--navbar-darker); } @layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } } .str-chat { - --str-chat__primary-color: #804EEC; - --str-chat__active-primary-color: #6f3fd4; - --str-chat__surface-color: #f5f3f7; - --str-chat__secondary-surface-color: #e8e4ec; - --str-chat__primary-surface-color: #F4F3FF; - --str-chat__primary-surface-color-low-emphasis: #f8f7ff; - --str-chat__border-radius-circle: 6px; - --str-chat__own-message-bubble-color: #804EEC; - --str-chat__own-message-text-color: #ffffff; + --str-chat__primary-color: #804eec; + --str-chat__active-primary-color: #6f3fd4; + --str-chat__surface-color: #f5f3f7; + --str-chat__secondary-surface-color: #e8e4ec; + --str-chat__primary-surface-color: #f4f3ff; + --str-chat__primary-surface-color-low-emphasis: #f8f7ff; + --str-chat__border-radius-circle: 6px; + --str-chat__own-message-bubble-color: #804eec; + --str-chat__own-message-text-color: #ffffff; } .dark .str-chat { - --str-chat__primary-color: #9b6ff0; - --str-chat__active-primary-color: #804EEC; - --str-chat__surface-color: rgba(128, 78, 236, 0.12); - --str-chat__secondary-surface-color: rgba(128, 78, 236, 0.08); - --str-chat__primary-surface-color: rgba(128, 78, 236, 0.1); - --str-chat__primary-surface-color-low-emphasis: rgba(128, 78, 236, 0.05); - --str-chat__background-color: rgba(30, 27, 46, 0.6); - --str-chat__secondary-background-color: rgba(20, 17, 33, 0.5); - --str-chat__border-color: rgba(128, 78, 236, 0.15); - --str-chat__text-color: #eeeaf5; - --str-chat__text-low-emphasis-color: #a8a0b8; - --str-chat__disabled-color: rgba(128, 78, 236, 0.2); - --str-chat__own-message-bubble-color: #804EEC; - --str-chat__own-message-text-color: #ffffff; + --str-chat__primary-color: #9b6ff0; + --str-chat__active-primary-color: #804eec; + --str-chat__surface-color: rgba(128, 78, 236, 0.12); + --str-chat__secondary-surface-color: rgba(128, 78, 236, 0.08); + --str-chat__primary-surface-color: rgba(128, 78, 236, 0.1); + --str-chat__primary-surface-color-low-emphasis: rgba(128, 78, 236, 0.05); + --str-chat__background-color: rgba(30, 27, 46, 0.6); + --str-chat__secondary-background-color: rgba(20, 17, 33, 0.5); + --str-chat__border-color: rgba(128, 78, 236, 0.15); + --str-chat__text-color: #eeeaf5; + --str-chat__text-low-emphasis-color: #a8a0b8; + --str-chat__disabled-color: rgba(128, 78, 236, 0.2); + --str-chat__own-message-bubble-color: #804eec; + --str-chat__own-message-text-color: #ffffff; } @keyframes gradient-x { - 0%, - 100% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } + 0%, + 100% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } } .animate-gradient-x { - animation: gradient-x 15s ease infinite; + animation: gradient-x 15s ease infinite; } @keyframes wave-float { - 0%, - 100% { - transform: translateY(0px) rotate(0deg); - } - 25% { - transform: translateY(-20px) rotate(1deg); - } - 50% { - transform: translateY(-10px) rotate(-1deg); - } - 75% { - transform: translateY(-15px) rotate(0.5deg); - } + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + } + 25% { + transform: translateY(-20px) rotate(1deg); + } + 50% { + transform: translateY(-10px) rotate(-1deg); + } + 75% { + transform: translateY(-15px) rotate(0.5deg); + } } @keyframes wave-pulse { - 0%, - 100% { - transform: scale(1) rotateZ(0deg); - opacity: 0.3; - } - 25% { - transform: scale(1.05) rotateZ(1deg); - opacity: 0.4; - } - 50% { - transform: scale(0.95) rotateZ(-1deg); - opacity: 0.5; - } - 75% { - transform: scale(1.02) rotateZ(0.5deg); - opacity: 0.35; - } + 0%, + 100% { + transform: scale(1) rotateZ(0deg); + opacity: 0.3; + } + 25% { + transform: scale(1.05) rotateZ(1deg); + opacity: 0.4; + } + 50% { + transform: scale(0.95) rotateZ(-1deg); + opacity: 0.5; + } + 75% { + transform: scale(1.02) rotateZ(0.5deg); + opacity: 0.35; + } } .animate-wave-float { - animation: wave-float 8s ease-in-out infinite; + animation: wave-float 8s ease-in-out infinite; } .animate-wave-float-delayed { - animation: wave-float 10s ease-in-out infinite 2s; + animation: wave-float 10s ease-in-out infinite 2s; } .animate-wave-float-slow { - animation: wave-float 12s ease-in-out infinite 4s; + animation: wave-float 12s ease-in-out infinite 4s; } .animate-wave-pulse { - animation: wave-pulse 6s ease-in-out infinite; + animation: wave-pulse 6s ease-in-out infinite; } .animate-wave-pulse-delayed { - animation: wave-pulse 8s ease-in-out infinite 3s; + animation: wave-pulse 8s ease-in-out infinite 3s; } .animate-wave-pulse-slow { - animation: wave-pulse 10s ease-in-out infinite 1s; + animation: wave-pulse 10s ease-in-out infinite 1s; } /* Moving Animations */ @keyframes move-right-slow { - 0% { - transform: translateX(-100px); - } - 100% { - transform: translateX(calc(100vw + 100px)); - } + 0% { + transform: translateX(-100px); + } + 100% { + transform: translateX(calc(100vw + 100px)); + } } @keyframes move-right-medium { - 0% { - transform: translateX(-80px); - } - 100% { - transform: translateX(calc(100vw + 80px)); - } + 0% { + transform: translateX(-80px); + } + 100% { + transform: translateX(calc(100vw + 80px)); + } } @keyframes move-right-fast { - 0% { - transform: translateX(-120px); - } - 100% { - transform: translateX(calc(100vw + 120px)); - } + 0% { + transform: translateX(-120px); + } + 100% { + transform: translateX(calc(100vw + 120px)); + } } @keyframes move-down-slow { - 0% { - transform: translateY(-100px); - } - 100% { - transform: translateY(calc(100vh + 100px)); - } + 0% { + transform: translateY(-100px); + } + 100% { + transform: translateY(calc(100vh + 100px)); + } } @keyframes move-down-medium { - 0% { - transform: translateY(-80px); - } - 100% { - transform: translateY(calc(100vh + 80px)); - } + 0% { + transform: translateY(-80px); + } + 100% { + transform: translateY(calc(100vh + 80px)); + } } @keyframes move-diagonal-1 { - 0% { - transform: translate(-100px, -100px); - } - 100% { - transform: translate(calc(100vw + 100px), calc(100vh + 100px)); - } + 0% { + transform: translate(-100px, -100px); + } + 100% { + transform: translate(calc(100vw + 100px), calc(100vh + 100px)); + } } @keyframes move-diagonal-2 { - 0% { - transform: translate(-80px, -50px); - } - 100% { - transform: translate(calc(100vw + 80px), calc(100vh + 50px)); - } + 0% { + transform: translate(-80px, -50px); + } + 100% { + transform: translate(calc(100vw + 80px), calc(100vh + 50px)); + } } @keyframes move-diagonal-3 { - 0% { - transform: translate(-60px, -80px); - } - 100% { - transform: translate(calc(100vw + 60px), calc(100vh + 80px)); - } + 0% { + transform: translate(-60px, -80px); + } + 100% { + transform: translate(calc(100vw + 60px), calc(100vh + 80px)); + } } @keyframes orbit-1 { - 0% { - transform: translate(-50%, -50%) rotate(0deg) translateX(150px) rotate(0deg); - } - 100% { - transform: translate(-50%, -50%) rotate(360deg) translateX(150px) rotate(-360deg); - } + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(150px) + rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(360deg) translateX(150px) + rotate(-360deg); + } } @keyframes orbit-2 { - 0% { - transform: translate(-50%, -50%) rotate(0deg) translateX(200px) rotate(0deg); - } - 100% { - transform: translate(-50%, -50%) rotate(-360deg) translateX(200px) rotate(360deg); - } + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(200px) + rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(-360deg) translateX(200px) + rotate(360deg); + } } @keyframes orbit-3 { - 0% { - transform: translate(-50%, -50%) rotate(0deg) translateX(100px) rotate(0deg); - } - 100% { - transform: translate(-50%, -50%) rotate(360deg) translateX(100px) rotate(-360deg); - } + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(100px) + rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(360deg) translateX(100px) + rotate(-360deg); + } } /* Gentle Animations */ @keyframes spin-slow { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } @keyframes spin-reverse { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(-360deg); - } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-360deg); + } } @keyframes bounce-gentle { - 0%, - 100% { - transform: translateY(0px); - } - 50% { - transform: translateY(-10px); - } + 0%, + 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-10px); + } } @keyframes bounce-soft { - 0%, - 100% { - transform: translateY(0px); - } - 50% { - transform: translateY(-8px); - } + 0%, + 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-8px); + } } @keyframes pulse-gentle { - 0%, - 100% { - transform: scale(1); - opacity: 0.4; - } - 50% { - transform: scale(1.1); - opacity: 0.6; - } + 0%, + 100% { + transform: scale(1); + opacity: 0.4; + } + 50% { + transform: scale(1.1); + opacity: 0.6; + } } @keyframes wiggle { - 0%, - 100% { - transform: rotate(0deg); - } - 25% { - transform: rotate(3deg); - } - 75% { - transform: rotate(-3deg); - } + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(3deg); + } + 75% { + transform: rotate(-3deg); + } } @keyframes float-gentle { - 0%, - 100% { - transform: translateY(0px); - } - 50% { - transform: translateY(-5px); - } + 0%, + 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-5px); + } } @keyframes scale-gentle { - 0%, - 100% { - transform: scale(1); - } - 50% { - transform: scale(1.05); - } + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } } @keyframes rotate-gentle { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(180deg); - } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(180deg); + } } @keyframes sway { - 0%, - 100% { - transform: translateX(0px); - } - 50% { - transform: translateX(10px); - } + 0%, + 100% { + transform: translateX(0px); + } + 50% { + transform: translateX(10px); + } } /* Animation Classes */ .animate-move-right-slow { - animation: move-right-slow 25s linear infinite; + animation: move-right-slow 25s linear infinite; } .animate-move-right-medium { - animation: move-right-medium 20s linear infinite; + animation: move-right-medium 20s linear infinite; } .animate-move-right-fast { - animation: move-right-fast 15s linear infinite; + animation: move-right-fast 15s linear infinite; } .animate-move-down-slow { - animation: move-down-slow 30s linear infinite; + animation: move-down-slow 30s linear infinite; } .animate-move-down-medium { - animation: move-down-medium 25s linear infinite; + animation: move-down-medium 25s linear infinite; } .animate-move-diagonal-1 { - animation: move-diagonal-1 35s linear infinite; + animation: move-diagonal-1 35s linear infinite; } .animate-move-diagonal-2 { - animation: move-diagonal-2 28s linear infinite; + animation: move-diagonal-2 28s linear infinite; } .animate-move-diagonal-3 { - animation: move-diagonal-3 32s linear infinite; + animation: move-diagonal-3 32s linear infinite; } .animate-orbit-1 { - animation: orbit-1 20s linear infinite; + animation: orbit-1 20s linear infinite; } .animate-orbit-2 { - animation: orbit-2 25s linear infinite reverse; + animation: orbit-2 25s linear infinite reverse; } .animate-orbit-3 { - animation: orbit-3 15s linear infinite; + animation: orbit-3 15s linear infinite; } .animate-spin-slow { - animation: spin-slow 8s linear infinite; + animation: spin-slow 8s linear infinite; } .animate-spin-reverse { - animation: spin-reverse 6s linear infinite; + animation: spin-reverse 6s linear infinite; } .animate-bounce-gentle { - animation: bounce-gentle 3s ease-in-out infinite; + animation: bounce-gentle 3s ease-in-out infinite; } .animate-bounce-soft { - animation: bounce-soft 4s ease-in-out infinite; + animation: bounce-soft 4s ease-in-out infinite; } .animate-pulse-gentle { - animation: pulse-gentle 4s ease-in-out infinite; + animation: pulse-gentle 4s ease-in-out infinite; } .animate-wiggle { - animation: wiggle 2s ease-in-out infinite; + animation: wiggle 2s ease-in-out infinite; } .animate-float-gentle { - animation: float-gentle 5s ease-in-out infinite; + animation: float-gentle 5s ease-in-out infinite; } .animate-scale-gentle { - animation: scale-gentle 6s ease-in-out infinite; + animation: scale-gentle 6s ease-in-out infinite; } .animate-rotate-gentle { - animation: rotate-gentle 8s ease-in-out infinite; + animation: rotate-gentle 8s ease-in-out infinite; } .animate-sway { - animation: sway 3s ease-in-out infinite; + animation: sway 3s ease-in-out infinite; } /* Enhanced Animations */ @keyframes orbit-4 { - 0% { - transform: translate(-50%, -50%) rotate(0deg) translateX(250px) rotate(0deg); - } - 100% { - transform: translate(-50%, -50%) rotate(360deg) translateX(250px) rotate(-360deg); - } + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(250px) + rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(360deg) translateX(250px) + rotate(-360deg); + } } @keyframes orbit-5 { - 0% { - transform: translate(-50%, -50%) rotate(0deg) translateX(120px) rotate(0deg); - } - 100% { - transform: translate(-50%, -50%) rotate(-360deg) translateX(120px) rotate(360deg); - } + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(120px) + rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(-360deg) translateX(120px) + rotate(360deg); + } } @keyframes zigzag-1 { - 0% { - transform: translateX(-100px) translateY(0px); - } - 25% { - transform: translateX(25vw) translateY(-50px); - } - 50% { - transform: translateX(50vw) translateY(50px); - } - 75% { - transform: translateX(75vw) translateY(-30px); - } - 100% { - transform: translateX(calc(100vw + 100px)) translateY(20px); - } + 0% { + transform: translateX(-100px) translateY(0px); + } + 25% { + transform: translateX(25vw) translateY(-50px); + } + 50% { + transform: translateX(50vw) translateY(50px); + } + 75% { + transform: translateX(75vw) translateY(-30px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(20px); + } } @keyframes zigzag-2 { - 0% { - transform: translateX(-80px) translateY(0px); - } - 20% { - transform: translateX(20vw) translateY(40px); - } - 40% { - transform: translateX(40vw) translateY(-60px); - } - 60% { - transform: translateX(60vw) translateY(30px); - } - 80% { - transform: translateX(80vw) translateY(-40px); - } - 100% { - transform: translateX(calc(100vw + 80px)) translateY(0px); - } + 0% { + transform: translateX(-80px) translateY(0px); + } + 20% { + transform: translateX(20vw) translateY(40px); + } + 40% { + transform: translateX(40vw) translateY(-60px); + } + 60% { + transform: translateX(60vw) translateY(30px); + } + 80% { + transform: translateX(80vw) translateY(-40px); + } + 100% { + transform: translateX(calc(100vw + 80px)) translateY(0px); + } } @keyframes zigzag-3 { - 0% { - transform: translateX(-120px) translateY(0px); - } - 16% { - transform: translateX(16vw) translateY(-70px); - } - 33% { - transform: translateX(33vw) translateY(80px); - } - 50% { - transform: translateX(50vw) translateY(-50px); - } - 66% { - transform: translateX(66vw) translateY(60px); - } - 83% { - transform: translateX(83vw) translateY(-40px); - } - 100% { - transform: translateX(calc(100vw + 120px)) translateY(0px); - } + 0% { + transform: translateX(-120px) translateY(0px); + } + 16% { + transform: translateX(16vw) translateY(-70px); + } + 33% { + transform: translateX(33vw) translateY(80px); + } + 50% { + transform: translateX(50vw) translateY(-50px); + } + 66% { + transform: translateX(66vw) translateY(60px); + } + 83% { + transform: translateX(83vw) translateY(-40px); + } + 100% { + transform: translateX(calc(100vw + 120px)) translateY(0px); + } } @keyframes spiral-1 { - 0% { - transform: translate(0px, 0px) rotate(0deg) scale(0.5); - } - 25% { - transform: translate(25vw, 25vh) rotate(90deg) scale(1); - } - 50% { - transform: translate(50vw, 50vh) rotate(180deg) scale(1.5); - } - 75% { - transform: translate(75vw, 75vh) rotate(270deg) scale(1); - } - 100% { - transform: translate(100vw, 100vh) rotate(360deg) scale(0.5); - } + 0% { + transform: translate(0px, 0px) rotate(0deg) scale(0.5); + } + 25% { + transform: translate(25vw, 25vh) rotate(90deg) scale(1); + } + 50% { + transform: translate(50vw, 50vh) rotate(180deg) scale(1.5); + } + 75% { + transform: translate(75vw, 75vh) rotate(270deg) scale(1); + } + 100% { + transform: translate(100vw, 100vh) rotate(360deg) scale(0.5); + } } @keyframes spiral-2 { - 0% { - transform: translate(0px, 0px) rotate(0deg) scale(1.5); - } - 25% { - transform: translate(-25vw, 25vh) rotate(-90deg) scale(0.8); - } - 50% { - transform: translate(-50vw, 50vh) rotate(-180deg) scale(0.5); - } - 75% { - transform: translate(-75vw, 75vh) rotate(-270deg) scale(1.2); - } - 100% { - transform: translate(-100vw, 100vh) rotate(-360deg) scale(1.5); - } + 0% { + transform: translate(0px, 0px) rotate(0deg) scale(1.5); + } + 25% { + transform: translate(-25vw, 25vh) rotate(-90deg) scale(0.8); + } + 50% { + transform: translate(-50vw, 50vh) rotate(-180deg) scale(0.5); + } + 75% { + transform: translate(-75vw, 75vh) rotate(-270deg) scale(1.2); + } + 100% { + transform: translate(-100vw, 100vh) rotate(-360deg) scale(1.5); + } } @keyframes float-random-1 { - 0%, - 100% { - transform: translate(0px, 0px) rotate(0deg); - } - 25% { - transform: translate(50px, -30px) rotate(45deg); - } - 50% { - transform: translate(-30px, 40px) rotate(-30deg); - } - 75% { - transform: translate(40px, 20px) rotate(60deg); - } + 0%, + 100% { + transform: translate(0px, 0px) rotate(0deg); + } + 25% { + transform: translate(50px, -30px) rotate(45deg); + } + 50% { + transform: translate(-30px, 40px) rotate(-30deg); + } + 75% { + transform: translate(40px, 20px) rotate(60deg); + } } @keyframes float-random-2 { - 0%, - 100% { - transform: translate(0px, 0px) rotate(0deg); - } - 20% { - transform: translate(-40px, -50px) rotate(-45deg); - } - 40% { - transform: translate(60px, -20px) rotate(90deg); - } - 60% { - transform: translate(-20px, 60px) rotate(-60deg); - } - 80% { - transform: translate(30px, -40px) rotate(120deg); - } + 0%, + 100% { + transform: translate(0px, 0px) rotate(0deg); + } + 20% { + transform: translate(-40px, -50px) rotate(-45deg); + } + 40% { + transform: translate(60px, -20px) rotate(90deg); + } + 60% { + transform: translate(-20px, 60px) rotate(-60deg); + } + 80% { + transform: translate(30px, -40px) rotate(120deg); + } } @keyframes float-random-3 { - 0%, - 100% { - transform: translate(0px, 0px) rotate(0deg); - } - 33% { - transform: translate(70px, 30px) rotate(180deg); - } - 66% { - transform: translate(-50px, -40px) rotate(-90deg); - } + 0%, + 100% { + transform: translate(0px, 0px) rotate(0deg); + } + 33% { + transform: translate(70px, 30px) rotate(180deg); + } + 66% { + transform: translate(-50px, -40px) rotate(-90deg); + } } @keyframes float-random-4 { - 0%, - 100% { - transform: translate(0px, 0px) rotate(0deg); - } - 25% { - transform: translate(-60px, 50px) rotate(270deg); - } - 50% { - transform: translate(80px, -30px) rotate(180deg); - } - 75% { - transform: translate(-40px, -60px) rotate(90deg); - } + 0%, + 100% { + transform: translate(0px, 0px) rotate(0deg); + } + 25% { + transform: translate(-60px, 50px) rotate(270deg); + } + 50% { + transform: translate(80px, -30px) rotate(180deg); + } + 75% { + transform: translate(-40px, -60px) rotate(90deg); + } } @keyframes wave-1 { - 0% { - transform: translateX(-100px) translateY(0px); - } - 25% { - transform: translateX(25vw) translateY(-80px); - } - 50% { - transform: translateX(50vw) translateY(0px); - } - 75% { - transform: translateX(75vw) translateY(80px); - } - 100% { - transform: translateX(calc(100vw + 100px)) translateY(0px); - } + 0% { + transform: translateX(-100px) translateY(0px); + } + 25% { + transform: translateX(25vw) translateY(-80px); + } + 50% { + transform: translateX(50vw) translateY(0px); + } + 75% { + transform: translateX(75vw) translateY(80px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(0px); + } } @keyframes wave-2 { - 0% { - transform: translateX(-100px) translateY(0px); - } - 20% { - transform: translateX(20vw) translateY(60px); - } - 40% { - transform: translateX(40vw) translateY(-60px); - } - 60% { - transform: translateX(60vw) translateY(60px); - } - 80% { - transform: translateX(80vw) translateY(-60px); - } - 100% { - transform: translateX(calc(100vw + 100px)) translateY(0px); - } + 0% { + transform: translateX(-100px) translateY(0px); + } + 20% { + transform: translateX(20vw) translateY(60px); + } + 40% { + transform: translateX(40vw) translateY(-60px); + } + 60% { + transform: translateX(60vw) translateY(60px); + } + 80% { + transform: translateX(80vw) translateY(-60px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(0px); + } } @keyframes wave-3 { - 0% { - transform: translateX(-100px) translateY(0px); - } - 33% { - transform: translateX(33vw) translateY(-100px); - } - 66% { - transform: translateX(66vw) translateY(100px); - } - 100% { - transform: translateX(calc(100vw + 100px)) translateY(0px); - } + 0% { + transform: translateX(-100px) translateY(0px); + } + 33% { + transform: translateX(33vw) translateY(-100px); + } + 66% { + transform: translateX(66vw) translateY(100px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(0px); + } } @keyframes wave-4 { - 0% { - transform: translateX(-100px) translateY(0px); - } - 16% { - transform: translateX(16vw) translateY(40px); - } - 33% { - transform: translateX(33vw) translateY(-80px); - } - 50% { - transform: translateX(50vw) translateY(40px); - } - 66% { - transform: translateX(66vw) translateY(-80px); - } - 83% { - transform: translateX(83vw) translateY(40px); - } - 100% { - transform: translateX(calc(100vw + 100px)) translateY(0px); - } + 0% { + transform: translateX(-100px) translateY(0px); + } + 16% { + transform: translateX(16vw) translateY(40px); + } + 33% { + transform: translateX(33vw) translateY(-80px); + } + 50% { + transform: translateX(50vw) translateY(40px); + } + 66% { + transform: translateX(66vw) translateY(-80px); + } + 83% { + transform: translateX(83vw) translateY(40px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(0px); + } } @keyframes corner-shoot-1 { - 0% { - transform: translate(0px, 0px) rotate(0deg); - } - 100% { - transform: translate(100vw, 100vh) rotate(720deg); - } + 0% { + transform: translate(0px, 0px) rotate(0deg); + } + 100% { + transform: translate(100vw, 100vh) rotate(720deg); + } } @keyframes corner-shoot-2 { - 0% { - transform: translate(0px, 0px) rotate(0deg); - } - 100% { - transform: translate(-100vw, 100vh) rotate(-720deg); - } + 0% { + transform: translate(0px, 0px) rotate(0deg); + } + 100% { + transform: translate(-100vw, 100vh) rotate(-720deg); + } } @keyframes corner-shoot-3 { - 0% { - transform: translate(0px, 0px) rotate(0deg); - } - 100% { - transform: translate(100vw, -100vh) rotate(720deg); - } + 0% { + transform: translate(0px, 0px) rotate(0deg); + } + 100% { + transform: translate(100vw, -100vh) rotate(720deg); + } } @keyframes corner-shoot-4 { - 0% { - transform: translate(0px, 0px) rotate(0deg); - } - 100% { - transform: translate(-100vw, -100vh) rotate(-720deg); - } + 0% { + transform: translate(0px, 0px) rotate(0deg); + } + 100% { + transform: translate(-100vw, -100vh) rotate(-720deg); + } } @keyframes bounce-ball-1 { - 0%, - 100% { - transform: translate(0px, 0px); - } - 25% { - transform: translate(200px, -150px); - } - 50% { - transform: translate(400px, 0px); - } - 75% { - transform: translate(600px, -100px); - } + 0%, + 100% { + transform: translate(0px, 0px); + } + 25% { + transform: translate(200px, -150px); + } + 50% { + transform: translate(400px, 0px); + } + 75% { + transform: translate(600px, -100px); + } } @keyframes bounce-ball-2 { - 0%, - 100% { - transform: translate(0px, 0px); - } - 33% { - transform: translate(-300px, -200px); - } - 66% { - transform: translate(-600px, 0px); - } + 0%, + 100% { + transform: translate(0px, 0px); + } + 33% { + transform: translate(-300px, -200px); + } + 66% { + transform: translate(-600px, 0px); + } } @keyframes bounce-ball-3 { - 0%, - 100% { - transform: translate(0px, 0px); - } - 20% { - transform: translate(150px, -100px); - } - 40% { - transform: translate(300px, 50px); - } - 60% { - transform: translate(150px, -80px); - } - 80% { - transform: translate(-150px, 30px); - } + 0%, + 100% { + transform: translate(0px, 0px); + } + 20% { + transform: translate(150px, -100px); + } + 40% { + transform: translate(300px, 50px); + } + 60% { + transform: translate(150px, -80px); + } + 80% { + transform: translate(-150px, 30px); + } } /* Crazy Animations */ @keyframes spin-fast { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(720deg); - } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(720deg); + } } @keyframes pulse-fast { - 0%, - 100% { - transform: scale(0.8); - opacity: 0.3; - } - 50% { - transform: scale(1.3); - opacity: 0.8; - } + 0%, + 100% { + transform: scale(0.8); + opacity: 0.3; + } + 50% { + transform: scale(1.3); + opacity: 0.8; + } } @keyframes wobble { - 0%, - 100% { - transform: rotate(0deg) scale(1); - } - 25% { - transform: rotate(5deg) scale(1.1); - } - 50% { - transform: rotate(-5deg) scale(0.9); - } - 75% { - transform: rotate(3deg) scale(1.05); - } + 0%, + 100% { + transform: rotate(0deg) scale(1); + } + 25% { + transform: rotate(5deg) scale(1.1); + } + 50% { + transform: rotate(-5deg) scale(0.9); + } + 75% { + transform: rotate(3deg) scale(1.05); + } } @keyframes shake { - 0%, - 100% { - transform: translateX(0px); - } - 25% { - transform: translateX(-10px); - } - 75% { - transform: translateX(10px); - } + 0%, + 100% { + transform: translateX(0px); + } + 25% { + transform: translateX(-10px); + } + 75% { + transform: translateX(10px); + } } @keyframes bounce-crazy { - 0%, - 100% { - transform: translateY(0px) scale(1); - } - 50% { - transform: translateY(-50px) scale(1.2); - } + 0%, + 100% { + transform: translateY(0px) scale(1); + } + 50% { + transform: translateY(-50px) scale(1.2); + } } @keyframes spin-wobble { - 0% { - transform: rotate(0deg) scale(1); - } - 25% { - transform: rotate(90deg) scale(1.1); - } - 50% { - transform: rotate(180deg) scale(0.9); - } - 75% { - transform: rotate(270deg) scale(1.05); - } - 100% { - transform: rotate(360deg) scale(1); - } + 0% { + transform: rotate(0deg) scale(1); + } + 25% { + transform: rotate(90deg) scale(1.1); + } + 50% { + transform: rotate(180deg) scale(0.9); + } + 75% { + transform: rotate(270deg) scale(1.05); + } + 100% { + transform: rotate(360deg) scale(1); + } } @keyframes flip { - 0% { - transform: rotateY(0deg); - } - 50% { - transform: rotateY(180deg); - } - 100% { - transform: rotateY(360deg); - } + 0% { + transform: rotateY(0deg); + } + 50% { + transform: rotateY(180deg); + } + 100% { + transform: rotateY(360deg); + } } @keyframes twirl { - 0% { - transform: rotate(0deg) translateX(0px); - } - 25% { - transform: rotate(90deg) translateX(20px); - } - 50% { - transform: rotate(180deg) translateX(0px); - } - 75% { - transform: rotate(270deg) translateX(-20px); - } - 100% { - transform: rotate(360deg) translateX(0px); - } + 0% { + transform: rotate(0deg) translateX(0px); + } + 25% { + transform: rotate(90deg) translateX(20px); + } + 50% { + transform: rotate(180deg) translateX(0px); + } + 75% { + transform: rotate(270deg) translateX(-20px); + } + 100% { + transform: rotate(360deg) translateX(0px); + } } @keyframes dance { - 0%, - 100% { - transform: translateY(0px) rotate(0deg); - } - 25% { - transform: translateY(-20px) rotate(10deg); - } - 50% { - transform: translateY(10px) rotate(-5deg); - } - 75% { - transform: translateY(-15px) rotate(8deg); - } + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + } + 25% { + transform: translateY(-20px) rotate(10deg); + } + 50% { + transform: translateY(10px) rotate(-5deg); + } + 75% { + transform: translateY(-15px) rotate(8deg); + } } @keyframes jiggle { - 0%, - 100% { - transform: rotate(0deg); - } - 25% { - transform: rotate(2deg) translateX(2px); - } - 50% { - transform: rotate(-2deg) translateX(-2px); - } - 75% { - transform: rotate(1deg) translateX(1px); - } + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(2deg) translateX(2px); + } + 50% { + transform: rotate(-2deg) translateX(-2px); + } + 75% { + transform: rotate(1deg) translateX(1px); + } } @keyframes vibrate { - 0%, - 100% { - transform: translate(0px, 0px); - } - 25% { - transform: translate(2px, -2px); - } - 50% { - transform: translate(-2px, 2px); - } - 75% { - transform: translate(2px, 2px); - } + 0%, + 100% { + transform: translate(0px, 0px); + } + 25% { + transform: translate(2px, -2px); + } + 50% { + transform: translate(-2px, 2px); + } + 75% { + transform: translate(2px, 2px); + } } @keyframes swing { - 0%, - 100% { - transform: rotate(0deg); - } - 25% { - transform: rotate(15deg); - } - 75% { - transform: rotate(-15deg); - } + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(15deg); + } + 75% { + transform: rotate(-15deg); + } } @keyframes pendulum { - 0%, - 100% { - transform: rotate(0deg); - } - 50% { - transform: rotate(30deg); - } + 0%, + 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(30deg); + } } @keyframes elastic { - 0%, - 100% { - transform: scale(1); - } - 50% { - transform: scale(1.3) rotate(180deg); - } + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.3) rotate(180deg); + } } @keyframes rubber { - 0%, - 100% { - transform: scaleX(1) scaleY(1); - } - 25% { - transform: scaleX(1.2) scaleY(0.8); - } - 75% { - transform: scaleX(0.8) scaleY(1.2); - } + 0%, + 100% { + transform: scaleX(1) scaleY(1); + } + 25% { + transform: scaleX(1.2) scaleY(0.8); + } + 75% { + transform: scaleX(0.8) scaleY(1.2); + } } @keyframes rocket { - 0% { - transform: scale(0.5) rotate(0deg); - } - 100% { - transform: scale(2) rotate(360deg); - } + 0% { + transform: scale(0.5) rotate(0deg); + } + 100% { + transform: scale(2) rotate(360deg); + } } @keyframes comet { - 0% { - transform: scale(1) rotate(0deg); - opacity: 1; - } - 100% { - transform: scale(0.2) rotate(720deg); - opacity: 0.2; - } + 0% { + transform: scale(1) rotate(0deg); + opacity: 1; + } + 100% { + transform: scale(0.2) rotate(720deg); + opacity: 0.2; + } } @keyframes meteor { - 0% { - transform: scale(0.2) rotate(0deg); - opacity: 0.2; - } - 100% { - transform: scale(1.5) rotate(-720deg); - opacity: 1; - } + 0% { + transform: scale(0.2) rotate(0deg); + opacity: 0.2; + } + 100% { + transform: scale(1.5) rotate(-720deg); + opacity: 1; + } } @keyframes blast { - 0%, - 100% { - transform: scale(1) rotate(0deg); - } - 50% { - transform: scale(2) rotate(180deg); - } + 0%, + 100% { + transform: scale(1) rotate(0deg); + } + 50% { + transform: scale(2) rotate(180deg); + } } @keyframes spin-bounce { - 0%, - 100% { - transform: rotate(0deg) translateY(0px); - } - 50% { - transform: rotate(180deg) translateY(-30px); - } + 0%, + 100% { + transform: rotate(0deg) translateY(0px); + } + 50% { + transform: rotate(180deg) translateY(-30px); + } } @keyframes flip-bounce { - 0%, - 100% { - transform: rotateX(0deg) translateY(0px); - } - 50% { - transform: rotateX(180deg) translateY(-25px); - } + 0%, + 100% { + transform: rotateX(0deg) translateY(0px); + } + 50% { + transform: rotateX(180deg) translateY(-25px); + } } @keyframes scale-bounce { - 0%, - 100% { - transform: scale(1) translateY(0px); - } - 50% { - transform: scale(1.5) translateY(-40px); - } + 0%, + 100% { + transform: scale(1) translateY(0px); + } + 50% { + transform: scale(1.5) translateY(-40px); + } } /* New Animation Classes */ .animate-orbit-4 { - animation: orbit-4 18s linear infinite; + animation: orbit-4 18s linear infinite; } .animate-orbit-5 { - animation: orbit-5 22s linear infinite reverse; + animation: orbit-5 22s linear infinite reverse; } .animate-zigzag-1 { - animation: zigzag-1 18s linear infinite; + animation: zigzag-1 18s linear infinite; } .animate-zigzag-2 { - animation: zigzag-2 22s linear infinite; + animation: zigzag-2 22s linear infinite; } .animate-zigzag-3 { - animation: zigzag-3 16s linear infinite; + animation: zigzag-3 16s linear infinite; } .animate-spiral-1 { - animation: spiral-1 30s linear infinite; + animation: spiral-1 30s linear infinite; } .animate-spiral-2 { - animation: spiral-2 25s linear infinite; + animation: spiral-2 25s linear infinite; } .animate-float-random-1 { - animation: float-random-1 8s ease-in-out infinite; + animation: float-random-1 8s ease-in-out infinite; } .animate-float-random-2 { - animation: float-random-2 10s ease-in-out infinite; + animation: float-random-2 10s ease-in-out infinite; } .animate-float-random-3 { - animation: float-random-3 12s ease-in-out infinite; + animation: float-random-3 12s ease-in-out infinite; } .animate-float-random-4 { - animation: float-random-4 9s ease-in-out infinite; + animation: float-random-4 9s ease-in-out infinite; } .animate-wave-1 { - animation: wave-1 20s linear infinite; + animation: wave-1 20s linear infinite; } .animate-wave-2 { - animation: wave-2 24s linear infinite; + animation: wave-2 24s linear infinite; } .animate-wave-3 { - animation: wave-3 18s linear infinite; + animation: wave-3 18s linear infinite; } .animate-wave-4 { - animation: wave-4 26s linear infinite; + animation: wave-4 26s linear infinite; } .animate-corner-shoot-1 { - animation: corner-shoot-1 15s linear infinite; + animation: corner-shoot-1 15s linear infinite; } .animate-corner-shoot-2 { - animation: corner-shoot-2 18s linear infinite; + animation: corner-shoot-2 18s linear infinite; } .animate-corner-shoot-3 { - animation: corner-shoot-3 20s linear infinite; + animation: corner-shoot-3 20s linear infinite; } .animate-corner-shoot-4 { - animation: corner-shoot-4 16s linear infinite; + animation: corner-shoot-4 16s linear infinite; } .animate-bounce-ball-1 { - animation: bounce-ball-1 12s ease-in-out infinite; + animation: bounce-ball-1 12s ease-in-out infinite; } .animate-bounce-ball-2 { - animation: bounce-ball-2 14s ease-in-out infinite; + animation: bounce-ball-2 14s ease-in-out infinite; } .animate-bounce-ball-3 { - animation: bounce-ball-3 10s ease-in-out infinite; + animation: bounce-ball-3 10s ease-in-out infinite; } .animate-spin-fast { - animation: spin-fast 2s linear infinite; + animation: spin-fast 2s linear infinite; } .animate-pulse-fast { - animation: pulse-fast 1.5s ease-in-out infinite; + animation: pulse-fast 1.5s ease-in-out infinite; } .animate-wobble { - animation: wobble 2s ease-in-out infinite; + animation: wobble 2s ease-in-out infinite; } .animate-shake { - animation: shake 0.5s ease-in-out infinite; + animation: shake 0.5s ease-in-out infinite; } .animate-bounce-crazy { - animation: bounce-crazy 1s ease-in-out infinite; + animation: bounce-crazy 1s ease-in-out infinite; } .animate-spin-wobble { - animation: spin-wobble 4s ease-in-out infinite; + animation: spin-wobble 4s ease-in-out infinite; } .animate-flip { - animation: flip 3s ease-in-out infinite; + animation: flip 3s ease-in-out infinite; } .animate-twirl { - animation: twirl 5s ease-in-out infinite; + animation: twirl 5s ease-in-out infinite; } .animate-dance { - animation: dance 3s ease-in-out infinite; + animation: dance 3s ease-in-out infinite; } .animate-jiggle { - animation: jiggle 1s ease-in-out infinite; + animation: jiggle 1s ease-in-out infinite; } .animate-vibrate { - animation: vibrate 0.3s ease-in-out infinite; + animation: vibrate 0.3s ease-in-out infinite; } .animate-swing { - animation: swing 4s ease-in-out infinite; + animation: swing 4s ease-in-out infinite; } .animate-pendulum { - animation: pendulum 6s ease-in-out infinite; + animation: pendulum 6s ease-in-out infinite; } .animate-elastic { - animation: elastic 4s ease-in-out infinite; + animation: elastic 4s ease-in-out infinite; } .animate-rubber { - animation: rubber 2s ease-in-out infinite; + animation: rubber 2s ease-in-out infinite; } .animate-rocket { - animation: rocket 8s ease-in-out infinite; + animation: rocket 8s ease-in-out infinite; } .animate-comet { - animation: comet 12s ease-in-out infinite; + animation: comet 12s ease-in-out infinite; } .animate-meteor { - animation: meteor 10s ease-in-out infinite; + animation: meteor 10s ease-in-out infinite; } .animate-blast { - animation: blast 3s ease-in-out infinite; + animation: blast 3s ease-in-out infinite; } .animate-spin-bounce { - animation: spin-bounce 4s ease-in-out infinite; + animation: spin-bounce 4s ease-in-out infinite; } .animate-flip-bounce { - animation: flip-bounce 5s ease-in-out infinite; + animation: flip-bounce 5s ease-in-out infinite; } .animate-scale-bounce { - animation: scale-bounce 3s ease-in-out infinite; + animation: scale-bounce 3s ease-in-out infinite; } /* Animated Border Light */ @keyframes border-light { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } .animate-border-light { - position: relative; - border-radius: 1rem; + position: relative; + border-radius: 1rem; } .animate-border-light::before { - content: ""; - position: absolute; - inset: -2px; - background: conic-gradient( - from 0deg, - transparent 0deg, - transparent 270deg, - rgba(168, 85, 247, 0.8) 300deg, - rgba(147, 51, 234, 1) 330deg, - rgba(168, 85, 247, 0.8) 360deg, - transparent 30deg, - transparent 360deg - ); - border-radius: inherit; - animation: border-light 3s linear infinite; - z-index: -1; + content: ""; + position: absolute; + inset: -2px; + background: conic-gradient( + from 0deg, + transparent 0deg, + transparent 270deg, + rgba(168, 85, 247, 0.8) 300deg, + rgba(147, 51, 234, 1) 330deg, + rgba(168, 85, 247, 0.8) 360deg, + transparent 30deg, + transparent 360deg + ); + border-radius: inherit; + animation: border-light 3s linear infinite; + z-index: -1; } .animate-border-light::after { - content: ""; - position: absolute; - inset: 2px; - background: inherit; - border-radius: inherit; - z-index: -1; + content: ""; + position: absolute; + inset: 2px; + background: inherit; + border-radius: inherit; + z-index: -1; } diff --git a/apps/main/src/pages/planning.tsx b/apps/main/src/pages/planning.tsx index 4bd0a3a..7628b25 100644 --- a/apps/main/src/pages/planning.tsx +++ b/apps/main/src/pages/planning.tsx @@ -3,6 +3,13 @@ import { WebcalModal } from "@ui/components/WebcalModal"; import { downloadICSFile, generateICSFromEvents, toast } from "@xtablo/shared"; import { EventAndTablo } from "@xtablo/shared/types/events.types"; import { Button } from "@xtablo/ui/components/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@xtablo/ui/components/dropdown-menu"; import { Select, SelectContent, @@ -11,10 +18,33 @@ import { SelectValue, } from "@xtablo/ui/components/select"; import { TypographyH3, TypographyH4 } from "@xtablo/ui/components/typography"; -import { ClockIcon, Download, EllipsisVerticalIcon, FolderInputIcon, PlusIcon, RefreshCcw } from "lucide-react"; +import { + ClockIcon, + Compass, + Download, + EllipsisVerticalIcon, + Flame, + FolderIcon, + FolderInputIcon, + Gem, + Heart, + Leaf, + PlusIcon, + RefreshCcw, + Sparkles, + Star, + Sun, + Waves, + Zap, +} from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Outlet, useNavigate, useParams, useSearchParams } from "react-router-dom"; +import { + Outlet, + useNavigate, + useParams, + useSearchParams, +} from "react-router-dom"; import { useDeleteEvent, useEventsByTablo } from "../hooks/events"; import { useGetAllTabloAccess, useTablosList } from "../hooks/tablos"; import { useIsReadOnlyUser } from "../providers/UserStoreProvider"; @@ -22,6 +52,43 @@ import { EventModal } from "../components/EventModal"; type ViewType = "month" | "week" | "day"; +function getTabloIcon(color: string | null | undefined) { + switch (color) { + case "bg-blue-500": + return Zap; + case "bg-green-500": + return Leaf; + case "bg-purple-500": + return Gem; + case "bg-red-500": + return Flame; + case "bg-yellow-500": + return Star; + case "bg-indigo-500": + return Compass; + case "bg-pink-500": + return Heart; + case "bg-teal-500": + return Waves; + case "bg-orange-500": + return Sun; + case "bg-cyan-500": + return Sparkles; + default: + return FolderIcon; + } +} + +function getTabloIconColor(color: string | null | undefined): string { + switch (color) { + case "bg-yellow-500": + case "bg-cyan-500": + return "text-gray-700"; + default: + return "text-white"; + } +} + export const PlanningPage = () => { const { t } = useTranslation(["planning", "common"]); const { tablo_id } = useParams(); @@ -33,10 +100,14 @@ export const PlanningPage = () => { // Initialize view from URL search params, default to "month" const viewFromUrl = searchParams.get("view") as ViewType | null; const initialView: ViewType = - viewFromUrl && ["month", "week", "day"].includes(viewFromUrl) ? viewFromUrl : "month"; + viewFromUrl && ["month", "week", "day"].includes(viewFromUrl) + ? viewFromUrl + : "month"; const [currentView, setCurrentView] = useState(initialView); - const [selectedTabloId, setSelectedTabloId] = useState(tablo_id || "all"); + const [selectedTabloId, setSelectedTabloId] = useState( + tablo_id || "all", + ); const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [isWebcalModalOpen, setIsWebcalModalOpen] = useState(false); const isReadOnly = useIsReadOnlyUser(); @@ -48,9 +119,8 @@ export const PlanningPage = () => { const { data: tablos, isLoading: tablosLoading } = useTablosList(); // Fetch events for selected tablo or all tablos - const { data: tabloEvents = [], isLoading: tabloEventsLoading } = useEventsByTablo( - selectedTabloId !== "all" ? selectedTabloId : null - ); + const { data: tabloEvents = [], isLoading: tabloEventsLoading } = + useEventsByTablo(selectedTabloId !== "all" ? selectedTabloId : null); // Fetch all tablo accesses const { data: tabloAccess } = useGetAllTabloAccess(); @@ -61,7 +131,7 @@ export const PlanningPage = () => { if ( tabloAccess?.find( (access: { tablo_id: string; is_admin: boolean }) => - access.tablo_id === event.tablo_id && access.is_admin + access.tablo_id === event.tablo_id && access.is_admin, ) ) { return true; @@ -84,13 +154,17 @@ export const PlanningPage = () => { return newParams; }); }, - [setSearchParams] + [setSearchParams], ); // Sync view with URL on mount and when URL changes useEffect(() => { const viewParam = searchParams.get("view") as ViewType | null; - if (viewParam && ["month", "week", "day"].includes(viewParam) && viewParam !== currentView) { + if ( + viewParam && + ["month", "week", "day"].includes(viewParam) && + viewParam !== currentView + ) { setCurrentView(viewParam); } else if (!viewParam) { // If no view param in URL, set it to current view @@ -100,7 +174,7 @@ export const PlanningPage = () => { newParams.set("view", currentView); return newParams; }, - { replace: true } + { replace: true }, ); } }, [searchParams, currentView, setSearchParams]); @@ -151,7 +225,8 @@ export const PlanningPage = () => { const calendarName = selectedTabloId === "all" ? t("planning:allEvents") - : tablos?.find((t) => t.id === selectedTabloId)?.name || t("planning:title"); + : tablos?.find((t) => t.id === selectedTabloId)?.name || + t("planning:title"); const icsContent = generateICSFromEvents(tabloEvents, calendarName); const filename = @@ -176,6 +251,18 @@ export const PlanningPage = () => { } }; + const navigateToEditEvent = (event: EventAndTablo) => { + if (!canEditEvent(event) || isReadOnly) return; + + const params = new URLSearchParams(searchParams); + params.set("tab", "events"); + + navigate({ + pathname: `/planning/${event.tablo_id}/events/${event.event_id}/edit`, + search: `?${params.toString()}`, + }); + }; + const monthNames = [ t("planning:months.january"), t("planning:months.february"), @@ -268,7 +355,12 @@ export const PlanningPage = () => { // const nowMinute = now.getMinutes(); const nowDay = now.getDate(); - fullDate.setHours(Number(time.split(":")[0]), Number(time.split(":")[1]), 0, 0); + fullDate.setHours( + Number(time.split(":")[0]), + Number(time.split(":")[1]), + 0, + 0, + ); const hour = fullDate.getHours(); // const minute = fullDate.getMinutes(); @@ -336,7 +428,8 @@ export const PlanningPage = () => { const daysInMonth = lastDay.getDate(); // Adjust for Monday as first day of week const startingDayOfWeek = firstDay.getDay(); - const mondayStartingDay = startingDayOfWeek === 0 ? 6 : startingDayOfWeek - 1; + const mondayStartingDay = + startingDayOfWeek === 0 ? 6 : startingDayOfWeek - 1; const days = []; for (let i = 0; i < mondayStartingDay; i++) { @@ -373,7 +466,9 @@ export const PlanningPage = () => { if (currentView === "week") { const weekDays = getWeekDays(); const weekDateStrings = weekDays.map(formatDate); - return tabloEvents.filter((event) => weekDateStrings.includes(event.start_date)); + return tabloEvents.filter((event) => + weekDateStrings.includes(event.start_date), + ); } else if (currentView === "day") { const dateString = formatDate(currentDate); return tabloEvents.filter((event) => event.start_date === dateString); @@ -394,7 +489,7 @@ export const PlanningPage = () => { ...visibleEvents.map((event) => { const [hour] = event.start_time.split(":").map(Number); return hour; - }) + }), ); // Return the earlier of 8am or the earliest event hour @@ -418,7 +513,7 @@ export const PlanningPage = () => { } const [hour] = event.end_time.split(":").map(Number); return hour; - }) + }), ); // Return the later of 7pm or the latest event hour @@ -445,7 +540,7 @@ export const PlanningPage = () => { return Array.from( { length: numSlots }, - (_, i) => `${(startHour + i).toString().padStart(2, "0")}:00` + (_, i) => `${(startHour + i).toString().padStart(2, "0")}:00`, ); }; @@ -473,7 +568,9 @@ export const PlanningPage = () => { className={`min-h-[120px] border-b border-border ${ (index + 1) % 7 !== 0 ? "border-r border-border" : "" } ${day ? "cursor-pointer hover:bg-muted" : "bg-muted"} ${ - day && formatDate(day) === formatDate(new Date()) ? "bg-primary/10" : "" + day && formatDate(day) === formatDate(new Date()) + ? "bg-primary/10" + : "" }`} onClick={() => { if (day) { @@ -491,7 +588,9 @@ export const PlanningPage = () => {
{day.getDate()} @@ -518,14 +617,18 @@ export const PlanningPage = () => { onClick={(e) => { e.stopPropagation(); if (canEditEvent(event)) { - navigate(`/planning/${event.tablo_id}/events/${event.event_id}/edit`); + navigate( + `/planning/${event.tablo_id}/events/${event.event_id}/edit`, + ); } }} >
{formatTime(event.start_time)} {event.title} {selectedTabloId === "all" && event.tablo_name && ( - • {event.tablo_name} + + • {event.tablo_name} + )}
{canDeleteEvent(event) && ( @@ -573,7 +676,9 @@ export const PlanningPage = () => {
{day.getDate()} @@ -615,10 +720,18 @@ export const PlanningPage = () => { )} {getEventsForDate(day) - .filter((event) => event.start_time.startsWith(time.split(":")[0])) + .filter((event) => + event.start_time.startsWith(time.split(":")[0]), + ) .map((event) => { - const eventHeight = calculateEventHeight(event.start_time, event.end_time); - const eventOffset = calculateEventOffset(event.start_time, time); + const eventHeight = calculateEventHeight( + event.start_time, + event.end_time, + ); + const eventOffset = calculateEventOffset( + event.start_time, + time, + ); return (
{ minHeight: "30px", }} title={`${formatTime(event.start_time)} - ${formatTime( - event.end_time + event.end_time, )} ${event.title}${ selectedTabloId === "all" && event.tablo_name ? ` - ${event.tablo_name}` @@ -644,19 +757,24 @@ export const PlanningPage = () => { onClick={(e) => { e.stopPropagation(); if (canEditEvent(event)) { - navigate(`/planning/${event.tablo_id}/events/${event.event_id}/edit`); + navigate( + `/planning/${event.tablo_id}/events/${event.event_id}/edit`, + ); } }} >
{event.title} {selectedTabloId === "all" && event.tablo_name && ( - • {event.tablo_name} + + • {event.tablo_name} + )}
{eventHeight >= 30 && (
- {formatTime(event.start_time)} - {formatTime(event.end_time)} + {formatTime(event.start_time)} -{" "} + {formatTime(event.end_time)}
)} {canDeleteEvent(event) && ( @@ -689,7 +807,9 @@ export const PlanningPage = () => {
{dayNames[currentDate.getDay()]}
-
{currentDate.getDate()}
+
+ {currentDate.getDate()} +
{/* Time slots */} @@ -722,10 +842,18 @@ export const PlanningPage = () => { )} {getEventsForDate(currentDate) - .filter((event) => event.start_time.startsWith(time.split(":")[0])) + .filter((event) => + event.start_time.startsWith(time.split(":")[0]), + ) .map((event) => { - const eventHeight = calculateEventHeight(event.start_time, event.end_time); - const eventOffset = calculateEventOffset(event.start_time, time); + const eventHeight = calculateEventHeight( + event.start_time, + event.end_time, + ); + const eventOffset = calculateEventOffset( + event.start_time, + time, + ); return (
{ minHeight: "30px", }} title={`${formatTime(event.start_time)} - ${formatTime( - event.end_time + event.end_time, )} ${event.title}${ selectedTabloId === "all" && event.tablo_name ? ` - ${event.tablo_name}` @@ -751,19 +879,24 @@ export const PlanningPage = () => { onClick={(e) => { e.stopPropagation(); if (canEditEvent(event)) { - navigate(`/planning/${event.tablo_id}/events/${event.event_id}/edit`); + navigate( + `/planning/${event.tablo_id}/events/${event.event_id}/edit`, + ); } }} >
{event.title} {selectedTabloId === "all" && event.tablo_name && ( - • {event.tablo_name} + + • {event.tablo_name} + )}
{eventHeight >= 30 && (
- {formatTime(event.start_time)} - {formatTime(event.end_time)} + {formatTime(event.start_time)} -{" "} + {formatTime(event.end_time)}
)} {eventHeight >= 75 && event.description && ( @@ -799,11 +932,26 @@ export const PlanningPage = () => { today.setHours(0, 0, 0, 0); const filtered = tabloEvents.filter((e) => { if (showAllEvents) return true; - const eventDate = e.start_date ? new Date(e.start_date + "T00:00:00") : null; + const eventDate = e.start_date + ? new Date(e.start_date + "T00:00:00") + : null; return !eventDate || eventDate >= today; }); - const months = ["JAN", "FÉV", "MAR", "AVR", "MAI", "JUN", "JUL", "AOÛ", "SEP", "OCT", "NOV", "DÉC"]; + const months = [ + "JAN", + "FÉV", + "MAR", + "AVR", + "MAI", + "JUN", + "JUL", + "AOÛ", + "SEP", + "OCT", + "NOV", + "DÉC", + ]; return (
@@ -814,7 +962,9 @@ export const PlanningPage = () => {

+ + + + + + navigateToEditEvent(event)} + disabled={!canEditEvent(event) || isReadOnly} + > + Replanifier + + + deleteEvent.mutate(event.event_id)} + disabled={ + !canDeleteEvent(event) || + isReadOnly || + deleteEvent.isPending + } + className="text-destructive focus:text-destructive" + > + Supprimer + + +
{/* Date badge */} @@ -922,11 +1109,13 @@ export const PlanningPage = () => { {event.tablo_name && (
- {event.tablo_name.charAt(0).toUpperCase()} +
- {event.tablo_name} + + {event.tablo_name} +
)}
@@ -947,7 +1136,9 @@ export const PlanningPage = () => { mode="create" isOpen={isCreateEventOpen} onClose={() => setIsCreateEventOpen(false)} - defaultTabloId={selectedTabloId !== "all" ? selectedTabloId : undefined} + defaultTabloId={ + selectedTabloId !== "all" ? selectedTabloId : undefined + } defaultDate={currentDate} /> @@ -968,10 +1159,15 @@ export const PlanningPage = () => { onValueChange={(value) => setSelectedTabloId(value)} disabled={tablosLoading} > - + @@ -996,15 +1192,17 @@ export const PlanningPage = () => { "Vous êtes en mode lecture seule. Vous ne pouvez pas créer d'événement.", type: "error", }, - { timeout: 5000 } + { timeout: 5000 }, ); return; } if (selectedTabloId === "all") { - navigate(`/planning/create?date=${currentDate.toISOString()}`); + navigate( + `/planning/create?date=${currentDate.toISOString()}`, + ); } else { navigate( - `/planning/create?tablo_id=${selectedTabloId}&date=${currentDate.toISOString()}` + `/planning/create?tablo_id=${selectedTabloId}&date=${currentDate.toISOString()}`, ); } }} @@ -1025,7 +1223,7 @@ export const PlanningPage = () => { "Vous êtes en mode lecture seule. Vous ne pouvez pas importer de calendrier.", type: "error", }, - { timeout: 5000 } + { timeout: 5000 }, ); return; } @@ -1058,7 +1256,10 @@ export const PlanningPage = () => {
{dayNamesShort.map((day) => ( -
+
{day.slice(0, 1)}
))} @@ -1094,12 +1295,25 @@ export const PlanningPage = () => {
{t("planning:title")} -
-
) : ( <> @@ -1179,9 +1403,14 @@ export const PlanningPage = () => { - {isImportModalOpen && setIsImportModalOpen(false)} />} + {isImportModalOpen && ( + setIsImportModalOpen(false)} /> + )} - +
); };