diff --git a/apps/craft-of-ui/public/audio/pop.mp3 b/apps/craft-of-ui/public/audio/pop.mp3 new file mode 100644 index 000000000..4b64f7f62 Binary files /dev/null and b/apps/craft-of-ui/public/audio/pop.mp3 differ diff --git a/apps/craft-of-ui/src/app/(content)/posts/_components/post-video-subscribe-form.tsx b/apps/craft-of-ui/src/app/(content)/posts/_components/post-video-subscribe-form.tsx index 68b3269df..4cf8b990e 100644 --- a/apps/craft-of-ui/src/app/(content)/posts/_components/post-video-subscribe-form.tsx +++ b/apps/craft-of-ui/src/app/(content)/posts/_components/post-video-subscribe-form.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useRouter } from 'next/navigation' -import { redirectUrlBuilder, SubscribeToConvertkitForm } from '@/convertkit' +import { SubscribeToConvertkitForm } from '@/convertkit' import { Subscriber } from '@/schemas/subscriber' import common from '@/text/common' import { api } from '@/trpc/react' @@ -41,8 +41,7 @@ export const PostNewsletterCta: React.FC< const handleOnSuccess = (subscriber: Subscriber | undefined) => { if (subscriber) { track(trackProps.event as string, trackProps.params) - const redirectUrl = redirectUrlBuilder(subscriber, '/confirm') - router.push(redirectUrl) + // No redirect - let the form handle inline success response } } diff --git a/apps/craft-of-ui/src/app/layout.tsx b/apps/craft-of-ui/src/app/layout.tsx index 5044df5a4..4e18d2ea5 100644 --- a/apps/craft-of-ui/src/app/layout.tsx +++ b/apps/craft-of-ui/src/app/layout.tsx @@ -74,7 +74,7 @@ export default function RootLayout({ diff --git a/apps/craft-of-ui/src/app/page.tsx b/apps/craft-of-ui/src/app/page.tsx index 4691f55eb..09f0441d3 100644 --- a/apps/craft-of-ui/src/app/page.tsx +++ b/apps/craft-of-ui/src/app/page.tsx @@ -6,6 +6,7 @@ import { SubscribeForm, Testimonial, } from '@/app/admin/pages/_components/page-builder-mdx-components' +import { BearAnimation } from '@/components/bear-animation' import { CldImage, ThemeImage } from '@/components/cld-image' import MDXVideo from '@/components/content/mdx-video' import { JheyProfile } from '@/components/jhey-profile' @@ -89,14 +90,16 @@ const Home = async (props: Props) => { withContainer withGrid > -
-
-
-

+ + +
+
+
+

The Craft of UI — by  @@ -110,19 +113,17 @@ const Home = async (props: Props) => { />

-

+

What if you could build{' '} - anything? + anything?

p:not(:has(+ul))]:mb-8 [&_p]:text-gray-600 dark:[&_p]:text-gray-300', - '[&_ul]:mb-8 [&_ul]:mt-2 [&_ul]:flex [&_ul]:list-disc [&_ul]:flex-col [&_ul]:gap-y-2 [&_ul]:pl-6', - '[&_h2]:font-serif [&_h2]:text-3xl [&_h2]:font-normal [&_h2]:leading-none', - '[&_h2]:mb-4 [&_h2]:mt-20', + '[&_p]:text-md leading-[1.5]', + '[&_h2]:mb-4 [&_h2]:mt-20 [&_h2]:font-serif [&_h2]:font-[600] [&_h2]:leading-none [&_h2]:[--font-level:1.8] [&_h2]:[--font-size-min:20]', + '[&_p:not(:has(+ul))]:mb-8 [&_ul]:mb-8 [&_ul]:mt-2 [&_ul]:flex [&_ul]:list-disc [&_ul]:flex-col [&_ul]:gap-y-2 [&_ul]:pl-6', )} > {firstPageResource && ( @@ -138,6 +139,17 @@ const Home = async (props: Props) => { )} +

+ Master the tools, mindset, and techniques behind crafting + exceptional user interfaces with HTML, CSS, and JavaScript +

+ + +
{page?.fields?.body ? ( <> diff --git a/apps/craft-of-ui/src/components/bear-animation.tsx b/apps/craft-of-ui/src/components/bear-animation.tsx new file mode 100644 index 000000000..11c3709a0 --- /dev/null +++ b/apps/craft-of-ui/src/components/bear-animation.tsx @@ -0,0 +1,238 @@ +'use client' + +import * as React from 'react' +import { useEffect, useRef } from 'react' + +/** + * BearAnimation component that creates floating bear animations on subscription success + * Implements the complete balloon bear animation system with random colors, positions, and durations + */ +export const BearAnimation: React.FC = () => { + const bearCaveRef = useRef(null) + const bearTemplateRef = useRef(null) + + /** + * Creates a new floating bear animation + * @param subscriber - The subscriber data from successful subscription + */ + const createFloatingBear = React.useCallback((subscriber: any) => { + if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) { + const successBear = bearTemplateRef.current?.cloneNode( + true, + ) as HTMLElement + + if (successBear && bearCaveRef.current) { + const randomHue = Math.random() * 359 + const randomX = Math.random().toFixed(2) + const randomDuration = (10 + Math.random() * 20).toFixed(2) + + successBear.style.setProperty('--accent', `hsl(${randomHue} 90% 80%)`) + successBear.style.setProperty('--x', randomX) + successBear.style.setProperty('--duration', `${randomDuration}s`) + + const svg = successBear.querySelector('svg') + if (svg) { + svg.style.setProperty('--accent', `hsl(${randomHue} 90% 80%)`) + } + + successBear.classList.remove('sr-only') + successBear.style.animation = `float ${randomDuration}s linear 2s forwards` + + const balloonButton = successBear.querySelector('button') + if (balloonButton) { + balloonButton.addEventListener('click', () => { + // Play pop sound + const pop = new Audio('/audio/pop.mp3') + pop.play() + + const balloon = successBear.querySelector('.balloon') + if (balloon) { + // Balloon pop animation + balloon.animate( + { + scale: [2], + opacity: [0], + }, + { + fill: 'forwards', + duration: 75, + easing: 'ease-out', + }, + ) + + successBear.animate( + { + translate: ['0 100%'], + }, + { + composite: 'replace', + duration: 200, + easing: 'linear', + }, + ).onfinish = () => { + successBear.remove() + } + } + }) + } + + bearCaveRef.current.appendChild(successBear) + + setTimeout( + () => { + if (successBear.parentNode) { + successBear.remove() + } + }, + (parseFloat(randomDuration) + 2) * 1000, + ) + } + } + }, []) + + useEffect(() => { + ;(window as any).createFloatingBear = createFloatingBear + + return () => { + delete (window as any).createFloatingBear + } + }, [createFloatingBear]) + + return ( + <> + {/* Bear Cave Container */} +
+ + {/* Bear Template (Hidden) */} +
+ + + {/* Bear SVG with Balloon */} + +
+ + ) +} diff --git a/apps/craft-of-ui/src/components/jhey-profile.tsx b/apps/craft-of-ui/src/components/jhey-profile.tsx index f68621b9f..b397dfaaf 100644 --- a/apps/craft-of-ui/src/components/jhey-profile.tsx +++ b/apps/craft-of-ui/src/components/jhey-profile.tsx @@ -4,33 +4,31 @@ import Link from 'next/link' export function JheyProfile() { return ( -
-
- Hey – I'm Jhey Tompkins 🤙 -
+
+

Hey – I'm Jhey Tompkins 🤙

Jhey on stage at All Day Hey -
+

- I'm a web developer who loves making the web feel magical whilst + I’m a web developer who loves making the web feel magical whilst showing others how to do the same.

Currently, I'm a{' '} - Design Engineer at Vercel + Staff Design Engineer at Shopify . Before that I worked in{' '} CSS and UI {' '} - team on Chrome. Along the way, I've built for brands like Nike, - Uber, Nearform, and Monzo. + team on Chrome. I was also a Design Engineer at Vercel. Along the + way, I’ve built for brands like Nike, Uber, Nearform, and Monzo.

Over the years I've shared thousands of demos with the community @@ -64,7 +62,7 @@ export function JheyProfile() { > following on X {' '} - of over 120,000 people and 18,000 on{' '} + of over 130,000 people and 19,000 on{' '} CodePen {' '} - (of which many have asked for this course). It's given me the - opportunity to{' '} + (of which many have asked for this course). becoming the{' '} + + "Most Hearted" + {' '} + of all time. It's given me the opportunity to{' '}

– Jhey, the Craft of UI
diff --git a/apps/craft-of-ui/src/components/layout-client.tsx b/apps/craft-of-ui/src/components/layout-client.tsx index ab8bfff5b..a03034cac 100644 --- a/apps/craft-of-ui/src/components/layout-client.tsx +++ b/apps/craft-of-ui/src/components/layout-client.tsx @@ -28,6 +28,10 @@ export default function LayoutClient({ }) { return (
+
-
- - {children} -
-
+ {children} +
) diff --git a/apps/craft-of-ui/src/components/navigation/index.tsx b/apps/craft-of-ui/src/components/navigation/index.tsx index f8f317dc4..888beb6e5 100644 --- a/apps/craft-of-ui/src/components/navigation/index.tsx +++ b/apps/craft-of-ui/src/components/navigation/index.tsx @@ -12,7 +12,7 @@ import { useSession } from 'next-auth/react' import { Button } from '@coursebuilder/ui' import { useFeedback } from '@coursebuilder/ui/feedback-widget/feedback-context' -import { Logo } from '../brand/logo' +import { Logo, LogoMark } from '../brand/logo' import { MobileNavigation } from './mobile-navigation' import { NavLinkItem } from './nav-link-item' import { ThemeToggle } from './theme-toggle' @@ -52,7 +52,7 @@ const Navigation = ({ return (
- + + {links.length > 0 && (
) diff --git a/apps/craft-of-ui/src/components/video-block-newsletter-cta.tsx b/apps/craft-of-ui/src/components/video-block-newsletter-cta.tsx index 5ac52e045..46e8f0485 100644 --- a/apps/craft-of-ui/src/components/video-block-newsletter-cta.tsx +++ b/apps/craft-of-ui/src/components/video-block-newsletter-cta.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useRouter } from 'next/navigation' -import { redirectUrlBuilder, SubscribeToConvertkitForm } from '@/convertkit' +import { SubscribeToConvertkitForm } from '@/convertkit' import { Subscriber } from '@/schemas/subscriber' import { track } from '@/utils/analytics' import { @@ -44,8 +44,7 @@ export const VideoBlockNewsletterCta: React.FC< const handleOnSuccess = (subscriber: Subscriber | undefined) => { if (subscriber) { track(trackProps.event as string, trackProps.params) - const redirectUrl = redirectUrlBuilder(subscriber, '/confirm') - router.push(redirectUrl) + // No redirect - let the form handle inline success response } } diff --git a/apps/craft-of-ui/src/convertkit/convertkit-subscribe-form.tsx b/apps/craft-of-ui/src/convertkit/convertkit-subscribe-form.tsx index 834453198..01a32047a 100644 --- a/apps/craft-of-ui/src/convertkit/convertkit-subscribe-form.tsx +++ b/apps/craft-of-ui/src/convertkit/convertkit-subscribe-form.tsx @@ -1,9 +1,11 @@ -import React from 'react' +import React, { useEffect, useRef, useState } from 'react' import Spinner from '@/components/spinner' import { useConvertkitForm } from '@/hooks/use-convertkit-form' import { type Subscriber } from '@/schemas/subscriber' import { api } from '@/trpc/react' +import { cn } from '@/utils/cn' import { CK_SUBSCRIBER_KEY } from '@skillrecordings/config' +import { LockIcon, ShieldCheckIcon } from 'lucide-react' import queryString from 'query-string' import * as Yup from 'yup' @@ -97,6 +99,7 @@ export const SubscribeToConvertkitForm: React.FC< showMascot, ...rest }) => { + const [isEmailFocused, setIsEmailFocused] = useState(false) const { isSubmitting, status, @@ -115,176 +118,323 @@ export const SubscribeToConvertkitForm: React.FC< validateOnChange, }) + // Check if bear should show: focused AND has content + const shouldShowBear = + isEmailFocused && values.email && values.email.trim().length > 0 + return ( -
-
- - - {validationSchema && touched.first_name && errors.first_name && ( -

{errors.first_name}

- )} -
-
- -
- {showMascot && ( -

+ You want to build exceptional user interfaces, I want to empower you + to do so. +
+ + Join the waitlist to learn more and get course launch updates. + +

+ {/* Hide form on success */} + {status !== 'success' && ( + +
- - - - - - - - - - )} - -
- {validationSchema && touched.email && errors.email && ( -

{errors.email}

+ + + +
+ {/* Mascot bear - peeks out when email input is focused */} +
+ +
+ { + setIsEmailFocused(true) + onEmailFocus?.() + }} + onBlur={() => { + setIsEmailFocused(false) + onEmailBlur?.() + }} + /> +
+ +
+ + )} + {/* Success message */} + {status === 'success' && ( +
+

+ + Thanks{values.first_name ? ` ${values.first_name}` : ''}! + +
+ Please check your email to confirm your subscription. +

+
+ )} + {!formId && status !== 'success' && ( +

+ I respect your privacy. + Unsubscribe at any time. +

)}
- {submitButtonElem ? ( - React.cloneElement(submitButtonElem, { - type: 'submit', - disabled: Boolean(isSubmitting), - children: isSubmitting ? ( - - ) : ( - submitButtonElem.props.children - ), - }) - ) : ( - - )} - {status === 'success' && - (React.isValidElement(successMessage) ? ( - successMessage - ) : ( -

{successMessage}

- ))} - {status === 'error' && - (React.isValidElement(errorMessage) ? ( - errorMessage - ) : ( -

{errorMessage}

- ))} - + {isSubmitting ?

loading...

: null} +
) } export default SubscribeToConvertkitForm - -export const redirectUrlBuilder = ( - subscriber: Subscriber, - path: string, - queryParams?: { - [key: string]: string - }, -) => { - const url = queryString.stringifyUrl({ - url: path, - query: { - [CK_SUBSCRIBER_KEY]: subscriber.id, - email: subscriber.email_address, - ...queryParams, - }, - }) - return url -} diff --git a/apps/craft-of-ui/src/convertkit/index.ts b/apps/craft-of-ui/src/convertkit/index.ts index dac61bfe3..ae5866dcf 100644 --- a/apps/craft-of-ui/src/convertkit/index.ts +++ b/apps/craft-of-ui/src/convertkit/index.ts @@ -1,5 +1,3 @@ -import SubscribeToConvertkitForm, { - redirectUrlBuilder, -} from './convertkit-subscribe-form' +import SubscribeToConvertkitForm from './convertkit-subscribe-form' -export { SubscribeToConvertkitForm, redirectUrlBuilder } +export { SubscribeToConvertkitForm } diff --git a/apps/craft-of-ui/src/styles/globals.css b/apps/craft-of-ui/src/styles/globals.css index f29a884bf..1d292cb8f 100644 --- a/apps/craft-of-ui/src/styles/globals.css +++ b/apps/craft-of-ui/src/styles/globals.css @@ -28,7 +28,7 @@ @layer base { :root { - --nav-height: 5rem; + --nav-height: 80px; --command-bar-height: 2.25rem; --pane-layout-height: calc( 100vh - var(--nav-height) - var(--command-bar-height) @@ -36,7 +36,7 @@ --code-editor-layout-height: calc( 100vh - var(--nav-height) - var(--command-bar-height) - 30px ); - --background: 0 0% 93%; + --background: 0 0% 98%; --foreground: 240 2% 20%; --card: 0 0% 100%; --card-foreground: 240 2% 20%; @@ -63,6 +63,34 @@ --chart-5: 27 87% 67%; color-scheme: light; + /* imported */ + --header-height: 80px; + --content: 600px; + --gutter: 1.5rem; + --font-size-min: 14; + --font-size-max: 18; + --font-ratio-min: 1.2; + --font-ratio-max: 1.33; + --font-width-min: 375; + --font-width-max: 1500; + --breakpoint-lg: 968px; + --color-red-400: oklch(70.4% 0.191 22.216); + --color-red-500: #ef4444; + --color-red-600: #ef4444; + --color-red-800: #991b1b; + --red-600: #ef4444; + --color-green-200: oklch(0.925 0.084 155.995); + --color-green-600: oklch(0.627 0.194 149.214); + --color-green-800: oklch(0.448 0.119 151.328); + --color-gray-100: oklch(0.967 0.003 264.542); + --color-gray-200: oklch(0.928 0.006 264.531); + --color-gray-300: oklch(0.872 0.01 258.338); + --color-gray-400: oklch(0.707 0.022 261.325); + --color-gray-500: oklch(0.551 0.027 264.364); + --color-gray-800: oklch(0.278 0.033 256.848); + --color-gray-900: oklch(0.21 0.034 264.665); + --color-white: #fff; + /* codehike theme */ --ch-0: light; --ch-1: #6e7781; @@ -176,13 +204,19 @@ } body { - @apply bg-background text-foreground selection:bg-primary selection:text-primary-foreground overflow-x-hidden font-normal antialiased; + background: color-mix(in hsl, #fff, hsl(45 80% 50%) 1%); + @apply selection:bg-primary selection:text-primary-foreground overflow-x-hidden font-[300] text-[canvasText] antialiased; + color: color-mix(in hsl, canvasText, canvas 40%); font-feature-settings: 'rlig' 1, 'calt' 1; } - .home-page-grid::before { + .dark body { + background: hsl(210 10% 10%); + } + + body::before { --size: 40px; --line: color-mix(in hsl, canvasText, transparent 90%); content: ''; @@ -201,10 +235,76 @@ z-index: -1; } + .text-fluid { + --fluid-min: calc( + var(--font-size-min) * pow(var(--font-ratio-min), var(--font-level, 0)) + ); + --fluid-max: calc( + var(--font-size-max) * pow(var(--font-ratio-max), var(--font-level, 0)) + ); + --fluid-preferred: calc( + (var(--fluid-max) - var(--fluid-min)) / + (var(--font-width-max) - var(--font-width-min)) + ); + --fluid-type: clamp( + (var(--fluid-min) / 16) * 1rem, + ((var(--fluid-min) / 16) * 1rem) - + (((var(--fluid-preferred) * var(--font-width-min)) / 16) * 1rem) + + (var(--fluid-preferred) * var(--variable-unit, 100vi)), + (var(--fluid-max) / 16) * 1rem + ); + font-size: var(--fluid-type); + } + .mask-content { + mask-image: + linear-gradient( + #0000 var(--start), + #fff var(--end) calc(100% - var(--header-height)), + #0000 + ), + linear-gradient(#fff 0 100%); + mask-position: + 0 50%, + calc(100vw - var(--gutter)) 0; + mask-size: calc(100vw - var(--gutter)) 100%; + mask-repeat: no-repeat; + } + html { /* scrollbar-gutter: stable; */ } + :where(a, button):focus-visible { + outline: 2px solid var(--color-red-400); + outline-offset: 2px; + } + + strong { + @apply font-[600]; + } + + ::selection { + background: oklch(70.4% 0.191 22.216) !important; + color: #fff !important; + } + + ::-moz-selection { + background: oklch(70.4% 0.191 22.216) !important; + color: #fff !important; + } + + html[lang] { + color-scheme: light dark; + scrollbar-color: light-dark(var(--color-red-400), var(--color-red-400)) + #0000; + scrollbar-width: thin; + } + @media (prefers-reduced-motion: no-preference) { + .overflow-auto { + scroll-behavior: smooth; + } + } + html.dark .shiki, html.dark .shiki span { color: var(--shiki-dark) !important; @@ -215,7 +315,7 @@ text-decoration: var(--shiki-dark-text-decoration) !important; } - /* + /* Hide the second #primary-newsletter-cta only if there are exactly two on the page. This is a temporary workaround for duplicate IDs rendered by the framework. Ideally, IDs should be unique—consider refactoring to use class names or unique IDs. @@ -235,3 +335,108 @@ list-style-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNCAxNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0IDMuMTVMMTMuMyAxLjc1QzcuMjYyIDMuNSA0LjIgNy4zNSA0LjIgNy4zNUwxLjQgNS4yNUwwIDYuNjVMNC4yIDEyLjI1QzcuNDM3IDYuMjEzIDE0IDMuMTUgMTQgMy4xNVoiIGZpbGw9IiM3QzNCRUQiLz4KPC9zdmc+Cg=='); } } + +@layer signature { + .sig { + --duration: 1.4; + --delay: 0.2; + --base-delay: calc((var(--duration) + var(--delay))); + --natty-delay: 0.973125; + width: 160px; + rotate: 10deg; + path { + --end: 1.025; + stroke-dasharray: var(--end); + stroke-dashoffset: var(--end); + animation: draw calc(var(--path-speed) * 1s) + calc((var(--base-delay) * 1s) + var(--path-delay, 0) * 1s) ease-in + forwards; + } + + :is(.eye, .nose) { + fill: hsl(0 0% 0% / 0); + animation: + draw calc(var(--path-speed) * 1s) + calc((var(--base-delay) * 1s) + var(--path-delay, 0) * 1s) ease-in + forwards, + fill 0.5s calc((var(--base-delay) * 1s) + var(--natty-delay, 0) * 1s) + forwards; + } + .eye { + transform-box: fill-box; + transform-origin: 50% 50%; + animation: + draw calc(var(--path-speed) * 1s) + calc((var(--base-delay) * 1s) + var(--path-delay, 0) * 1s) ease-in + forwards, + fill 0.5s calc((var(--base-delay) * 1s) + var(--natty-delay, 0) * 1s) + forwards, + blink 6s calc((var(--base-delay) * 1s) + var(--natty-delay, 0) * 1s) + infinite; + } + } + + @keyframes fill { + to { + fill: currentColor; + } + } + + @keyframes draw { + to { + stroke-dashoffset: 0; + } + } + + @keyframes blink { + 0%, + 46%, + 48%, + 50%, + 100% { + transform: scaleY(1); + } + 47%, + 49% { + transform: scaleY(0.1); + } + } +} + +@keyframes move { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(100%); + } +} + +@keyframes float { + to { + translate: 0 calc(-100% + -100vh); + } +} + +@keyframes bear-float-up { + 0% { + transform: translateY(100%); + opacity: 1; + } + 80% { + opacity: 1; + } + 100% { + transform: translateY(-200vh); + opacity: 0; + } +} + +.animate-bear-float-up { + animation: bear-float-up 10s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +/* Ensure red button background is applied correctly */ +.bg-red-600 { + background-color: var(--red-600, oklch(0.637 0.237 25.331)); +} diff --git a/apps/craft-of-ui/src/styles/primary-newsletter-cta.css b/apps/craft-of-ui/src/styles/primary-newsletter-cta.css index 2b5bdf286..c2caa5aca 100644 --- a/apps/craft-of-ui/src/styles/primary-newsletter-cta.css +++ b/apps/craft-of-ui/src/styles/primary-newsletter-cta.css @@ -5,8 +5,8 @@ @apply mb-0; } input { - @apply placeholder:text-muted-foreground text-foreground relative flex p-5 text-base placeholder:opacity-75; - @apply before:bg-linear-to-r before:absolute before:left-0 before:top-0 before:h-px before:w-full before:from-transparent before:via-white before:to-transparent; + @apply placeholder:text-muted-foreground text-foreground relative flex text-base placeholder:opacity-75; + @apply before:absolute before:left-0 before:top-0 before:h-px before:w-full before:bg-gradient-to-r before:from-transparent before:via-white before:to-transparent; } [data-sr-input-label] { @apply text-muted-foreground mt-3 inline-block pb-1 text-base font-normal after:content-[':']; diff --git a/apps/craft-of-ui/tailwind.config.ts b/apps/craft-of-ui/tailwind.config.ts index db92b3a74..bac65f9d2 100644 --- a/apps/craft-of-ui/tailwind.config.ts +++ b/apps/craft-of-ui/tailwind.config.ts @@ -69,6 +69,17 @@ module.exports = { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))', }, + red: { + 400: '#f87171', + 500: '#ef4444', + 600: '#ef4444', + 800: '#991b1b', + }, + green: { + 200: 'var(--color-green-200)', + 600: 'var(--color-green-600)', + 800: 'var(--color-green-800)', + }, }, borderRadius: { lg: `var(--radius)`, @@ -93,11 +104,41 @@ module.exports = { '0%': { 'background-position': '100%' }, '100%': { 'background-position': '-100%' }, }, + // Add missing animations that might be used + blink: { + '0%, 46%, 48%, 50%, 100%': { transform: 'scaleY(1)' }, + '47%, 49%': { transform: 'scaleY(0.1)' }, + }, + draw: { + to: { strokeDashoffset: 0 }, + }, + fill: { + to: { fill: 'currentColor' }, + }, + move: { + '0%': { transform: 'translateX(0)' }, + '100%': { transform: 'translateX(100%)' }, + }, + 'bear-float-up': { + '0%': { transform: 'translateY(100%)', opacity: 1 }, + '80%': { opacity: 1 }, + '100%': { transform: 'translateY(-200vh)', opacity: 0 }, + }, + float: { + to: { translate: '0 calc(-100% + -100vh)' }, + }, }, animation: { 'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out', shine: 'shine 5s linear infinite', + blink: 'blink 6s infinite', + draw: 'draw var(--path-speed, 1s) ease-in forwards', + fill: 'fill 0.5s forwards', + move: 'move 1s ease-in-out', + 'bear-float-up': + 'bear-float-up 10s cubic-bezier(0.4, 0, 0.2, 1) forwards', + float: 'float var(--duration, 15s) linear 2s forwards', }, typography: (theme: any) => ({ DEFAULT: {