Skip to content

new conf design — speakers and schedule #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: new-conf-design--marquee
Choose a base branch
from
Draft
2 changes: 1 addition & 1 deletion src/app/conf/2023/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function ConfPage() {

function Hero() {
return (
<div className="bg-black/20 bg-[url('/img/conf/graphql-conf-bg.png')] bg-cover text-white bg-blend-multiply max-md:text-base">
<div className="bg-blk/20 bg-[url('/img/conf/graphql-conf-bg.png')] bg-cover text-white bg-blend-multiply max-md:text-base">
<div className="container py-16 md:py-20">
<div className="flex items-center gap-6">
<GraphQLConf className="h-14" />
Expand Down
84 changes: 84 additions & 0 deletions src/app/conf/2025/_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import "server-only"
import { stripHtml } from "string-strip-html"
import { SchedSpeaker, ScheduleSession } from "@/app/conf/2023/types"
import pLimit from "p-limit"

async function fetchData<T>(url: string): Promise<T> {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "GraphQL Conf / GraphQL Foundation",
},
})
const data = await response.json()
return data
} catch (error) {
throw new Error(
`Error fetching data from ${url}: ${(error as Error).message || (error as Error).toString()}`,
)
}
}

const token = process.env.SCHED_ACCESS_TOKEN_2024

async function getUsernames(): Promise<string[]> {
const response = await fetchData<{ username: string }[]>(
`https://graphqlconf2024.sched.com/api/user/list?api_key=${token}&format=json&fields=username`,
)
return response.map(user => user.username)
}

const limit = pLimit(40) // rate limit is 30req/min

async function getSpeakers(): Promise<SchedSpeaker[]> {
const usernames = await getUsernames()

const users = await Promise.all(
usernames.map(username =>
limit(() => {
return fetchData<SchedSpeaker>(
`https://graphqlconf2024.sched.com/api/user/get?api_key=${token}&by=username&term=${username}&format=json&fields=username,company,position,name,about,location,url,avatar,role,socialurls`,
)
}),
),
)

const result = users
.filter(speaker => speaker.role.includes("speaker"))
.map(user => {
return {
...user,
about: stripHtml(user.about).result,
}
})

return result
}

async function getSchedule(): Promise<ScheduleSession[]> {
const sessions = await fetchData<ScheduleSession[]>(
`https://graphqlconf2024.sched.com/api/session/export?api_key=${token}&format=json`,
)

const result = sessions.map(session => {
const { description } = session
if (description?.includes("<")) {
// console.log(`Found HTML element in about field for session "${session.name}"`)
}

return {
...session,
description: description && stripHtml(description).result,
}
})

return result
}

// @ts-expect-error -- fixme
export const speakers = await getSpeakers()

// @ts-expect-error -- fixme
export const schedule = await getSchedule()
10 changes: 10 additions & 0 deletions src/app/conf/2025/_videos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const videos: {
id: string
title: string
}[] = [
// temporary
{
id: "fA81OFu9BVY",
title: `Top 10 GraphQL Security Checks for Every Developer - Ankita Gupta, Ankush Jain - Akto.io`,
},
]
144 changes: 112 additions & 32 deletions src/app/conf/2025/assets/graphql-foundation-wordmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
125 changes: 125 additions & 0 deletions src/app/conf/2025/components/become-a-sponsor/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import clsx from "clsx"

import { Button } from "../../../_design-system/button"

import blurBlob from "./blur-blob.webp"
import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration"

export function BecomeASponsor() {
return (
<section id="sponsors" className="relative">
<Stripes />
<div className="gql-conf-container gql-conf-section xl:py-16 2xl:px-64">
<header className="flex flex-col gap-x-48 gap-y-4 md:flex-row md:items-end md:justify-between">
<div>
<h2 className="typography-h2">Become a Sponsor</h2>
<p className="mt-6 text-pretty typography-body-lg">
Connect with the global GraphQL community and showcase your brand
to industry leaders and decision-makers.
</p>
</div>
<Button
variant="primary"
href="https://events.linuxfoundation.org/sponsor-GraphQLConf-25?utm_source=graphql_conf_2025&utm_medium=website&utm_campaign=sponsor_section"
target="_blank"
rel="noreferrer"
className="shrink-0"
>
Download the Prospectus
</Button>
</header>
<dl className="relative z-10 mt-10 border border-neu-300 md:backdrop-blur-[6.4px] xl:mt-16">
<DefinitionListItem
term="Brand Visibility"
definition="Showcase your brand to thousands of GraphQL enthusiasts and decision-makers."
/>
<DefinitionListItem
term="Lead Generation"
definition="Connect with potential customers and partners in the GraphQL ecosystem."
/>
<DefinitionListItem
term="Thought Leadership"
definition="Position your company as a leader in the GraphQL space."
/>
<DefinitionListItem
term="Talent Acquisition"
definition="Meet and recruit top GraphQL developers and engineers."
/>
<DefinitionListItem
term="Product Feedback"
definition="Gather valuable feedback from the GraphQL community."
/>
<DefinitionListItem
term="Community Impact"
definition="Support and shape the future of GraphQL technology."
/>
</dl>
</div>
</section>
)
}

function DefinitionListItem({
className,
term,
definition,
}: {
className?: string
term: string
definition: string
}) {
return (
<div
className={clsx(
className,
"flex border-b border-neu-300 last:border-b-0 max-sm:flex-col",
)}
>
<dt className="flex min-w-[320px] shrink-0 items-center whitespace-pre border-b border-neu-300 p-4 typography-body-lg max-sm:w-full sm:border-b-0 sm:border-r">
{term}
</dt>
<dd className="flex items-center p-4 typography-body-md">{definition}</dd>
</div>
)
}

function Stripes() {
return (
<div
role="presentation"
// prettier-ignore
// false positive
// eslint-disable-next-line tailwindcss/no-contradicting-classname
className="pointer-events-none absolute inset-0
[--start-1:hsl(var(--color-sec-lighter))]
[--end-1:hsl(hsl(320deg_100%_96%/.8)]
dark:[--start-1:hsl(var(--color-sec-lighter))]
dark:[--end-1:hsl(var(--color-pri-lighter)/.2)]

[--start-2:#FFEAF8]
[--end-2:hsl(var(--color-neu-0))]
dark:[--start-2:rgba(255,204,239,.1)]
dark:[--end-2:hsl(var(--color-pri-lighter)/.4)]

translate-y-12

[mask-size:120%]
3xl:[mask-size:2000px] 2xl:opacity-50
max-md:[mask-size:600%] max-md:opacity-50
"
style={{
maskImage: `url(${blurBlob.src})`,
WebkitMaskImage: `url(${blurBlob.src})`,
maskPosition: "center",
WebkitMaskPosition: "center",
maskRepeat: "no-repeat",
WebkitMaskRepeat: "no-repeat",
}}
>
<StripesDecoration
evenClassName="bg-[linear-gradient(180deg,var(--start-1)_-200%,var(--end-1)_100%)]"
oddClassName="bg-[linear-gradient(180deg,var(--start-2)_0%,var(--end-2)_100%)]"
/>
</div>
)
}
2 changes: 1 addition & 1 deletion src/app/conf/2025/components/graphql-foundation-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function GraphQLFoundationCard({ className }: { className?: string }) {
<section className={clsx("gql-conf-section", className)}>
<div className="flex divide-neu-300 border border-neu-300 bg-neu-100 dark:divide-neu-100 dark:border-neu-100 dark:bg-neu-50 max-md:flex-col max-md:divide-y md:divide-x">
<div className="flex items-center justify-center px-8 py-10 lg:px-16 lg:py-24">
<FoundationWordmark className="text-rhodamine h-[68px] dark:text-current lg:h-[100px] [&_g]:fill-current" />
<FoundationWordmark className="h-[68px] text-rhodamine dark:text-current lg:h-[100px] [&_g]:fill-current" />
</div>
<p className="text-pretty px-8 py-10 typography-body-lg max-md:text-center lg:px-16 lg:py-24">
GraphQLConf is presented by the GraphQL Foundation, uniting the global
Expand Down
8 changes: 4 additions & 4 deletions src/app/conf/2025/components/hero/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function Hero() {
return (
<article className="gql-conf-navbar-strip relative isolate flex flex-col justify-center bg-pri-base text-neu-0 selection:bg-blk/40 before:bg-white/30 dark:bg-pri-darker dark:text-neu-900 dark:selection:bg-white/40 before:dark:bg-blk/40">
<article className="relative">
<Stripes />
<HeroStripes />
<div className="gql-conf-container mx-auto flex max-w-full flex-col gap-12 overflow-hidden p-4 pt-6 sm:p-8 sm:pt-12 md:gap-12 md:bg-left md:p-12 lg:px-24 lg:pb-16 lg:pt-24">
<div className="flex gap-10 max-md:flex-col md:justify-between">
<h1 className="flex flex-wrap gap-2 typography-d1">
Expand All @@ -35,7 +35,7 @@ export function Hero() {
</div>

<div className="flex flex-col gap-8">
<DateAndLocation />
<HeroDateAndLocation />
<Button className="md:w-fit" href={GET_TICKETS_LINK}>
Get your tickets
</Button>
Expand All @@ -55,7 +55,7 @@ export function Hero() {
)
}

function DateAndLocation() {
export function HeroDateAndLocation() {
return (
<div className="flex flex-col gap-4 typography-body-md md:flex-row md:gap-6">
<div className="flex items-center gap-2">
Expand All @@ -77,7 +77,7 @@ const maskEven =
const maskOdd =
"repeating-linear-gradient(to right, black, black 12px, transparent 12px, transparent 24px)"

function Stripes() {
export function HeroStripes() {
return (
<ImageLoaded
role="presentation"
Expand Down
27 changes: 21 additions & 6 deletions src/app/conf/2025/components/image-loaded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,34 @@
import type { StaticImageData } from "next/image"
import { useEffect, useState } from "react"

const _cache = new Map<string, HTMLImageElement>()

export interface ImageLoadedProps extends React.HTMLAttributes<HTMLDivElement> {
image: string | StaticImageData
}

export function ImageLoaded({ image, ...rest }: ImageLoadedProps) {
const [loaded, setLoaded] = useState(false)
const src = typeof image === "string" ? image : image.src

const alreadyLoaded = _cache.get(src)?.complete

useEffect(() => {
const img = new Image()
const src = typeof image === "string" ? image : image.src
img.src = src
img.onload = () => setLoaded(true)
}, [image])
let img: HTMLImageElement
if (_cache.has(src)) {
img = _cache.get(src)!
if (img.complete) {
setLoaded(true)
} else {
img.addEventListener("load", () => setLoaded(true))
}
} else {
img = new Image()
img.src = src
img.addEventListener("load", () => setLoaded(true))
_cache.set(src, img)
}
}, [src])

return <div data-loaded={loaded} {...rest} />
return <div data-loaded={alreadyLoaded || loaded} {...rest} />
}
2 changes: 1 addition & 1 deletion src/app/conf/2025/components/marquee-rows/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function MarqueeRows({
return (
<section
className={clsx(
"relative font-mono text-xl/none md:text-[56px]/none",
"relative font-mono text-xl/none md:text-[56px]/none 3xl:[mask-image:linear-gradient(to_right,transparent,black_5%,black_95%,transparent)]",
variant === "primary" ? "text-pri-base" : "text-neu-900",
className,
)}
Expand Down
1 change: 1 addition & 0 deletions src/app/conf/2025/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function Navbar({ links, year }: NavbarProps): ReactElement {
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false)

const handleDrawerClick = useCallback(() => {
// todo: block scrolling on body
setMobileDrawerOpen(prev => !prev)
}, [])

Expand Down
32 changes: 9 additions & 23 deletions src/app/conf/2025/components/sponsors.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import Stellate from "public/img/conf/Sponsors/Stellate.svg?svgr"
import Hasura from "public/img/conf/Sponsors/Hasura.svg?svgr"
import TheGuild from "public/img/conf/Sponsors/TheGuild.svg?svgr"
import Apollo from "public/img/conf/Sponsors/Apollo.svg?svgr"
import Tyk from "public/img/conf/Sponsors/Tyk.svg?svgr"
import IBM from "public/img/conf/Sponsors/IBM.svg?svgr"
import Graphweaver from "public/img/conf/Sponsors/Graphweaver.svg?svgr"
import Grafbase from "public/img/conf/Sponsors/Grafbase.svg?svgr"

import { clsx } from "clsx"
import { ChevronRight } from "../pixelarticons/chevron-right"
Expand All @@ -15,20 +9,12 @@ interface Sponsor {
link: string
}

const sponsorDiamond: Sponsor[] = [
{ icon: TheGuild, name: "The Guild", link: "https://the-guild.dev" },
{ icon: IBM, name: "IBM", link: "https://www.ibm.com/products/api-connect" },
]
const sponsorDiamond: Sponsor[] = []

const sponsorGold: Sponsor[] = [
{ icon: Apollo, name: "Apollo", link: "https://www.apollographql.com/" },
{ icon: Graphweaver, name: "Graphweaver", link: "https://graphweaver.com" },
{ icon: Hasura, name: "Hasura", link: "https://hasura.io" },
]
const sponsorGold: Sponsor[] = []

const sponsorSilver: Sponsor[] = [
{ icon: Stellate, name: "Stellate", link: "https://stellate.co" },
{ icon: Tyk, name: "Tyk", link: "https://tyk.io/" },
{ icon: Grafbase, name: "Grafbase", link: "https://grafbase.com/" },
]

export interface SponsorsProps {
Expand Down Expand Up @@ -57,13 +43,13 @@ const sponsorTiers: Tier[] = [

export function Sponsors({ heading }: SponsorsProps) {
return (
<section className="gql-conf-section mx-auto w-fit max-w-full py-16">
<section className="gql-conf-section mx-auto py-16 2xl:px-64">
<h1 className="typography-h2">{heading}</h1>

<div className="mt-10 md:mt-16">
{sponsorTiers.map(tier => (
<Tier key={tier.name} tier={tier} />
))}
{sponsorTiers.map(
tier => tier.items.length > 0 && <Tier key={tier.name} tier={tier} />,
)}
</div>
</section>
)
Expand All @@ -78,7 +64,7 @@ function Tier({ tier }: { tier: Tier }) {
</h3>
<div
className={clsx(
"grid justify-center gap-x-8 gap-y-4 md:grid-cols-2 xl:grid-cols-3",
"grid justify-center gap-x-8 gap-y-4 sm:grid-cols-2 xl:grid-cols-3",
)}
>
{tier.items.map(({ link, icon: Icon, name }, i) => (
Expand Down
Loading
Loading