diff --git a/src/app/day/2026/amsterdam/page.tsx b/src/app/day/2026/amsterdam/page.tsx index c80c398496..5031148cbf 100644 --- a/src/app/day/2026/amsterdam/page.tsx +++ b/src/app/day/2026/amsterdam/page.tsx @@ -10,7 +10,13 @@ import { CtaCardSection } from "../components/cta-card-section" import { MarqueeRows } from "@/app/conf/2026/components/marquee-rows" import { NavbarPlaceholder } from "../components/navbar" import { GallerySection } from "../../gallery-section" -import { ScheduleSection } from "@/app/day/2026/amsterdam/schedule-section.tsx" +import { EventScheduleSection } from "../components/event-schedule-section" +import { + amsterdamSessions, + AMSTERDAM_TIMEZONE, + AMSTERDAM_TIMEZONE_LABEL, + tagColors, +} from "./schedule-data" const MARQUEE_ITEMS = [ ["AMSTERDAM", "JUNE 2026", "GRAPHQL DAY", "FOST", "COMMUNITY", "APIs"], @@ -57,7 +63,12 @@ export default function AmsterdamPage() { />
- + = { @@ -46,7 +23,7 @@ export const tagColors: Record = { Observability: "#1a5b77", } -export const amsterdamSessions: AmsterdamSession[] = [ +export const amsterdamSessions: EventSession[] = [ { id: 3224, uuid: "80952503-07dd-4e31-acaf-b9e400f55126", diff --git a/src/app/day/2026/amsterdam/schedule-section.tsx b/src/app/day/2026/components/event-schedule-section.tsx similarity index 79% rename from src/app/day/2026/amsterdam/schedule-section.tsx rename to src/app/day/2026/components/event-schedule-section.tsx index ff5da68c83..9068193e66 100644 --- a/src/app/day/2026/amsterdam/schedule-section.tsx +++ b/src/app/day/2026/components/event-schedule-section.tsx @@ -2,6 +2,7 @@ import { useState } from "react" import Image from "next/image" +import type { StaticImageData } from "next/image" import clsx from "clsx" import { Tag } from "@/app/conf/_design-system/tag" @@ -13,29 +14,43 @@ import { SocialIconType, } from "@/app/conf/_design-system/social-icon" import { formatDescription } from "@/app/conf/2026/schedule/[id]/format-description" -import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" -import { - AmsterdamSession, - AmsterdamSpeaker, - amsterdamSessions, - tagColors, -} from "./schedule-data" - -const TIME_RANGE = new Intl.DateTimeFormat("en-US", { - hour: "2-digit", - minute: "2-digit", - hour12: false, - timeZone: "Europe/Amsterdam", -}) +export interface EventSpeaker { + id: number + name: string + company: string + jobtitle: string + avatar?: StaticImageData + socialurls: { service: string; url: string }[] +} -const DATE_FORMAT = new Intl.DateTimeFormat("en-US", { - day: "numeric", - month: "long", - timeZone: "Europe/Amsterdam", -}) +export interface EventSession { + id: number + uuid: string + title: string + /** ISO 8601 in venue local time */ + start: string + /** ISO 8601 in venue local time */ + end: string + /** Topic tags derived from the session description. */ + tags: string[] + /** HTML */ + description: string + venue: string + speakers: EventSpeaker[] +} -export function ScheduleSection() { +export function EventScheduleSection({ + sessions, + timezone, + timezoneLabel, + tagColors, +}: { + sessions: EventSession[] + timezone: string + timezoneLabel: string + tagColors: Record +}) { return (

Schedule

-

- All times in Amsterdam Time (CET, UTC+2) -

+

{timezoneLabel}

- {amsterdamSessions.map((session, i) => ( + {sessions.map((session, i) => ( ))}
@@ -68,13 +83,14 @@ export function ScheduleSection() { function SessionBlock({ session, isFirst, + timezone, + tagColors, }: { - session: AmsterdamSession + session: EventSession isFirst: boolean + timezone: string + tagColors: Record }) { - // On xl+ with a single speaker we slot the card next to the last two - // paragraphs of the description so it sits in the bottom-right corner. - // Multi-speaker sessions keep the regular "speakers below" layout. const sideSpeaker = session.speakers.length === 1 ? session.speakers[0] : null return ( @@ -82,7 +98,12 @@ function SessionBlock({
- + {session.description && ( <>
@@ -110,7 +131,7 @@ function SessionDescription({ sideSpeaker, }: { description: string - sideSpeaker: AmsterdamSpeaker | null + sideSpeaker: EventSpeaker | null }) { const [expanded, setExpanded] = useState(false) const paragraphs = parseParagraphs(description) @@ -165,12 +186,6 @@ function SessionDescription({ ) } -/** - * Split FOST description HTML (a sequence of `

...

` blocks) into the - * inner HTML of each paragraph so we can render them as real React `

` - * siblings — needed so we can splice the speaker card in alongside the last - * couple of paragraphs at xl+. - */ function parseParagraphs(html: string): string[] { const formatted = formatDescription(html) const matches = formatted.match(/

[\s\S]*?<\/p>/g) @@ -180,11 +195,26 @@ function parseParagraphs(html: string): string[] { function SessionHeader({ session, + timezone, + tagColors, className, }: { - session: AmsterdamSession + session: EventSession + timezone: string + tagColors: Record className?: string }) { + const timeRange = new Intl.DateTimeFormat("en-US", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: timezone, + }) + const dateFormat = new Intl.DateTimeFormat("en-US", { + day: "numeric", + month: "long", + timeZone: timezone, + }) const start = new Date(session.start) const end = new Date(session.end) @@ -196,9 +226,9 @@ function SessionHeader({

{session.venue && ( @@ -229,7 +259,7 @@ function SessionSpeakers({ speakers, className, }: { - speakers: AmsterdamSpeaker[] + speakers: EventSpeaker[] className?: string }) { return ( @@ -265,7 +295,7 @@ function SpeakerCard({ speaker, index, }: { - speaker: AmsterdamSpeaker + speaker: EventSpeaker index: number }) { const variant = STRIPE_VARIANTS[index % STRIPE_VARIANTS.length] @@ -280,15 +310,21 @@ function SpeakerCard({
-
- + {speaker.avatar && ( +
+ )} + {speaker.avatar ? ( + + ) : ( +
+ )}
links.find(l => l.service.toLowerCase() === service.toLowerCase()), diff --git a/src/app/day/2026/nyc/page.tsx b/src/app/day/2026/nyc/page.tsx index 877085514e..f77d6cbd96 100644 --- a/src/app/day/2026/nyc/page.tsx +++ b/src/app/day/2026/nyc/page.tsx @@ -4,19 +4,20 @@ import { Button } from "@/app/conf/_design-system/button" import { Hero, HeroDateAndLocation } from "../components/hero" import { AboutSection } from "../components/about-section" import { WhyAttendSection } from "../components/why-attend-section" -import { - BecomeASpeakerSection, - CfpButton, -} from "../components/become-a-speaker" import { EventPartnersSection } from "../components/event-partners" -import { CtaCardSection } from "../components/cta-card-section" import { MarqueeRows } from "@/app/conf/2026/components/marquee-rows" import { PastSpeakersSection } from "../components/past-speakers" import { NavbarPlaceholder } from "../components/navbar" import { GallerySection } from "../../gallery-section" +import { EventScheduleSection } from "../components/event-schedule-section" +import { + nycSessions, + NYC_TIMEZONE, + NYC_TIMEZONE_LABEL, + tagColors, +} from "./schedule-data" -const TICKET_LINK = - "https://portal.joinfost.io/event/future-of-software-technology-new-york-2026/82677ac7-3989-456b-93a7-b1c215bd51d6/apidays-new-york" +const SCHEDULE_ANCHOR = "#schedule" const MARQUEE_ITEMS = [ ["NEW YORK", "MAY 2026", "GRAPHQL DAY", "FOST", "COMMUNITY", "APIs"], @@ -46,10 +47,12 @@ export default function NYCPage() { location="New York City" />
- -
@@ -60,28 +63,21 @@ export default function NYCPage() { />
- - - - -
+ + ) diff --git a/src/app/day/2026/nyc/schedule-data.ts b/src/app/day/2026/nyc/schedule-data.ts new file mode 100644 index 0000000000..e60ddff8c2 --- /dev/null +++ b/src/app/day/2026/nyc/schedule-data.ts @@ -0,0 +1,192 @@ +import braxtonBraggAvatar from "./speakers/braxton-bragg.jpg" +import elenaBukarevaAvatar from "./speakers/elena-bukareva.jpg" +import jeffAuriemmaAvatar from "./speakers/jeff-auriemma.jpg" +import michaelStaibAvatar from "./speakers/michael-staib.webp" +import pascalSennAvatar from "./speakers/pascal-senn.webp" +import vanessaJohnsonAvatar from "./speakers/vanessa-johnson.webp" + +import type { EventSession } from "../components/event-schedule-section" + +export const NYC_TIMEZONE = "America/New_York" +export const NYC_TIMEZONE_LABEL = + "All times in Eastern Daylight Time (EDT, UTC−4)" + +/** Color per topic, picked to read clearly against the cream/dark backgrounds. */ +export const tagColors: Record = { + GraphQL: "#e07b39", + "GraphQL History": "#e07b39", + "Open Source": "#36C1A0", + Learning: "#3c8fc1", + "Schema Design": "#9f7aea", + "Best Practices": "#5c8a52", + "AI Agents": "#7e66cc", + Federation: "#FC8251", + "Public Sector": "#4e6e82", + "Schema Evolution": "#cbc749", + Observability: "#1a5b77", + Accessibility: "#CC6BB0", + "CI/CD": "#4a7c59", +} + +export const nycSessions: EventSession[] = [ + { + id: 1, + uuid: "built-to-evolve-13-years-of-graphql", + title: "Built to Evolve: 13 Years of GraphQL", + start: "2026-05-13T14:10:00-04:00", + end: "2026-05-13T14:35:00-04:00", + tags: ["GraphQL History", "Open Source"], + description: + "

In 2015, we promised GraphQL would be “easy to learn and use.” Ten years, and hundreds of billions of daily API calls later, we’ve learned that not all our hopes and promises turned out to be true.

\n", + venue: "GraphQL Stage", + speakers: [ + { + id: 1, + name: "Braxton Bragg", + company: "Meta", + jobtitle: "Product Manager", + avatar: braxtonBraggAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/braxtonbragg/", + }, + ], + }, + { + id: 2, + name: "Elena Bukareva", + company: "Meta", + jobtitle: "Engineering Manager", + avatar: elenaBukarevaAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/elena-bukareva/", + }, + ], + }, + ], + }, + { + id: 2, + uuid: "teach-yourself-graphql-2026", + title: "Teach yourself GraphQL in 2026: an anti-blueprint", + start: "2026-05-13T14:40:00-04:00", + end: "2026-05-13T15:05:00-04:00", + tags: ["Learning", "Schema Design", "Best Practices"], + description: + "

After eleven years as an open source technology, GraphQL has never had a more favorable learning curve. Clearer mental models, better educational materials, and a deeper collective understanding of best practices have transformed the “wild west” of 2015 to a much more manageable landscape today.

\n

You and your team are unique, so rather than a one-size-fits-all blueprint, this talk presents a practical guide to teaching yourself GraphQL in 2026. We’ll examine how beginners typically build their first mental model of GraphQL, the most common misconceptions, and the key design questions they encounter early.

\n

Special attention will be paid to different modalities: schema-first vs. code-first, schema design principles, common pitfalls when considering enums, the proper use of fragments, and security and performance by default. Attendees will leave with a conceptual roadmap for self-study, a recipe book for context engineering in their agent, and an understanding of the major decision points along the journey ahead.

\n", + venue: "GraphQL Stage", + speakers: [ + { + id: 3, + name: "Jeff Auriemma", + company: "Apollo", + jobtitle: "Senior Engineering Manager", + avatar: jeffAuriemmaAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/jeffreyauriemma/", + }, + ], + }, + ], + }, + { + id: 3, + uuid: "graphql-execution-layer-ai-agents", + title: "GraphQL as the Execution Layer for AI Agents", + start: "2026-05-13T15:10:00-04:00", + end: "2026-05-13T15:35:00-04:00", + tags: ["AI Agents", "Federation", "Public Sector"], + description: + "

Your next million API consumers won’t be developers. They’ll be AI agents. And they don’t read documentation, parse hypermedia links, or guess which of your 200 REST endpoints returns the data they need.

\n

This talk examines what happens when autonomous AI agents become the primary consumers of your API layer. Drawing on real data from Singapore’s public government APIs, I’ll show how REST responses waste 30–60% of an agent’s token budget on structural overhead, and how a typed, self-describing schema changes the equation entirely.

\n

We’ll walk through the three properties that make an API truly agent-native: discoverability, precision, and composability. We’ll look at what it would take to unify API estates like Singapore’s 3,000+ government APIs across 75+ agencies into a single, self-describing surface. A pattern Gartner expects 30% of enterprises to adopt by 2027.

\n

You’ll leave with a framework for what makes an API truly agent-native, why GraphQL’s type system and federation model get you there, and how to start without a rewrite.

\n", + venue: "GraphQL Stage", + speakers: [ + { + id: 4, + name: "Pascal Senn", + company: "ChilliCream", + jobtitle: "Founder", + avatar: pascalSennAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/pascal-senn-90899a15a", + }, + { service: "github", url: "https://github.com/PascalSenn" }, + { service: "website", url: "https://chillicream.com" }, + ], + }, + ], + }, + { + id: 4, + uuid: "closing-the-loop-coding-agents", + title: + "Closing the Loop: How GraphQL Gives Coding Agents Eyes on What Actually Matters", + start: "2026-05-13T15:40:00-04:00", + end: "2026-05-13T16:05:00-04:00", + tags: ["AI Agents", "Schema Evolution", "Observability"], + description: + "

Coding agents are reshaping how we build software. Implementing features, refactoring systems, and shipping changes at a pace unthinkable 6 months ago. But to be successful with agents you need the right feedback loop. One that guides your agent to success, not into the spiral of death.

\n

Ask Claude to add a review system to your product API. Without knowing what’s in use, it might reshape your types, move fields, and break your deployed clients because it is missing a crucial feedback loop of what’s in use in your clients.

\n

GraphQL changes this. Every client operation explicitly declares the exact fields and types it needs. That gives you something rare: field-level usage data across your entire consumer base. Not endpoint hits, but actual demand, broken down to the individual field.

\n

When coding agents can access this data, they stop guessing. Evolve your schema grounded in reality, not assumptions.

\n

This talk shows how GraphQL’s inherent usage visibility and the rise of coding agents create a feedback loop that didn’t exist before. And why it matters for anyone building APIs that need to evolve fast.

\n", + venue: "GraphQL Stage", + speakers: [ + { + id: 5, + name: "Michael Staib", + company: "ChilliCream", + jobtitle: "Founder", + avatar: michaelStaibAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/michael-staib-31519571/", + }, + { service: "github", url: "https://github.com/michaelstaib" }, + { service: "website", url: "https://chillicream.com" }, + ], + }, + ], + }, + { + id: 5, + uuid: "tbd-slot-1630", + title: "TBD", + start: "2026-05-13T16:30:00-04:00", + end: "2026-05-13T16:55:00-04:00", + tags: ["GraphQL"], + description: "", + venue: "GraphQL Stage", + speakers: [], + }, + { + id: 6, + uuid: "server-assisted-accessibility-graphql-ci", + title: + "Server Assisted Accessibility (Part 2): Enforcing Consistent Semantics via GraphQL + CI", + start: "2026-05-13T17:00:00-04:00", + end: "2026-05-13T17:25:00-04:00", + tags: ["Accessibility", "Schema Design", "CI/CD"], + description: + "

In my apidays Paris session last year, I introduced a “shift left” pattern for accessibility: attach accessibility metadata to GraphQL fields using lightweight directives, expose it through code generation, and let Android (Jetpack Compose), iOS (SwiftUI), and web clients map it into native accessibility semantics for consistent defaults.

\n

This follow-up, Part 2, focuses on the next problem teams hit in production: keeping that metadata accurate as the schema changes. We’ll walk through a practical, low-friction approach adding CI-friendly validation that catches common contract regressions before changes ship: missing required metadata, invalid values, and template drift.

\n

This approach standardizes the repeatable, high-leverage semantics (labels, roles, states, templated summaries) so clients can focus on the platform-specific work that truly belongs in the UI (complex interactions, focus order, and behavior). You’ll leave with schema examples you can adapt, a realistic enforcement blueprint that fits into pull requests and CI, and rollout patterns for introducing rules gradually without breaking existing clients.

\n", + venue: "GraphQL Stage", + speakers: [ + { + id: 7, + name: "Vanessa Johnson", + company: "The New York Times", + jobtitle: "Android Engineer", + avatar: vanessaJohnsonAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/vanessa-johnson999/", + }, + ], + }, + ], + }, +] diff --git a/src/app/day/2026/nyc/speakers/braxton-bragg.jpg b/src/app/day/2026/nyc/speakers/braxton-bragg.jpg new file mode 100644 index 0000000000..cff3551511 Binary files /dev/null and b/src/app/day/2026/nyc/speakers/braxton-bragg.jpg differ diff --git a/src/app/day/2026/nyc/speakers/elena-bukareva.jpg b/src/app/day/2026/nyc/speakers/elena-bukareva.jpg new file mode 100644 index 0000000000..d27db7266b Binary files /dev/null and b/src/app/day/2026/nyc/speakers/elena-bukareva.jpg differ diff --git a/src/app/day/2026/nyc/speakers/jeff-auriemma.jpg b/src/app/day/2026/nyc/speakers/jeff-auriemma.jpg new file mode 100644 index 0000000000..4bf99a939e Binary files /dev/null and b/src/app/day/2026/nyc/speakers/jeff-auriemma.jpg differ diff --git a/src/app/day/2026/nyc/speakers/michael-staib.webp b/src/app/day/2026/nyc/speakers/michael-staib.webp new file mode 100644 index 0000000000..34ccb4ee9c Binary files /dev/null and b/src/app/day/2026/nyc/speakers/michael-staib.webp differ diff --git a/src/app/day/2026/nyc/speakers/pascal-senn.webp b/src/app/day/2026/nyc/speakers/pascal-senn.webp new file mode 100644 index 0000000000..05c0399d37 Binary files /dev/null and b/src/app/day/2026/nyc/speakers/pascal-senn.webp differ diff --git a/src/app/day/2026/nyc/speakers/vanessa-johnson.webp b/src/app/day/2026/nyc/speakers/vanessa-johnson.webp new file mode 100644 index 0000000000..1002421565 Binary files /dev/null and b/src/app/day/2026/nyc/speakers/vanessa-johnson.webp differ diff --git a/src/app/day/2026/singapore/page.tsx b/src/app/day/2026/singapore/page.tsx index e1dd72d221..b3a2de4ee9 100644 --- a/src/app/day/2026/singapore/page.tsx +++ b/src/app/day/2026/singapore/page.tsx @@ -9,7 +9,13 @@ import { MarqueeRows } from "@/app/conf/2026/components/marquee-rows" import { PastSpeakersSection } from "../components/past-speakers" import { NavbarPlaceholder } from "../components/navbar" import { GallerySection } from "../../gallery-section" -import { ScheduleSection } from "./schedule-section" +import { EventScheduleSection } from "../components/event-schedule-section" +import { + singaporeSessions, + SINGAPORE_TIMEZONE, + SINGAPORE_TIMEZONE_LABEL, + tagColors, +} from "./schedule-data" const SCHEDULE_ANCHOR = "#schedule" @@ -61,7 +67,12 @@ export default function SingaporePage() {
- + = { @@ -43,7 +20,7 @@ export const tagColors: Record = { Observability: "#1a5b77", } -export const singaporeSessions: SingaporeSession[] = [ +export const singaporeSessions: EventSession[] = [ { id: 3224, uuid: "80952503-07dd-4e31-acaf-b9e400f55126", diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx deleted file mode 100644 index 3e5229c083..0000000000 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ /dev/null @@ -1,339 +0,0 @@ -import Image from "next/image" -import clsx from "clsx" - -import { Tag } from "@/app/conf/_design-system/tag" -import { CalendarIcon } from "@/app/conf/_design-system/pixelarticons/calendar-icon" -import { PinIcon } from "@/app/conf/_design-system/pixelarticons/pin-icon" -import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" -import { - SocialIcon, - SocialIconType, -} from "@/app/conf/_design-system/social-icon" -import { formatDescription } from "@/app/conf/2026/schedule/[id]/format-description" - -import { - SingaporeSession, - SingaporeSpeaker, - singaporeSessions, - tagColors, -} from "./schedule-data" - -const TIME_RANGE = new Intl.DateTimeFormat("en-US", { - hour: "2-digit", - minute: "2-digit", - hour12: false, - timeZone: "Asia/Singapore", -}) - -const DATE_FORMAT = new Intl.DateTimeFormat("en-US", { - day: "numeric", - month: "long", - timeZone: "Asia/Singapore", -}) - -export function ScheduleSection() { - return ( -
-
-
-
-
-

Schedule

-

- All times in Singapore Time (SGT, UTC+8) -

-
- - {singaporeSessions.map((session, i) => ( - - ))} -
-
-
-
- ) -} - -function SessionBlock({ - session, - isFirst, -}: { - session: SingaporeSession - isFirst: boolean -}) { - // On xl+ with a single speaker we slot the card next to the last two - // paragraphs of the description so it sits in the bottom-right corner. - // Multi-speaker sessions keep the regular "speakers below" layout. - const sideSpeaker = session.speakers.length === 1 ? session.speakers[0] : null - - return ( -
-
- - {session.description && ( - <> -
- - - )} - {session.speakers.length > 0 && ( -
-
- -
- )} -
- ) -} - -function SessionDescription({ - description, - sideSpeaker, -}: { - description: string - sideSpeaker: SingaporeSpeaker | null -}) { - const paragraphs = parseParagraphs(description) - const splitAt = - sideSpeaker && paragraphs.length >= 2 - ? paragraphs.length - 2 - : paragraphs.length - const lead = paragraphs.slice(0, splitAt) - const tail = paragraphs.slice(splitAt) - - return ( -
- {lead.map((html, i) => ( -

- ))} - {tail.length > 0 && ( -

-
- {tail.map((html, i) => ( -

- ))} -

- {sideSpeaker && ( -
- -
- )} -
- )} -
- ) -} - -/** - * Split FOST description HTML (a sequence of `

...

` blocks) into the - * inner HTML of each paragraph so we can render them as real React `

` - * siblings — needed so we can splice the speaker card in alongside the last - * couple of paragraphs at xl+. - */ -function parseParagraphs(html: string): string[] { - const formatted = formatDescription(html) - const matches = formatted.match(/

[\s\S]*?<\/p>/g) - if (!matches) return [formatted] - return matches.map(p => p.replace(/^

/, "").replace(/<\/p>$/, "")) -} - -function SessionHeader({ - session, - className, -}: { - session: SingaporeSession - className?: string -}) { - const start = new Date(session.start) - const end = new Date(session.end) - - return ( -

-

{session.title}

-
-
-
- - -
- {session.venue && ( -
- - {session.venue} -
- )} -
- {session.tags.length > 0 && ( -
- {session.tags.map(tag => ( - - {tag} - - ))} -
- )} -
-
- ) -} - -function SessionSpeakers({ - speakers, - className, -}: { - speakers: SingaporeSpeaker[] - className?: string -}) { - return ( -
*:not(:last-child)]:border-b-0", - className, - )} - > - {speakers.map((speaker, i) => ( - - ))} -
- ) -} - -const STRIPE_VARIANTS: { mask: string; endColor: string }[] = [ - { - mask: "linear-gradient(120deg, hsl(var(--color-pri-base)) 0%, hsl(var(--color-pri-base)) 8%, transparent 35%, transparent)", - endColor: "hsl(var(--color-sec-base))", - }, - { - mask: "radial-gradient(circle at bottom right, hsl(var(--color-pri-base)) 0%, hsl(var(--color-pri-base)) 10%, transparent 40%, transparent)", - endColor: "hsl(var(--color-sec-darker))", - }, - { - mask: "linear-gradient(-40deg, hsl(var(--color-pri-base)) 0%, hsl(var(--color-pri-base)) 5%, transparent 40%, transparent)", - endColor: "hsl(var(--color-sec-light))", - }, -] - -function SpeakerCard({ - speaker, - index, -}: { - speaker: SingaporeSpeaker - index: number -}) { - const variant = STRIPE_VARIANTS[index % STRIPE_VARIANTS.length] - const subtitle = [ - speaker.company === "-" ? "" : speaker.company, - speaker.jobtitle, - ] - .filter(Boolean) - .join(", ") - - return ( -
-
-
-
- -
- -
-
- -
-
-
{speaker.name}
- {subtitle && ( -

- {subtitle} -

- )} -
- {speaker.socialurls.length > 0 && ( - - )} -
-
-
- ) -} - -function SpeakerSocialLinks({ - links, -}: { - links: SingaporeSpeaker["socialurls"] -}) { - const ordered = SocialIconType.all - .map(service => - links.find(l => l.service.toLowerCase() === service.toLowerCase()), - ) - .filter((x): x is { service: string; url: string } => !!x?.url) - - if (ordered.length === 0) return null - - return ( -
- {ordered.map(social => ( - - - - ))} -
- ) -} - -function Hr({ className }: { className?: string }) { - return ( -
- ) -} diff --git a/src/app/day/layout.tsx b/src/app/day/layout.tsx index 84934d035f..db98b20709 100644 --- a/src/app/day/layout.tsx +++ b/src/app/day/layout.tsx @@ -50,6 +50,10 @@ export default function DayLayout({ children: "Singapore Schedule", href: "/day/2026/singapore#schedule", }, + { + children: "NYC Schedule", + href: "/day/2026/nyc#schedule", + }, { children: "Code of Conduct", href: "https://www.apidays.global/legal/code-of-conduct#:~:text=Individuals%20who%20participate%20(or%20plan,during%20or%20after%20the%20event.",