diff --git a/apps/desktop/index.html b/apps/desktop/index.html index dc0606a57e..2a8e1dc271 100644 --- a/apps/desktop/index.html +++ b/apps/desktop/index.html @@ -7,7 +7,7 @@ name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> - Hypr - AI Notepad for Meetings + Hypr - Smart Notepad for Meetings diff --git a/apps/desktop/package.json b/apps/desktop/package.json index a0a61fc244..e8ba8224ed 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -11,6 +11,7 @@ "format": "prettier --write ." }, "dependencies": { + "@hypr/proto": "workspace:*", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-dropdown-menu": "^2.1.3", "@radix-ui/react-form": "^0.1.1", @@ -39,6 +40,7 @@ "date-fns": "^4.1.0", "embla-carousel": "^8.5.1", "embla-carousel-react": "^8.5.1", + "framer-motion": "^11.15.0", "lucide-react": "^0.468.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 713c637a40..50087d5d47 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -15,7 +15,7 @@ "decorations": true, "height": 800, "width": 1200, - "title": "Hypr - AI Notepad for Meetings" + "title": "Hypr - Smart Notepad for Meetings" } ], "security": { diff --git a/apps/desktop/src/components/AudioControls.tsx b/apps/desktop/src/components/AudioControls.tsx new file mode 100644 index 0000000000..74b2b308f3 --- /dev/null +++ b/apps/desktop/src/components/AudioControls.tsx @@ -0,0 +1,74 @@ +import { useState, useEffect } from "react"; +import { RiPlayFill, RiPauseFill } from "@remixicon/react"; + +export const AudioControls = () => { + const [isPlaying, setIsPlaying] = useState(true); + const [audio] = useState(new Audio("/bgm.mp3")); + + useEffect(() => { + audio.loop = true; + audio.play().catch((error) => console.log("Audio autoplay failed:", error)); + + // Set up media session + if ("mediaSession" in navigator) { + navigator.mediaSession.metadata = new MediaMetadata({ + title: "Hypr Background Music", + artist: "Hypr", + album: "Hypr App", + artwork: [ + { + src: "/favicon.ico", + sizes: "96x96", + type: "image/png", + }, + ], + }); + + navigator.mediaSession.setActionHandler("play", () => { + setIsPlaying(true); + audio.play(); + }); + + navigator.mediaSession.setActionHandler("pause", () => { + setIsPlaying(false); + audio.pause(); + }); + } + + return () => { + audio.pause(); + audio.currentTime = 0; + + // Clean up media session handlers + if ("mediaSession" in navigator) { + navigator.mediaSession.setActionHandler("play", null); + navigator.mediaSession.setActionHandler("pause", null); + } + }; + }, [audio]); + + useEffect(() => { + if (isPlaying) { + audio.play().catch((error) => console.log("Audio play failed:", error)); + if ("mediaSession" in navigator) { + navigator.mediaSession.playbackState = "playing"; + } + } else { + audio.pause(); + if ("mediaSession" in navigator) { + navigator.mediaSession.playbackState = "paused"; + } + } + }, [isPlaying, audio]); + + return ( +
+ +
+ ); +}; diff --git a/apps/desktop/src/components/ui/blur-fade.tsx b/apps/desktop/src/components/ui/blur-fade.tsx new file mode 100644 index 0000000000..44e897ee49 --- /dev/null +++ b/apps/desktop/src/components/ui/blur-fade.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { useRef } from "react"; +import { + AnimatePresence, + motion, + useInView, + UseInViewOptions, + Variants, +} from "framer-motion"; + +type MarginType = UseInViewOptions["margin"]; + +interface BlurFadeProps { + children: React.ReactNode; + className?: string; + variant?: { + hidden: { y: number }; + visible: { y: number }; + }; + duration?: number; + delay?: number; + yOffset?: number; + inView?: boolean; + inViewMargin?: MarginType; + blur?: string; +} + +export default function BlurFade({ + children, + className, + variant, + duration = 0.4, + delay = 0, + yOffset = 6, + inView = false, + inViewMargin = "-50px", + blur = "6px", +}: BlurFadeProps) { + const ref = useRef(null); + const inViewResult = useInView(ref, { once: true, margin: inViewMargin }); + const isInView = !inView || inViewResult; + const defaultVariants: Variants = { + hidden: { y: yOffset, opacity: 0, filter: `blur(${blur})` }, + visible: { y: -yOffset, opacity: 1, filter: `blur(0px)` }, + }; + const combinedVariants = variant || defaultVariants; + return ( + + + {children} + + + ); +} diff --git a/apps/desktop/src/components/ui/retro-grid.tsx b/apps/desktop/src/components/ui/retro-grid.tsx index b026f00008..8d938082df 100644 --- a/apps/desktop/src/components/ui/retro-grid.tsx +++ b/apps/desktop/src/components/ui/retro-grid.tsx @@ -31,7 +31,6 @@ export function RetroGrid({ )} /> - {/* Background Gradient */}
diff --git a/apps/desktop/src/components/ui/shimmer-button.tsx b/apps/desktop/src/components/ui/shimmer-button.tsx new file mode 100644 index 0000000000..8aacad5fd2 --- /dev/null +++ b/apps/desktop/src/components/ui/shimmer-button.tsx @@ -0,0 +1,96 @@ +import React, { CSSProperties } from "react"; + +import { cn } from "../../lib/utils"; + +export interface ShimmerButtonProps + extends React.ButtonHTMLAttributes { + shimmerColor?: string; + shimmerSize?: string; + borderRadius?: string; + shimmerDuration?: string; + background?: string; + className?: string; + children?: React.ReactNode; +} + +const ShimmerButton = React.forwardRef( + ( + { + shimmerColor = "#ffffff", + shimmerSize = "0.05em", + shimmerDuration = "3s", + borderRadius = "100px", + background = "rgba(0, 0, 0, 1)", + className, + children, + ...props + }, + ref, + ) => { + return ( + + ); + }, +); + +ShimmerButton.displayName = "ShimmerButton"; + +export default ShimmerButton; diff --git a/apps/desktop/src/components/ui/sparkles-text.tsx b/apps/desktop/src/components/ui/sparkles-text.tsx new file mode 100644 index 0000000000..08f1ad5f69 --- /dev/null +++ b/apps/desktop/src/components/ui/sparkles-text.tsx @@ -0,0 +1,150 @@ +"use client"; + +import { CSSProperties, ReactElement, useEffect, useState } from "react"; +import { motion } from "framer-motion"; +import { cn } from "../../lib/utils"; + +interface Sparkle { + id: string; + x: string; + y: string; + color: string; + delay: number; + scale: number; + lifespan: number; +} + +interface SparklesTextProps { + /** + * @default
+ * @type ReactElement + * @description + * The component to be rendered as the text + * */ + as?: ReactElement; + + /** + * @default "" + * @type string + * @description + * The className of the text + */ + className?: string; + + /** + * @required + * @type string + * @description + * The text to be displayed + * */ + text: string; + + /** + * @default 10 + * @type number + * @description + * The count of sparkles + * */ + sparklesCount?: number; + + /** + * @default "{first: '#9E7AFF', second: '#FE8BBB'}" + * @type string + * @description + * The colors of the sparkles + * */ + colors?: { + first: string; + second: string; + }; +} + +const SparklesText: React.FC = ({ + text, + colors = { first: "#9E7AFF", second: "#FE8BBB" }, + className, + sparklesCount = 10, + ...props +}) => { + const [sparkles, setSparkles] = useState([]); + + useEffect(() => { + const generateStar = (): Sparkle => { + const starX = `${Math.random() * 100}%`; + const starY = `${Math.random() * 100}%`; + const color = Math.random() > 0.5 ? colors.first : colors.second; + const delay = Math.random() * 2; + const scale = Math.random() * 1 + 0.3; + const lifespan = Math.random() * 10 + 5; + const id = `${starX}-${starY}-${Date.now()}`; + return { id, x: starX, y: starY, color, delay, scale, lifespan }; + }; + + const initializeStars = () => { + const newSparkles = Array.from({ length: sparklesCount }, generateStar); + setSparkles(newSparkles); + }; + + const updateStars = () => { + setSparkles((currentSparkles) => + currentSparkles.map((star) => { + if (star.lifespan <= 0) { + return generateStar(); + } else { + return { ...star, lifespan: star.lifespan - 0.1 }; + } + }), + ); + }; + + initializeStars(); + const interval = setInterval(updateStars, 100); + + return () => clearInterval(interval); + }, [colors.first, colors.second]); + + return ( +
+ + {sparkles.map((sparkle) => ( + + ))} + + {text} + + +
+ ); +}; + +const Sparkle: React.FC = ({ id, x, y, color, delay, scale }) => { + return ( + + + + ); +}; + +export default SparklesText; diff --git a/apps/desktop/src/pages/Login.tsx b/apps/desktop/src/pages/Login.tsx index 1b4321b92d..328b1af655 100644 --- a/apps/desktop/src/pages/Login.tsx +++ b/apps/desktop/src/pages/Login.tsx @@ -1,30 +1,11 @@ -import { useState, useEffect } from "react"; -import { - RiGoogleFill, - RiAppleFill, - RiVolumeMuteFill, - RiVolumeUpFill, -} from "@remixicon/react"; +import { RiGoogleFill, RiAppleFill } from "@remixicon/react"; import { RetroGrid } from "../components/ui/retro-grid.tsx"; +import { AudioControls } from "../components/AudioControls"; +import BlurFade from "../components/ui/blur-fade.tsx"; +import SparklesText from "../components/ui/sparkles-text.tsx"; +import ShimmerButton from "../components/ui/shimmer-button.tsx"; const Login = () => { - const [isMuted, setIsMuted] = useState(false); - const [audio] = useState(new Audio("/bgm.mp3")); - - useEffect(() => { - audio.loop = true; - audio.play().catch((error) => console.log("Audio autoplay failed:", error)); - - return () => { - audio.pause(); - audio.currentTime = 0; - }; - }, [audio]); - - useEffect(() => { - audio.muted = isMuted; - }, [isMuted, audio]); - const handleGoogleSignIn = () => { // Implement Google Sign In console.log("Google Sign In clicked"); @@ -36,38 +17,42 @@ const Login = () => { }; return ( -
- - - - -
-

Welcome to Hypr

- - - - +
+ + + + +
+ +

+ Welcome to{" "} + +

+
+ +
+ + + Continue with Google + + + + + Continue with Apple + +
); diff --git a/apps/desktop/src/styles/global.css b/apps/desktop/src/styles/global.css index 9b3e9b4497..d48d503e2b 100644 --- a/apps/desktop/src/styles/global.css +++ b/apps/desktop/src/styles/global.css @@ -1,3 +1,5 @@ +@import url("https://fonts.googleapis.com/css2?family=Racing+Sans+One&display=swap"); + @tailwind base; @tailwind components; @tailwind utilities; diff --git a/apps/desktop/src/types/index.ts b/apps/desktop/src/types/index.ts index f97747f2ab..532bf71b7e 100644 --- a/apps/desktop/src/types/index.ts +++ b/apps/desktop/src/types/index.ts @@ -2,6 +2,7 @@ import type { Node as PMNode } from "@tiptap/pm/model"; export * from "./db"; export * from "./tauri"; +export * from "@hypr/proto"; // Google Calendar Event Type export interface CalendarEvent { diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index 96c4c46f7e..e5c8fbe6d0 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -73,10 +73,32 @@ export default { transform: "translateY(0)", }, }, + "shimmer-slide": { + to: { + transform: "translate(calc(100cqw - 100%), 0)", + }, + }, + "spin-around": { + "0%": { + transform: "translateZ(0) rotate(0)", + }, + "15%, 35%": { + transform: "translateZ(0) rotate(90deg)", + }, + "65%, 85%": { + transform: "translateZ(0) rotate(270deg)", + }, + "100%": { + transform: "translateZ(0) rotate(360deg)", + }, + }, }, animation: { marquee: "marquee 10s linear infinite", grid: "grid 15s linear infinite", + "shimmer-slide": + "shimmer-slide var(--speed) ease-in-out infinite alternate", + "spin-around": "spin-around calc(var(--speed) * 2) infinite linear", }, transitionTimingFunction: { "ease-in-out-expo": "cubic-bezier(0.87, 0, 0.13, 1)", diff --git a/apps/web/package.json b/apps/web/package.json index 674a9c00ca..7d4234b9f6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -18,7 +18,6 @@ "@remixicon/react": "^4.5.0", "@types/mdx": "^2.0.13", "class-variance-authority": "^0.7.1", - "classnames": "^2.5.1", "clsx": "^2.0.0", "framer-motion": "^11.15.0", "next": "15.1.0", diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index c3a7bf1dac..45648ad692 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -4,13 +4,72 @@ @layer base { :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + + --radius: 0.5rem; + --color-1: 0 100% 63%; --color-2: 270 100% 63%; --color-3: 210 100% 63%; --color-4: 195 100% 63%; --color-5: 90 100% 63%; } + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + --color-1: 0 100% 63%; --color-2: 270 100% 63%; --color-3: 210 100% 63%; @@ -18,3 +77,12 @@ --color-5: 90 100% 63%; } } + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/apps/web/src/components/hero/Announcement.tsx b/apps/web/src/components/hero/Announcement.tsx new file mode 100644 index 0000000000..dffab570d6 --- /dev/null +++ b/apps/web/src/components/hero/Announcement.tsx @@ -0,0 +1,12 @@ +import AnimatedGradientText from "../ui/animated-gradient-text"; + +export default function Announcement() { + return ( + + ⚡️ + + Hyprnote is now in public beta! + + + ); +} diff --git a/apps/web/src/components/hero/Demo.tsx b/apps/web/src/components/hero/Demo.tsx new file mode 100644 index 0000000000..e481a43c48 --- /dev/null +++ b/apps/web/src/components/hero/Demo.tsx @@ -0,0 +1,47 @@ +export default function Demo() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ ); +} diff --git a/apps/web/src/components/hero/LogoCard.tsx b/apps/web/src/components/hero/LogoCard.tsx new file mode 100644 index 0000000000..ebcdedb51b --- /dev/null +++ b/apps/web/src/components/hero/LogoCard.tsx @@ -0,0 +1,8 @@ +export default function LogoCard() { + return ( +
+ logo +

R

+
+ ); +} diff --git a/apps/web/src/components/sections/cta.tsx b/apps/web/src/components/sections/cta.tsx index 8a2402e9ec..0c0f422fcf 100644 --- a/apps/web/src/components/sections/cta.tsx +++ b/apps/web/src/components/sections/cta.tsx @@ -5,11 +5,10 @@ export default function CtaSection() {

- Ready to transform your meeting notes? + Start Typing Now

- Join thousands of professionals who are already using HyprNote to - capture and understand their meetings better. + Take notes smarter, faster, and more efficiently

diff --git a/apps/web/src/components/sections/hero.tsx b/apps/web/src/components/sections/hero.tsx index ee95462ef9..61bf38b3f5 100644 --- a/apps/web/src/components/sections/hero.tsx +++ b/apps/web/src/components/sections/hero.tsx @@ -1,51 +1,24 @@ import Image from "next/image"; import { CtaButton } from "@/components/ui/cta-button"; +import Announcement from "../hero/Announcement"; +import Demo from "../hero/Demo"; export default function Hero() { return (
-
- - Now with offline support for English -
+ -

- Your AI Meeting Assistant - - that actually understands your conversations - -

+

Hypercharge Notetaking

-

- HyprNote uses advanced AI to capture every detail of your meetings, - lectures, and video calls. Get instant summaries, action items, and - searchable transcripts - all without inviting any bots. +

+ Hypnote is a smart notepad that upgrades your notes by automatically + transcribing and analyzing the conversation

-
- HyprNote Interface -
- -
-

- Trusted by innovative teams at -

-
- Stanford - MIT - Harvard -
-
+
); diff --git a/apps/web/src/components/sections/pricing.tsx b/apps/web/src/components/sections/pricing.tsx index 7c43e85bd6..b271ebf2a1 100644 --- a/apps/web/src/components/sections/pricing.tsx +++ b/apps/web/src/components/sections/pricing.tsx @@ -23,7 +23,7 @@ export default function Pricing() { const [currentPlan, setCurrentPlan] = useState<"Free" | "Pro" | "Business">( "Free", ); - const [daysLeft, setDaysLeft] = useState(14); // 남은 무료 사용 기간 (일) + const [daysLeft, setDaysLeft] = useState(14); // Remaining free trial days const billingPlans: BillingPlan[] = [ { @@ -34,11 +34,11 @@ export default function Pricing() { yearly: 0, }, features: [ - // 1. 기본 기능 - "노트 작성 및 편집", - "영어 음성 인식", - "기본 AI 기능", - "2주 무료 체험", + // 1. Basic features + "Note creation and editing", + "English speech recognition", + "Basic AI features", + "2-week free trial", ], }, { @@ -49,14 +49,14 @@ export default function Pricing() { yearly: 8, }, features: [ - // 2. 고급 기능 - "무제한 음성 인식", - "다국어 지원", - "오프라인 모드 (영어)", - "고급 AI 기능", - // 3. 협업 기능 - "노트 공유 링크", - "앱 연동 (노션, 슬랙 등)", + // 2. Advanced features + "Unlimited speech recognition", + "Multi-language support", + "Offline mode (English)", + "Advanced AI features", + // 3. Collaboration features + "Note sharing links", + "App integrations (Notion, Slack, etc.)", ], }, { @@ -67,15 +67,15 @@ export default function Pricing() { yearly: 12, }, features: [ - // 1. Pro 기능 포함 - "Pro 플랜의 모든 기능", - // 2. 팀 기능 - "팀 워크스페이스", - "상세 접근 권한 설정", - "공유 노트 수정 권한", - // 3. 관리 기능 - "팀 단위 결제", - "사용 현황 대시보드", + // 1. Including Pro features + "All Pro plan features", + // 2. Team features + "Team workspace", + "Detailed access control", + "Shared note editing permissions", + // 3. Management features + "Team billing", + "Usage dashboard", ], isComingSoon: true, }, @@ -88,12 +88,12 @@ export default function Pricing() { ) => { if (planType === "Free" && currentPlan === "Free") { if (daysLeft > 0) { - return `${daysLeft}일 남음`; + return `${daysLeft} days left`; } - return "기간 만료"; + return "Trial expired"; } - if (planLevel === currentLevel) return "사용중"; - return planLevel > currentLevel ? "업그레이드" : "다운그레이드"; + if (planLevel === currentLevel) return "Current plan"; + return planLevel > currentLevel ? "Upgrade" : "Downgrade"; }; const getPlanLevel = (planType: "Free" | "Pro" | "Business") => { @@ -152,7 +152,7 @@ export default function Pricing() { )} {currentPlan === plan.type && !plan.isComingSoon && ( - 현재 플랜 + Current Plan )} @@ -166,7 +166,7 @@ export default function Pricing() { ] } - /월 + /month
); -}; +} export default PushableButton; diff --git a/apps/web/src/components/ui/word-rotate.tsx b/apps/web/src/components/ui/word-rotate.tsx new file mode 100644 index 0000000000..3df340ef11 --- /dev/null +++ b/apps/web/src/components/ui/word-rotate.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { AnimatePresence, HTMLMotionProps, motion } from "framer-motion"; +import { cn } from "@/lib/utils"; + +interface WordRotateProps { + words: string[]; + duration?: number; + framerProps?: HTMLMotionProps<"span">; + className?: string; +} + +export default function WordRotate({ + words, + duration = 2500, + framerProps = { + initial: { opacity: 0, y: -50 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: 50 }, + transition: { duration: 0.25, ease: "easeOut" }, + }, + className, +}: WordRotateProps) { + const [index, setIndex] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setIndex((prevIndex) => (prevIndex + 1) % words.length); + }, duration); + + // Clean up interval on unmount + return () => clearInterval(interval); + }, [words, duration]); + + return ( +
+ + + {words[index]} + + +
+ ); +} diff --git a/apps/web/src/data/logoData.ts b/apps/web/src/data/logoData.ts new file mode 100644 index 0000000000..717c9b12ce --- /dev/null +++ b/apps/web/src/data/logoData.ts @@ -0,0 +1,8 @@ +export const universities = [ + { title: "UC Berkeley", file: "BerkeleyIcon" }, + { title: "Stanford", file: "StanfordIcon" }, + { title: "UC Berkeley", file: "BerkeleyIcon" }, + { title: "UC Berkeley", file: "BerkeleyIcon" }, + { title: "UC Berkeley", file: "BerkeleyIcon" }, + { title: "UC Berkeley", file: "BerkeleyIcon" }, +]; diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts index 8030867755..dc232a724b 100644 --- a/apps/web/tailwind.config.ts +++ b/apps/web/tailwind.config.ts @@ -8,6 +8,13 @@ export default { "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, extend: { colors: { background: "hsl(var(--background))", @@ -67,6 +74,11 @@ export default { animation: { pulse: "pulse var(--duration) ease-out infinite", rainbow: "rainbow var(--speed, 2s) infinite linear", + gradient: "gradient 8s linear infinite", + marquee: "marquee var(--duration) infinite linear", + "marquee-vertical": "marquee-vertical var(--duration) linear infinite", + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", }, keyframes: { pulse: { @@ -85,6 +97,47 @@ export default { "background-position": "200%", }, }, + gradient: { + to: { + backgroundPosition: "var(--bg-size) 0", + }, + }, + marquee: { + from: { + transform: "translateX(0)", + }, + to: { + transform: "translateX(calc(-100% - var(--gap)))", + }, + }, + "marquee-vertical": { + from: { + transform: "translateY(0)", + }, + to: { + transform: "translateY(calc(-100% - var(--gap)))", + }, + }, + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + }, + components: { + button: { + padding: "0.5rem 1rem", + borderRadius: "var(--radius)", + fontWeight: "500", + cursor: "pointer", + disabled: { + opacity: 0.5, + cursor: "not-allowed", + }, }, }, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 134b1a473f..21b96a8254 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: apps/desktop: dependencies: + '@hypr/proto': + specifier: workspace:* + version: link:../../packages/proto '@radix-ui/react-dialog': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -92,6 +95,9 @@ importers: embla-carousel-react: specifier: ^8.5.1 version: 8.5.1(react@18.3.1) + framer-motion: + specifier: ^11.15.0 + version: 11.15.0(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lucide-react: specifier: ^0.468.0 version: 0.468.0(react@18.3.1) @@ -186,9 +192,6 @@ importers: class-variance-authority: specifier: ^0.7.1 version: 0.7.1 - classnames: - specifier: ^2.5.1 - version: 2.5.1 clsx: specifier: ^2.0.0 version: 2.1.1 @@ -1906,9 +1909,6 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -5503,8 +5503,6 @@ snapshots: dependencies: clsx: 2.1.1 - classnames@2.5.1: {} - client-only@0.0.1: {} clsx@2.1.1: {} @@ -6089,6 +6087,16 @@ snapshots: fraction.js@4.3.7: {} + framer-motion@11.15.0(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + motion-dom: 11.14.3 + motion-utils: 11.14.3 + tslib: 2.8.1 + optionalDependencies: + '@emotion/is-prop-valid': 0.8.8 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + framer-motion@11.15.0(@emotion/is-prop-valid@0.8.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: motion-dom: 11.14.3