+
-
@@ -65,8 +64,9 @@ export function Navbar({ links, year }: NavbarProps): ReactElement {
{mobileDrawerOpen && (
)}
@@ -131,3 +131,19 @@ function BackdropBlur() {
/>
)
}
+
+export function NavbarPlaceholder({
+ className,
+ ...rest
+}: React.HTMLAttributes
) {
+ return (
+
+ )
+}
diff --git a/src/app/conf/2025/page.tsx b/src/app/conf/2025/page.tsx
index 49be2a4f4c..b284fccfbb 100644
--- a/src/app/conf/2025/page.tsx
+++ b/src/app/conf/2025/page.tsx
@@ -11,16 +11,29 @@ import { GetYourTicket } from "./components/get-your-ticket"
import { RegisterSection } from "./components/register-section"
import { Sponsors } from "./components/sponsors"
import { GraphQLFoundationCard } from "./components/graphql-foundation-card"
+import { MarqueeRows } from "./components/marquee-rows"
+
export const metadata: Metadata = {
title: "GraphQLConf 2025 — Sept 08-10",
}
+const HERO_MARQUEE_ITEMS = [
+ ["COMMUNITY", "DEVELOPER EXPERIENCE", "APIs", "TOOLS & LIBRARIES"],
+ ["OPEN SOURCE", "FEDERATION", "ECOSYSTEMS", "TRACING & OBSERVABILITY"],
+ ["BEST PRACTICES", "WORKSHOPS", "SCHEMAS", "SECURITY"],
+]
+
export default function Page() {
return (
-
+
-
+
+
@@ -32,9 +45,33 @@ export default function Page() {
+
+
)
diff --git a/src/app/conf/2025/pixelarticons/code.svg b/src/app/conf/2025/pixelarticons/code.svg
new file mode 100644
index 0000000000..70d68d1694
--- /dev/null
+++ b/src/app/conf/2025/pixelarticons/code.svg
@@ -0,0 +1,16 @@
+
diff --git a/src/app/conf/_design-system/marquee.tsx b/src/app/conf/_design-system/marquee.tsx
new file mode 100644
index 0000000000..8963d067ee
--- /dev/null
+++ b/src/app/conf/_design-system/marquee.tsx
@@ -0,0 +1,143 @@
+"use client"
+
+import { clsx } from "clsx"
+import { useMotionValue, animate, motion } from "motion/react"
+import { useState, useEffect, Fragment } from "react"
+import useMeasure from "react-use-measure"
+
+export interface MarqueeProps {
+ children: React.ReactNode
+ gap?: number
+ speed?: number
+ speedOnHover?: number
+ direction?: "horizontal" | "vertical"
+ reverse?: boolean
+ className?: string
+ drag?: boolean
+ separator?: React.ReactNode
+}
+
+export function Marquee({
+ children,
+ gap = 16,
+ speed = 100,
+ speedOnHover,
+ direction = "horizontal",
+ reverse = false,
+ className,
+ drag = false,
+ separator,
+}: MarqueeProps) {
+ const [currentSpeed, setCurrentSpeed] = useState(speed)
+ const [ref, { width, height }] = useMeasure()
+ const translation = useMotionValue(0)
+ const [isTransitioning, setIsTransitioning] = useState(false)
+ const [key, setKey] = useState(0)
+
+ useEffect(() => {
+ let controls
+ const size = direction === "horizontal" ? width : height
+ const contentSize = size + gap
+ const from = reverse ? 0 : -contentSize / 2
+ const to = reverse ? -contentSize / 2 : 0
+
+ const distanceToTravel = Math.abs(to - from)
+ const duration = distanceToTravel / currentSpeed
+
+ if (isTransitioning) {
+ const remainingDistance = Math.abs(translation.get() - to)
+ const transitionDuration = remainingDistance / currentSpeed
+
+ controls = animate(translation, [translation.get(), to], {
+ ease: "linear",
+ duration: transitionDuration,
+ onComplete: () => {
+ setIsTransitioning(false)
+ setKey(prevKey => prevKey + 1)
+ },
+ })
+ } else {
+ controls = animate(translation, [from, to], {
+ ease: "linear",
+ duration: duration,
+ repeat: Infinity,
+ repeatType: "loop",
+ repeatDelay: 0,
+ onRepeat: () => {
+ translation.set(from)
+ },
+ })
+ }
+
+ return controls?.stop
+ }, [
+ key,
+ translation,
+ currentSpeed,
+ width,
+ height,
+ gap,
+ isTransitioning,
+ direction,
+ reverse,
+ ])
+
+ const hoverProps =
+ speedOnHover != null
+ ? {
+ onHoverStart: () => {
+ setIsTransitioning(true)
+ setCurrentSpeed(speedOnHover)
+ },
+ onHoverEnd: () => {
+ setIsTransitioning(true)
+ setCurrentSpeed(speed)
+ },
+ onPointerUp: () => {
+ if (window.matchMedia("(hover: none)").matches) {
+ setIsTransitioning(true)
+ setCurrentSpeed(speed)
+ }
+ },
+ }
+ : {}
+
+ const multiples = drag ? 12 : 2
+ const dragProps = drag
+ ? {
+ drag: "x" as const,
+ onDragStart: () => {
+ document.documentElement.style.cursor = "grabbing"
+ },
+ onDragEnd: () => {
+ document.documentElement.style.cursor = "initial"
+ },
+ }
+ : {}
+
+ return (
+
+
+ {Array.from({ length: multiples }).map((_, i) => (
+
+ {children}
+ {separator}
+
+ ))}
+
+
+ )
+}
diff --git a/src/app/conf/_design-system/stripes-decoration.tsx b/src/app/conf/_design-system/stripes-decoration.tsx
new file mode 100644
index 0000000000..971f89e706
--- /dev/null
+++ b/src/app/conf/_design-system/stripes-decoration.tsx
@@ -0,0 +1,37 @@
+import clsx from "clsx"
+
+const maskEven =
+ "repeating-linear-gradient(to right, transparent, transparent 12px, black 12px, black 24px)"
+
+const maskOdd =
+ "repeating-linear-gradient(to right, black, black 12px, transparent 12px, transparent 24px)"
+
+export interface StripesDecorationProps {
+ evenClassName?: string
+ oddClassName?: string
+}
+
+export function StripesDecoration(props: StripesDecorationProps) {
+ return (
+ <>
+ {props.evenClassName && (
+
+ )}
+ {props.oddClassName && (
+
+ )}
+ >
+ )
+}