finished landing

This commit is contained in:
itsMapleLeaf
2023-03-12 14:43:23 -05:00
parent 95041acfd4
commit 3969e6471f
13 changed files with 441 additions and 3 deletions

View File

@@ -30,6 +30,8 @@ module.exports = {
},
rules: {
"react/no-unknown-property": "off",
"react/jsx-key": "off",
"react/jsx-no-undef": "off",
},
},
],

View File

@@ -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()],
})

View 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">
&copy; {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>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
---
export type Props = astroHTML.JSX.AnchorHTMLProps
---
<a rel="noopener noreferrer" target="_blank" {...Astro.props}>
<slot />
</a>

View 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>
)
}

View 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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View 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;
}
}

View File

@@ -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")],
}