Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 98 additions & 9 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,90 @@ import type * as Preset from "@docusaurus/preset-classic"
import prismTheme from "./src/theme/CodeBlock/theme"
import type {Config} from "@docusaurus/types"
import {getNavDropdownItemHtml} from "./src/utils"
import fs from "fs"
import path from "path"

const title = "Tailcall"
const organization = "tailcallhq"
const project = "tailcallhq.github.io"

const homeCriticalCss = `
*,::before,::after{box-sizing:border-box}
:root{--ifm-navbar-height:4.5rem}
html{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#121315;background:#fff}
body{margin:0;font-size:16px;line-height:1.5;background:#fff}
a{color:inherit;text-decoration:none}
img,svg{display:block;max-width:100%}
.skipToContent_fXgn{position:absolute;left:-999px}
.navbar{display:flex;align-items:center;min-height:4.5rem;width:100%;background:#fff;border-bottom:1px solid #e7e7e7;z-index:20}
.navbar__inner{display:flex;align-items:center;justify-content:space-between;width:100%;padding:0 1.25rem}
.navbar__items{display:flex;align-items:center;gap:1rem}
.navbar__items--right{margin-left:auto}
.navbar__brand{display:flex;align-items:center}
.navbar__logo{width:7.5rem;height:2.5rem;display:flex;align-items:center}
.navbar__logo img{height:2.25rem;width:auto}
.themedComponent--dark_xIcU{display:none}
.navbar__item,.navbarSearchContainer_Bca1,.dropdown__menu{display:none}
.navbar__toggle{display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;padding:0;border:0;background:transparent;color:#121315}
.main-wrapper{width:100%}
.grid{display:grid}.justify-center{justify-content:center}.flex{display:flex}.flex-col{flex-direction:column}.justify-between{justify-content:space-between}.items-center{align-items:center}.relative{position:relative}.absolute{position:absolute}.inset-0{inset:0}
.hidden{display:none}.w-full{width:100%}.h-full{height:100%}.z-20{position:relative;z-index:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-0{margin-bottom:0}.mt-SPACE_06{margin-top:24px}.space-x-SPACE_04>:not([hidden])~:not([hidden]){margin-left:16px}
.max-w-xs{max-width:20rem}.max-w-md{max-width:28rem}.max-w-7xl{max-width:80rem}
.px-8{padding-left:2rem;padding-right:2rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.px-SPACE_02{padding-left:8px;padding-right:8px}.px-SPACE_06{padding-left:24px;padding-right:24px}.py-SPACE_03{padding-top:12px;padding-bottom:12px}.\\!pb-0{padding-bottom:0!important}
.rounded-md{border-radius:.375rem}.rounded-lg{border-radius:.5rem}
.border{border-width:1px}.border-2{border-width:2px}.border-solid{border-style:solid}.border-tailCall-border-dark-100{border-color:#121315}
.bg-white{background:#fff}.bg-transparent{background:transparent}.bg-tailCall-yellow{background:#fdea2e}.bg-tailCall-dark-500{background:#121315}
.text-tailCall-dark-500{color:#121315}.text-tailCall-light-100{color:#fff}
.text-title-large{font-size:32px;line-height:41.6px;font-weight:700;letter-spacing:-1px}.text-content-small{font-size:16px;line-height:24px}.font-normal{font-weight:400}.font-bold{font-weight:700}
.hero-banner-title{margin:0;color:#121315}.hero-banner-sub-title{margin-top:1.5rem;color:#545556}
.group{overflow:hidden}.cursor-pointer{cursor:pointer}
.h-12{height:3rem}.gap-x-SPACE_03{column-gap:12px}.justify-center{justify-content:center}.items-center{align-items:center}
main.grid{width:100%}section.w-full{width:100%}
@media (max-width:639px){.sm\\:hidden{display:flex}.sm\\:flex{display:none}.hero-banner-title{max-width:22rem}.group{min-width:0}.group span{white-space:nowrap;font-size:14px}.navbar__inner{padding:0 1rem}.navbar__logo{width:6.8rem}}
@media (min-width:640px){:root{--ifm-navbar-height:6.5rem}.navbar{min-height:6.5rem}.navbar__inner{padding:0 2rem}.navbar__toggle{display:none}.navbar__item{display:inline-flex}.sm\\:hidden{display:none}.sm\\:flex{display:flex}.sm\\:items-center{align-items:center}.sm\\:text-center{text-align:center}.sm\\:max-w-2xl{max-width:42rem}.sm\\:max-w-5xl{max-width:64rem}.sm\\:m-auto{margin:auto}.sm\\:mt-SPACE_04{margin-top:16px}.sm\\:mt-SPACE_10{margin-top:40px}.sm\\:space-x-SPACE_06>:not([hidden])~:not([hidden]){margin-left:24px}.sm\\:rounded-2xl{border-radius:1rem}.sm\\:rounded-xl{border-radius:.75rem}.sm\\:h-16{height:4rem}.sm\\:text-display-small{font-size:56px;line-height:67.2px;font-weight:700;letter-spacing:-2px}.sm\\:text-content-medium{font-size:20px;line-height:32px}.sm\\:text-title-small{font-size:20px;line-height:26px;font-weight:700}.sm\\:px-SPACE_08{padding-left:32px;padding-right:32px}.sm\\:py-SPACE_04{padding-top:16px;padding-bottom:16px}.object-contain{object-fit:contain}.mt-8{margin-top:2rem}}
@media (min-width:768px){.md\\:px-24{padding-left:6rem;padding-right:6rem}.md\\:justify-center{justify-content:center}}
@media (min-width:1024px){.lg\\:py-20{padding-top:5rem;padding-bottom:5rem}.lg\\:px-36{padding-left:9rem;padding-right:9rem}.lg\\:text-display-large{font-size:96px;line-height:105.6px;font-weight:700;letter-spacing:-3px}.lg\\:text-content-large{font-size:24px;line-height:36px}.lg\\:px-SPACE_10{padding-left:40px;padding-right:40px}.lg\\:py-SPACE_05{padding-top:20px;padding-bottom:20px}.lg\\:block{display:block}.navbarSearchContainer_Bca1{display:flex}}
`

const deferHomeAssetsScript = (styleHref: string, scriptSrcs: string[]) => `
<script>
window.docusaurus=window.docusaurus||{};
(function(){
var loaded=false;
var scripts=${JSON.stringify(scriptSrcs)};
function loadStyle(){
if(!${JSON.stringify(styleHref)})return;
var link=document.createElement('link');
link.rel='stylesheet';
link.href=${JSON.stringify(styleHref)};
document.head.appendChild(link);
}
function loadScripts(index){
if(index>=scripts.length)return;
var script=document.createElement('script');
script.src=scripts[index];
script.defer=true;
script.onload=function(){loadScripts(index+1)};
document.head.appendChild(script);
}
function load(){
if(loaded)return;
loaded=true;
loadStyle();
loadScripts(0);
}
['pointerdown','keydown','touchstart','scroll'].forEach(function(eventName){
window.addEventListener(eventName,load,{once:true,passive:true});
});
window.addEventListener('load',function(){setTimeout(load,9000)});
})();
</script>`

export default {
title,
trailingSlash: true,
tagline: "GraphQL platform engineered for scale",
headTags: [
{
tagName: "script",
attributes: {
id: "chatbotscript",
"data-accountid": "CZPG9aVdtk59Tjz4SMTu8w==",
"data-websiteid": "75VGI0NlBqessD4BQn2pFg==",
src: "https://app.robofy.ai/bot/js/common.js?v=" + new Date().getTime(),
},
},
{
tagName: "script",
attributes: {
Expand Down Expand Up @@ -226,5 +291,29 @@ export default {
},
}
},
async function homePagePerformancePlugin() {
return {
name: "home-page-performance",
async postBuild({outDir}) {
const indexPath = path.join(outDir, "index.html")
if (!fs.existsSync(indexPath)) return

let html = fs.readFileSync(indexPath, "utf8")
const styleMatch = html.match(/<link rel="stylesheet" href="([^"]+)">/)
const scriptSrcs = Array.from(html.matchAll(/<script src="([^"]+)" defer="defer"><\/script>/g)).map(
(match) => match[1],
)

html = html.replace(
/<link rel="stylesheet" href="([^"]+)">/,
`<style id="home-critical-css">${homeCriticalCss}</style><noscript><link rel="stylesheet" href="$1"></noscript>`,
)
html = html.replace(/<script src="[^"]+" defer="defer"><\/script>\n?/g, "")
html = html.replace("</head>", `${deferHomeAssetsScript(styleMatch?.[1] ?? "", scriptSrcs)}</head>`)

fs.writeFileSync(indexPath, html)
},
}
},
],
} satisfies Config
34 changes: 27 additions & 7 deletions src/components/home/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
import React from "react"
import React, {useEffect, useState} from "react"
import Heading from "@theme/Heading"

import LinkButton from "../shared/LinkButton"
import HeroImage from "@site/static/images/home/hero.svg"
import {analyticsHandler} from "@site/src/utils"
import {Theme, codeSandboxUrl} from "@site/src/constants"
import {Theme} from "@site/src/constants"
import {pageLinks} from "@site/src/constants/routes"
import Link from "@docusaurus/Link"
import Section from "../shared/Section"

const DesktopHeroImage = (): JSX.Element | null => {
const [shouldRender, setShouldRender] = useState(false)

useEffect(() => {
setShouldRender(window.matchMedia("(min-width: 640px)").matches)
}, [])

if (!shouldRender) return null

return (
<img
src="/images/home/hero.svg"
alt="Tailcall GraphQL platform overview"
className="object-contain h-full sm:h-full w-full mt-8 max-w-7xl"
width={1400}
height={672}
loading="eager"
fetchPriority="high"
/>
)
}

const Banner = (): JSX.Element => {
return (
<main className="grid justify-center">
Expand All @@ -26,7 +46,7 @@ const Banner = (): JSX.Element => {
</p>
<div className="hidden sm:flex justify-center mt-SPACE_06 sm:mt-SPACE_10 space-x-SPACE_04 sm:space-x-SPACE_06">
<LinkButton
title="Learn More"
title="Explore Tailcall"
href={pageLinks.introduction}
theme={Theme.Dark}
width="small"
Expand All @@ -43,7 +63,7 @@ const Banner = (): JSX.Element => {

<div className="sm:hidden flex justify-between md:justify-center mt-SPACE_06 sm:mt-SPACE_10 space-x-SPACE_04 sm:space-x-SPACE_06">
<LinkButton
title="Learn More"
title="Explore Tailcall"
href={pageLinks.introduction}
theme={Theme.Dark}
onClick={() => analyticsHandler("Home Page", "Click", "Playground")}
Expand All @@ -59,7 +79,7 @@ const Banner = (): JSX.Element => {
</div>
</div>
</Section>
<HeroImage className="object-contain h-full sm:h-full w-full mt-8 max-w-7xl" />
<DesktopHeroImage />
</main>
)
}
Expand Down
26 changes: 17 additions & 9 deletions src/components/home/IntroductionVideo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, {useRef} from "react"
import React, {useRef, useState} from "react"
import {useCookieConsent} from "@site/src/utils/hooks/useCookieConsent"
import "./style.css"

const IntroductionVideo: React.FC = () => {
const videoId = "1011521201"
const videoRef = useRef<HTMLDivElement>(null)
const [isVideoLoaded, setIsVideoLoaded] = useState(false)
const {getCookieConsent} = useCookieConsent()
const cookieConsent = getCookieConsent()

Expand All @@ -15,14 +16,21 @@ const IntroductionVideo: React.FC = () => {
return (
<div className="video-wrapper" ref={videoRef}>
<div className="video-container">
<iframe
src={`https://player.vimeo.com/video/${videoId}?autoplay=0&badge=0&autopause=0&player_id=0&app_id=58479${handleVimeoAnalytics()}`}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="absolute top-0 left-0 w-full h-full"
title="Tailcall Introduction Video"
loading="lazy"
/>
{isVideoLoaded ? (
<iframe
src={`https://player.vimeo.com/video/${videoId}?autoplay=1&badge=0&autopause=0&player_id=0&app_id=58479${handleVimeoAnalytics()}`}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="absolute top-0 left-0 w-full h-full"
title="Tailcall Introduction Video"
loading="lazy"
/>
) : (
<button className="video-placeholder" type="button" onClick={() => setIsVideoLoaded(true)}>
<span className="video-play-button" aria-hidden="true" />
<span className="sr-only">Play Tailcall introduction video</span>
</button>
)}
</div>
</div>
)
Expand Down
35 changes: 30 additions & 5 deletions src/components/home/IntroductionVideo/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,43 @@
border-radius: 0.375rem;
z-index: 10;
box-shadow: 0px 4px 32px 0px #ffe933;
background-image: url("/static/images/video-thumbnail.webp");
background-size: 100%;
background-image: url("/images/video-thumbnail.webp");
background-position: center;
background-size: cover;
}

.video-background {
.video-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
cursor: pointer;
border: 0;
background: rgba(0, 0, 0, 0.08);
}

.video-play-button {
position: absolute;
top: 50%;
left: 50%;
width: 4.5rem;
height: 4.5rem;
transform: translate(-50%, -50%);
border-radius: 9999px;
background: #ffe933;
box-shadow: 0 0 0 0.75rem rgba(255, 233, 51, 0.24);
}

.video-play-button::before {
content: "";
position: absolute;
top: 50%;
left: 54%;
transform: translate(-50%, -50%);
border-top: 0.85rem solid transparent;
border-bottom: 0.85rem solid transparent;
border-left: 1.25rem solid #111;
}

@media (min-width: 768px) {
Expand Down
60 changes: 60 additions & 0 deletions src/components/home/LazyHomeSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, {Suspense, useEffect, useRef, useState} from "react"

interface LazyHomeSectionProps {
children: React.ReactNode
minHeight?: string
}

const LazyHomeSection: React.FC<LazyHomeSectionProps> = ({children, minHeight = "24rem"}) => {
const ref = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(false)

useEffect(() => {
const element = ref.current
if (!element) return

let observer: IntersectionObserver | null = null

const startObserving = () => {
if (isVisible || observer) return

if (!("IntersectionObserver" in window)) {
setIsVisible(true)
return
}

observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true)
observer?.disconnect()
}
},
{rootMargin: "0px"},
)

observer.observe(element)
}

window.addEventListener("scroll", startObserving, {once: true, passive: true})
window.addEventListener("wheel", startObserving, {once: true, passive: true})
window.addEventListener("touchstart", startObserving, {once: true, passive: true})
window.addEventListener("keydown", startObserving, {once: true})

return () => {
observer?.disconnect()
window.removeEventListener("scroll", startObserving)
window.removeEventListener("wheel", startObserving)
window.removeEventListener("touchstart", startObserving)
window.removeEventListener("keydown", startObserving)
}
}, [isVisible])

return (
<div ref={ref} style={!isVisible ? {minHeight} : undefined}>
{isVisible ? <Suspense fallback={null}>{children}</Suspense> : null}
</div>
)
}

export default LazyHomeSection
Loading
Loading