finished landing
This commit is contained in:
@@ -30,6 +30,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"react/no-unknown-property": "off",
|
"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"
|
import { defineConfig } from "astro/config"
|
||||||
|
|
||||||
export default defineConfig({
|
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 bannerUrl from "~/assets/banner.png"
|
||||||
import faviconUrl from "~/assets/favicon.png"
|
import faviconUrl from "~/assets/favicon.png"
|
||||||
import "~/styles/prism-theme.css"
|
import "~/styles/prism-theme.css"
|
||||||
|
import "~/styles/tailwind.css"
|
||||||
---
|
---
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!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"
|
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
|
// @ts-nocheck
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./app/**/*.{ts,tsx,md}"],
|
content: ["./src/**/*.{ts,tsx,md,astro}"],
|
||||||
theme: {
|
theme: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["RubikVariable", "sans-serif"],
|
sans: ["RubikVariable", "sans-serif"],
|
||||||
@@ -11,5 +11,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
corePlugins: {
|
||||||
|
container: false,
|
||||||
|
},
|
||||||
plugins: [require("@tailwindcss/typography")],
|
plugins: [require("@tailwindcss/typography")],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user