finished landing
This commit is contained in:
@@ -30,6 +30,8 @@ module.exports = {
|
||||
},
|
||||
rules: {
|
||||
"react/no-unknown-property": "off",
|
||||
"react/jsx-key": "off",
|
||||
"react/jsx-no-undef": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -3,5 +3,5 @@ import tailwind from "@astrojs/tailwind"
|
||||
import { defineConfig } from "astro/config"
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [tailwind(), react()],
|
||||
integrations: [tailwind({ config: { applyBaseStyles: false } }), react()],
|
||||
})
|
||||
|
||||
14
packages/website/src/components/app-footer.astro
Normal file
14
packages/website/src/components/app-footer.astro
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
import { HeartIcon } from "@heroicons/react/solid"
|
||||
import ExternalLink from "./external-link.astro"
|
||||
---
|
||||
|
||||
<footer class="container text-xs opacity-75">
|
||||
<address class="not-italic">
|
||||
© {new Date().getFullYear()} itsMapleLeaf
|
||||
</address>
|
||||
<p>
|
||||
Coded with <HeartIcon className="inline w-4 align-sub" /> using{" "}
|
||||
<ExternalLink class="link" href="https://astro.build">Astro</ExternalLink>
|
||||
</p>
|
||||
</footer>
|
||||
15
packages/website/src/components/app-logo.astro
Normal file
15
packages/website/src/components/app-logo.astro
Normal file
File diff suppressed because one or more lines are too long
7
packages/website/src/components/external-link.astro
Normal file
7
packages/website/src/components/external-link.astro
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
export type Props = astroHTML.JSX.AnchorHTMLProps
|
||||
---
|
||||
|
||||
<a rel="noopener noreferrer" target="_blank" {...Astro.props}>
|
||||
<slot />
|
||||
</a>
|
||||
195
packages/website/src/components/landing-animation.tsx
Normal file
195
packages/website/src/components/landing-animation.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import clsx from "clsx"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import blobComfyUrl from "~/assets/blob-comfy.png"
|
||||
import cursorIbeamUrl from "~/assets/cursor-ibeam.png"
|
||||
import cursorUrl from "~/assets/cursor.png"
|
||||
|
||||
const defaultState = {
|
||||
chatInputText: "",
|
||||
chatInputCursorVisible: true,
|
||||
messageVisible: false,
|
||||
count: 0,
|
||||
cursorLeft: "25%",
|
||||
cursorBottom: "-15px",
|
||||
}
|
||||
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const animationFrame = () =>
|
||||
new Promise((resolve) => requestAnimationFrame(resolve))
|
||||
|
||||
export function LandingAnimation() {
|
||||
const [state, setState] = useState(defaultState)
|
||||
const chatInputRef = useRef<HTMLDivElement>(null)
|
||||
const addRef = useRef<HTMLDivElement>(null)
|
||||
const deleteRef = useRef<HTMLDivElement>(null)
|
||||
const cursorRef = useRef<HTMLImageElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const animateClick = (element: HTMLElement) =>
|
||||
element.animate(
|
||||
[{ transform: `translateY(2px)` }, { transform: `translateY(0px)` }],
|
||||
300,
|
||||
)
|
||||
|
||||
let running = true
|
||||
|
||||
void (async () => {
|
||||
while (running) {
|
||||
setState(defaultState)
|
||||
await delay(700)
|
||||
|
||||
for (const letter of "/counter") {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
chatInputText: state.chatInputText + letter,
|
||||
}))
|
||||
await delay(100)
|
||||
}
|
||||
|
||||
await delay(1000)
|
||||
|
||||
setState((state) => ({
|
||||
...state,
|
||||
messageVisible: true,
|
||||
chatInputText: "",
|
||||
}))
|
||||
await delay(1000)
|
||||
|
||||
setState((state) => ({
|
||||
...state,
|
||||
cursorLeft: "70px",
|
||||
cursorBottom: "40px",
|
||||
}))
|
||||
await delay(1500)
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
count: state.count + 1,
|
||||
chatInputCursorVisible: false,
|
||||
}))
|
||||
animateClick(addRef.current!)
|
||||
await delay(700)
|
||||
}
|
||||
|
||||
await delay(500)
|
||||
|
||||
setState((state) => ({
|
||||
...state,
|
||||
cursorLeft: "140px",
|
||||
}))
|
||||
await delay(1000)
|
||||
|
||||
animateClick(deleteRef.current!)
|
||||
setState((state) => ({ ...state, messageVisible: false }))
|
||||
await delay(1000)
|
||||
|
||||
setState(() => ({
|
||||
...defaultState,
|
||||
chatInputCursorVisible: false,
|
||||
}))
|
||||
await delay(500)
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
running = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
let running = true
|
||||
|
||||
void (async () => {
|
||||
while (running) {
|
||||
// check if the cursor is in the input
|
||||
const cursorRect = cursorRef.current!.getBoundingClientRect()
|
||||
const chatInputRect = chatInputRef.current!.getBoundingClientRect()
|
||||
|
||||
const isOverInput =
|
||||
cursorRef.current &&
|
||||
chatInputRef.current &&
|
||||
cursorRect.top + cursorRect.height / 2 > chatInputRect.top
|
||||
|
||||
cursorRef.current!.src = isOverInput ? cursorIbeamUrl : cursorUrl
|
||||
|
||||
await animationFrame()
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
running = false
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className="grid gap-2 relative pointer-events-none select-none"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-slate-800 p-4 rounded-lg shadow transition",
|
||||
state.messageVisible ? "opacity-100" : "opacity-0 -translate-y-2",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 p-2 rounded-full bg-no-repeat bg-contain bg-black/25">
|
||||
<img
|
||||
src={blobComfyUrl}
|
||||
alt=""
|
||||
className="object-contain scale-90 w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-bold">comfybot</p>
|
||||
<p>this button was clicked {state.count} times</p>
|
||||
<div className="mt-2 flex flex-row gap-3">
|
||||
<div
|
||||
ref={addRef}
|
||||
className="bg-emerald-700 text-white py-1.5 px-3 text-sm rounded"
|
||||
>
|
||||
+1
|
||||
</div>
|
||||
<div
|
||||
ref={deleteRef}
|
||||
className="bg-red-700 text-white py-1.5 px-3 text-sm rounded"
|
||||
>
|
||||
🗑 delete
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bg-slate-700 pb-2 pt-1.5 px-4 rounded-lg shadow"
|
||||
ref={chatInputRef}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
"text-sm after:content-[attr(data-after)] after:relative after:-top-px after:-left-[2px]",
|
||||
state.chatInputCursorVisible
|
||||
? "after:opacity-100"
|
||||
: "after:opacity-0",
|
||||
)}
|
||||
data-after="|"
|
||||
>
|
||||
{state.chatInputText || (
|
||||
<span className="opacity-50 block absolute translate-y-1">
|
||||
Message #showing-off-reacord
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src={cursorUrl}
|
||||
alt=""
|
||||
className="transition-all duration-500 absolute scale-75 bg-transparent"
|
||||
style={{ left: state.cursorLeft, bottom: state.cursorBottom }}
|
||||
ref={cursorRef}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
69
packages/website/src/components/main-navigation.astro
Normal file
69
packages/website/src/components/main-navigation.astro
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
import { MenuIcon } from "@heroicons/react/outline"
|
||||
import {
|
||||
CodeIcon,
|
||||
DocumentTextIcon,
|
||||
ExternalLinkIcon,
|
||||
} from "@heroicons/react/solid"
|
||||
import AppLogo from "./app-logo.astro"
|
||||
import ExternalLink from "./external-link.astro"
|
||||
import MenuItem from "./menu-item.astro"
|
||||
import Menu from "./menu.astro"
|
||||
|
||||
const links = [
|
||||
{
|
||||
href: "/guides/getting-started",
|
||||
label: "Guides",
|
||||
icon: DocumentTextIcon,
|
||||
component: "a",
|
||||
},
|
||||
{
|
||||
href: "/api/",
|
||||
label: "API Reference",
|
||||
icon: CodeIcon,
|
||||
component: "a",
|
||||
},
|
||||
{
|
||||
href: "https://github.com/itsMapleLeaf/reacord",
|
||||
label: "GitHub",
|
||||
icon: ExternalLinkIcon,
|
||||
component: ExternalLink,
|
||||
},
|
||||
]
|
||||
---
|
||||
|
||||
<nav class="flex justify-between items-center h-16">
|
||||
<a href="/">
|
||||
<AppLogo class="w-32" />
|
||||
<span class="sr-only">Home</span>
|
||||
</a>
|
||||
<div class="hidden md:flex gap-4">
|
||||
{
|
||||
links.map((link) => (
|
||||
<link.component
|
||||
href={link.href}
|
||||
class="link inline-flex gap-1 items-center"
|
||||
>
|
||||
<link.icon className="inline-icon" />
|
||||
{link.label}
|
||||
</link.component>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<Menu>
|
||||
<Fragment slot="button">
|
||||
<MenuIcon className="w-6" />
|
||||
<span class="sr-only">Menu</span>
|
||||
</Fragment>
|
||||
{
|
||||
links.map((link) => (
|
||||
<link.component href={link.href}>
|
||||
<MenuItem icon={link.icon} label={link.label} />
|
||||
</link.component>
|
||||
))
|
||||
}
|
||||
<hr class="border-black/25" />
|
||||
<!-- TODO: guide links -->
|
||||
</Menu>
|
||||
</nav>
|
||||
13
packages/website/src/components/menu-item.astro
Normal file
13
packages/website/src/components/menu-item.astro
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
export type Props = {
|
||||
icon: (props: { class?: string; className?: string }) => any
|
||||
label: string
|
||||
}
|
||||
---
|
||||
|
||||
<div
|
||||
class="px-3 py-2 transition text-left font-medium block w-full opacity-50 inline-flex gap-1 items-center"
|
||||
>
|
||||
<Astro.props.icon class="inline-icon" className="inline-icon" />
|
||||
{Astro.props.label}
|
||||
</div>
|
||||
28
packages/website/src/components/menu.astro
Normal file
28
packages/website/src/components/menu.astro
Normal file
@@ -0,0 +1,28 @@
|
||||
<details class="md:hidden relative" data-menu>
|
||||
<summary class="list-none p-2 -m-2">
|
||||
<slot name="button" />
|
||||
</summary>
|
||||
<div
|
||||
class="w-48 max-h-[calc(100vh-5rem)] bg-slate-800 shadow rounded-lg overflow-x-hidden overflow-y-auto top-[calc(100%+8px)] right-0 absolute"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<script>
|
||||
for (const menu of document.querySelectorAll<HTMLDetailsElement>(
|
||||
"[data-menu]",
|
||||
)) {
|
||||
window.addEventListener("click", (event) => {
|
||||
if (!menu.contains(event.target as Node)) {
|
||||
menu.open = false
|
||||
}
|
||||
})
|
||||
menu.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
menu.open = false
|
||||
menu.querySelector("summary")!.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -5,6 +5,7 @@ import packageJson from "reacord/package.json"
|
||||
import bannerUrl from "~/assets/banner.png"
|
||||
import faviconUrl from "~/assets/favicon.png"
|
||||
import "~/styles/prism-theme.css"
|
||||
import "~/styles/tailwind.css"
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
@@ -1,5 +1,54 @@
|
||||
---
|
||||
import dotsBackgroundUrl from "~/assets/dots-background.svg"
|
||||
import AppFooter from "~/components/app-footer.astro"
|
||||
import AppLogo from "~/components/app-logo.astro"
|
||||
import { LandingAnimation } from "~/components/landing-animation"
|
||||
import MainNavigation from "~/components/main-navigation.astro"
|
||||
import Layout from "~/layout.astro"
|
||||
---
|
||||
|
||||
<Layout>content!</Layout>
|
||||
<Layout>
|
||||
<div
|
||||
class="fixed inset-0 rotate-6 scale-125 opacity-20"
|
||||
style={{ backgroundImage: `url(${dotsBackgroundUrl})` }}
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col relative min-w-0 min-h-screen pb-4 gap-4">
|
||||
<header class="container">
|
||||
<MainNavigation />
|
||||
</header>
|
||||
<div class="flex flex-col gap-4 my-auto px-4">
|
||||
<AppLogo class="w-full max-w-lg mx-auto" />
|
||||
|
||||
<div class="max-w-md w-full mx-auto">
|
||||
<LandingAnimation client:only />
|
||||
</div>
|
||||
|
||||
<p class="text-center text-lg font-light -mb-1">
|
||||
Create interactive Discord messages with React.
|
||||
</p>
|
||||
|
||||
<div class="flex gap-4 self-center">
|
||||
<a href="/guides/getting-started" class="button button-solid">
|
||||
Get Started
|
||||
</a>
|
||||
|
||||
<!-- <UncontrolledModal
|
||||
button={(button) => (
|
||||
<button {...button} class={buttonClass({ variant: "semiblack" })}>
|
||||
Show Code
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
<div class="text-sm sm:text-base">
|
||||
<LandingCode />
|
||||
</div>
|
||||
</UncontrolledModal> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<AppFooter />
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
42
packages/website/src/styles/tailwind.css
Normal file
42
packages/website/src/styles/tailwind.css
Normal file
@@ -0,0 +1,42 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
:focus-visible {
|
||||
@apply ring-2 ring-emerald-500 ring-inset;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.container {
|
||||
@apply mx-auto w-full max-w-screen-lg px-4;
|
||||
}
|
||||
|
||||
.inline-icon {
|
||||
@apply inline w-5 align-sub;
|
||||
}
|
||||
|
||||
.link {
|
||||
@apply font-medium inline-block relative opacity-60 hover:opacity-100 transition-opacity;
|
||||
}
|
||||
.link::after {
|
||||
@apply content-[''] absolute block w-full h-px bg-current translate-y-[3px] opacity-0 transition;
|
||||
}
|
||||
.link:hover::after {
|
||||
@apply -translate-y-px opacity-50;
|
||||
}
|
||||
.link-active {
|
||||
@apply text-emerald-500;
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply inline-block mt-4 px-4 py-2.5 text-xl transition rounded-lg bg-black/25 hover:bg-black/40 hover:-translate-y-0.5 hover:shadow active:translate-y-0 active:transition-none;
|
||||
}
|
||||
.button-solid {
|
||||
@apply bg-emerald-700 hover:bg-emerald-800;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
module.exports = {
|
||||
content: ["./app/**/*.{ts,tsx,md}"],
|
||||
content: ["./src/**/*.{ts,tsx,md,astro}"],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ["RubikVariable", "sans-serif"],
|
||||
@@ -11,5 +11,8 @@ module.exports = {
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
corePlugins: {
|
||||
container: false,
|
||||
},
|
||||
plugins: [require("@tailwindcss/typography")],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user