Skip to content

Commit

Permalink
feat(website): add easter egg
Browse files Browse the repository at this point in the history
  • Loading branch information
guilhermerodz committed Feb 19, 2024
1 parent 1f2d88f commit 8160411
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 60 deletions.
61 changes: 55 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"geist": "^1.2.2",
"input-otp": "workspace:*",
"lucide-react": "^0.330.0",
"next": "14.1.0",
"input-otp": "workspace:*",
"next-themes": "^0.2.1",
"react": "^18",
"react-canvas-confetti": "^2.0.5",
"react-dom": "^18",
"react-hook-form": "^7.50.1",
"rehype-pretty-code": "^0.13.0",
"rehype-stringify": "^10.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.0",
"sonner": "^1.4.0",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.4"
Expand Down
3 changes: 3 additions & 0 deletions website/src/app/(local-pages)/example-playground/code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ export function ExampleCode() {
<Code code={code} toCopy={tsx} />

<div className="absolute inset-0 code-example-overlay pointer-events-none z-20 [animation-delay:5000ms]"></div>

{/* Anchor */}
<div className="code-example-anchor absolute pointer-events-none w-px h-px -top-[5.5rem]" />
</div>
)
}
5 changes: 5 additions & 0 deletions website/src/app/(pages)/(home)/_components/confetti.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Fireworks from 'react-canvas-confetti/dist/presets/fireworks'

export function Confetti() {
return <Fireworks autorun={{ speed: 1 }} />
}
45 changes: 45 additions & 0 deletions website/src/app/(pages)/(home)/_components/confetti_.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useCallback, useRef } from 'react'
import ReactCanvasConfetti from 'react-canvas-confetti'

function randomInRange(min: number, max: number) {
return Math.random() * (max - min) + min
}

const canvasStyles: React.CSSProperties = {
zIndex: 9999,
position: 'fixed',
pointerEvents: 'none',
width: '100%',
height: '100%',
top: 0,
left: 0,
}

function getAnimationSettings(originXA: number, originXB: number) {
return {
startVelocity: 30,
spread: 360,
ticks: 60,
zIndex: 0,
particleCount: 150,
origin: {
x: randomInRange(originXA, originXB),
y: Math.random() - 0.2,
},
}
}

export default function Confetti() {
const done = useRef(false)

const getInstance = useCallback((fire: any) => {
if (fire && !done.current) {
fire(getAnimationSettings(0.1, 0.3))
fire(getAnimationSettings(0.7, 0.9))

done.current = true
}
}, [])

return <ReactCanvasConfetti refConfetti={getInstance} style={canvasStyles} />
}
154 changes: 101 additions & 53 deletions website/src/app/(pages)/(home)/_components/showcase.tsx
Original file line number Diff line number Diff line change
@@ -1,82 +1,130 @@
'use client'

import React from 'react'
import { toast } from 'sonner'
import dynamic from 'next/dynamic'

import { cn } from '@/lib/utils'
import { OTPInput, REGEXP_ONLY_DIGITS } from 'input-otp'
import { cn } from '@/lib/utils'

const DynamicConfetti = dynamic(() =>
import('./confetti').then(m => m.Confetti),
)

export function Showcase({ className, ...props }: { className?: string }) {
const [value, setValue] = React.useState('12')
const [disabled, setDisabled] = React.useState(true)

const [preloadConfetti, setPreloadConfetti] = React.useState(0)
const [hasGuessed, setHasGuessed] = React.useState(false)

const inputRef = React.useRef<HTMLInputElement>(null)

React.useEffect(() => {
const t = setTimeout(() => {
const t1 = setTimeout(() => {
setDisabled(false)
}, 2_400)
const t2 = setTimeout(() => {
inputRef.current?.focus()
}, 2000)
}, 2_500)

return () => {
clearTimeout(t)
clearTimeout(t1)
clearTimeout(t2)
}
}, [])

function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
React.useEffect(() => {
if (value.length > 3) {
setPreloadConfetti(p => p+1)
}
}, [value.length])

async function onSubmit(e?: React.FormEvent<HTMLFormElement>) {
await new Promise(r => setTimeout(r, 5_00))

e?.preventDefault?.()

inputRef.current?.blur()

if (value === '123456') {
// Easter egg...
return
setHasGuessed(true)

setTimeout(() => {
setHasGuessed(false)
}, 1_000)
} else {
toast("Try guessing the right password 🤔", { position: 'top-right' })
}

const firstDemoInput =
document.querySelector<HTMLInputElement>('#first-demo-input')
firstDemoInput!.focus()
const anchor = document.querySelector<HTMLInputElement>(
'.code-example-anchor',
)

window.scrollTo({
top: anchor?.getBoundingClientRect().top,
behavior: 'smooth',
})

setValue('')
}

return (
<form
className={cn(
'mx-auto flex max-w-[980px] justify-center pt-6 pb-4',
className,
<>
{preloadConfetti === 1 && <div className="hidden">
<DynamicConfetti />
</div>}
{hasGuessed && (
<div className="fixed inset-0 z-50 pointer-events-none motion-reduce:hidden">
<DynamicConfetti />
</div>
)}
onSubmit={onSubmit}
>
<OTPInput
ref={inputRef}
value={value}
onChange={setValue}
containerClassName={cn(
'group flex items-center has-[:disabled]:opacity-30',
)}
maxLength={6}
allowNavigation={true}
pattern={REGEXP_ONLY_DIGITS}
render={({ slots, isFocused }) => (
<>
<div className="flex">
{slots.slice(0, 3).map((slot, idx) => (
<Slot
key={idx}
isFocused={isFocused}
animateIdx={idx}
{...slot}
/>
))}
</div>

{/* Layout inspired by Stripe */}
<div className="flex w-10 md:20 justify-center items-center">
<div className="w-3 md:w-6 h-1 md:h-2 rounded-full bg-border"></div>
</div>

<div className="flex">
{slots.slice(3).map((slot, idx) => (
<Slot isFocused={isFocused} key={idx} {...slot} />
))}
</div>
</>

<form
className={cn(
'mx-auto flex max-w-[980px] justify-center pt-6 pb-4',
className,
)}
/>
</form>
onSubmit={onSubmit}
>
<OTPInput
onComplete={onSubmit}
disabled={disabled}
ref={inputRef}
value={value}
onChange={setValue}
containerClassName={cn('group flex items-center')}
maxLength={6}
allowNavigation={true}
pattern={REGEXP_ONLY_DIGITS}
render={({ slots, isFocused }) => (
<>
<div className="flex">
{slots.slice(0, 3).map((slot, idx) => (
<Slot
key={idx}
isFocused={isFocused}
animateIdx={idx}
{...slot}
/>
))}
</div>

{/* Layout inspired by Stripe */}
<div className="flex w-10 md:20 justify-center items-center">
<div className="w-3 md:w-6 h-1 md:h-2 rounded-full bg-border"></div>
</div>

<div className="flex">
{slots.slice(3).map((slot, idx) => (
<Slot isFocused={isFocused} key={idx} {...slot} />
))}
</div>
</>
)}
/>
</form>
</>
)
}

Expand Down
Loading

0 comments on commit 8160411

Please sign in to comment.