diff --git a/scripts/sync-sched/schedule-2026.json b/scripts/sync-sched/schedule-2026.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/scripts/sync-sched/schedule-2026.json @@ -0,0 +1 @@ +[] diff --git a/scripts/sync-sched/sync.ts b/scripts/sync-sched/sync.ts index 6b92cf15c2..d40b2ba94d 100644 --- a/scripts/sync-sched/sync.ts +++ b/scripts/sync-sched/sync.ts @@ -84,6 +84,7 @@ async function sync( 2023: "https://graphqlconf23.sched.com/api", 2024: "https://graphqlconf2024.sched.com/api", 2025: "https://graphqlconf2025.sched.com/api", + 2026: "https://graphqlconf2026.sched.com/api", }[year] assert(apiUrl, `API URL for year ${year} not found`) diff --git a/src/app/conf/2026/_data.ts b/src/app/conf/2026/_data.ts new file mode 100644 index 0000000000..5f969f05f4 --- /dev/null +++ b/src/app/conf/2026/_data.ts @@ -0,0 +1,59 @@ +import "server-only" + +import { SchedSpeaker, ScheduleSession } from "@/app/conf/2023/types" +import { readSpeakers } from "../_api/sched-data" + +const speakersData = require("../../../../scripts/sync-sched/speakers.json") +const equalitySets: string[][] = speakersData.equal || [] + +export const schedule: ScheduleSession[] = require("../../../../scripts/sync-sched/schedule-2026.json") + +type SpeakerUsername = SchedSpeaker["username"] + +export const speakerSessions = new Map() + +for (const session of schedule) { + for (const speaker of session.speakers || []) { + if (!speakerSessions.has(speaker.username)) { + speakerSessions.set(speaker.username, []) + } + + speakerSessions.get(speaker.username)!.push(session) + } +} + +export const speakers: SchedSpeaker[] = readSpeakers(2026).filter(speaker => + speakerSessions.has(speaker.username), +) + +export const previousEditionSessions = new Map< + SpeakerUsername, + ScheduleSession[] +>() + +{ + const schedule2023 = require("../../../../scripts/sync-sched/schedule-2023.json") + const schedule2024 = require("../../../../scripts/sync-sched/schedule-2024.json") + const schedule2025 = require("../../../../scripts/sync-sched/schedule-2025.json") + + collectSessionsFromPreviousYears(schedule2023) + collectSessionsFromPreviousYears(schedule2024) + collectSessionsFromPreviousYears(schedule2025) +} + +function collectSessionsFromPreviousYears(schedule: ScheduleSession[]) { + for (const session of schedule) { + for (const speaker of session.speakers || []) { + const duplicates = equalitySets.find(set => + set.includes(speaker.username), + ) + + for (const username of duplicates || [speaker.username]) { + if (!previousEditionSessions.has(username)) { + previousEditionSessions.set(username, []) + } + previousEditionSessions.get(username)!.push(session) + } + } + } +} diff --git a/src/app/conf/2026/_videos.ts b/src/app/conf/2026/_videos.ts new file mode 100644 index 0000000000..a644ca43ec --- /dev/null +++ b/src/app/conf/2026/_videos.ts @@ -0,0 +1,35 @@ +/** + * This was populated with data captured via: + * + * https://developers.google.com/youtube/v3/docs/playlistItems/list + * + * with: + * + * - part: snippet + * - maxResults: 50 + * - playlistId: + * + * And then also adding `pageToken` for the second request to get the second + * page. + * + * Note that the playlistId is the ID of the "Uploads" playlist for the + * "GraphQLFoundationTalks" channel. + * + * Since the videos are unlisted it needed to be authenticated with the + * https://www.googleapis.com/auth/youtube.readonly scope. This is also + * why we couldn't use the generate-videos-mappings.py script. + * + * Then we simply mapped over the results: + * + * ```js + * for (const item of data.items) { + * const id = item.snippet.resourceId.videoId; + * const title = item.snippet.title + * videos.push({ id, title }) + * } + * ``` + */ +export const videos: { + id: string + title: string +}[] = [] diff --git a/src/app/conf/2026/assets/graphql-foundation-wordmark.svg b/src/app/conf/2026/assets/graphql-foundation-wordmark.svg new file mode 100644 index 0000000000..8ecff1b893 --- /dev/null +++ b/src/app/conf/2026/assets/graphql-foundation-wordmark.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/conf/2026/code-of-conduct/code-of-conduct.mdx b/src/app/conf/2026/code-of-conduct/code-of-conduct.mdx new file mode 100644 index 0000000000..5909350faa --- /dev/null +++ b/src/app/conf/2026/code-of-conduct/code-of-conduct.mdx @@ -0,0 +1,53 @@ +The Linux Foundation and its project communities are dedicated to providing a harassment-free experience for participants at all of our events, whether they are held in person or virtually. Linux Foundation events are working conferences intended for professional networking and collaboration within the open source community. They exist to encourage the open exchange of ideas and expression and require an environment that recognizes the inherent worth of every person and group. While at Linux Foundation events or related ancillary or social events, any participants, including members, speakers, attendees, volunteers, sponsors, exhibitors, booth staff and anyone else, must not engage in harassment in any form. + +This Code of Conduct may be revised at any time by The Linux Foundation and the terms are non-negotiable. Your registration for or attendance at any Linux Foundation event, whether it's held in person or virtually, indicates your agreement to abide by this policy and its terms. + + +## Expected Behavior + +All event participants, whether they are attending an in-person event or a virtual event, are expected to behave in accordance with professional standards, with both this Code of Conduct as well as their respective employer's policies governing appropriate workplace behavior and applicable laws. + + +## Unacceptable Behavior + +Harassment will not be tolerated in any form, whether in person or virtually, including, but not limited to, harassment based on gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, age, religion or any other status protected by laws in which the conference or program is being held. Harassment includes the use of abusive, offensive or degrading language, intimidation, stalking, harassing photography or recording, inappropriate physical contact, sexual imagery and unwelcome sexual advances or requests for sexual favors. Any report of harassment at one of our events, whether in person or virtual, will be addressed immediately. Participants asked to stop any harassing behavior are expected to comply immediately. Anyone who witnesses or is subjected to unacceptable behavior should notify a conference organizer at once. + +Individuals who participate (or plan to participate) in Linux Foundation events, whether its an in-person event or a virtual event, should conduct themselves at all times in a manner that comports with both the letter and spirit of this policy prohibiting harassment and abusive behavior, whether before, during or after the event. This includes statements made in social media postings, on-line publications, text messages, and all other forms of electronic communication. + +Speakers should not use sexual language, images, or any language or images that would constitute harassment as defined above in their talks. Exhibitor booths serve as a platform for presenting businesses and/or projects and should maintain a professional and inclusive presence; therefore, the use of sexualized images, activities, materials, or attire, including costumes and uniforms that contribute to a sexualized environment, is strictly prohibited. Additionally, booths must not be utilized for political campaigning or promoting political causes, including the display or engagement in activities or materials that support such endeavors. + + +## Consequences of Unacceptable Behavior + +If a participant engages in harassing behavior, whether in person or virtually, the conference organizers may take any action they deem appropriate depending on the circumstances, ranging from issuance of a warning to the offending individual to expulsion from the conference with no refund. The Linux Foundation reserves the right to exclude any participant found to be engaging in harassing behavior from participating in any further Linux Foundation events, trainings or other activities. + +If a participant (or individual wishing to participate in a Linux Foundation event, in-person and/or virtual), through postings on social media or other online publications or another form of electronic communication, engages in conduct that violates this policy, whether before, during or after a Linux Foundation event, The Linux Foundation may take appropriate corrective action, which could include imposing a temporary or permanent ban on an individual's participation in future Linux Foundation events. + + +## What To Do If You Witness or Are Subject To Unacceptable Behavior + +If you are being harassed, notice that someone else is being harassed, or have any other concerns relating to harassment, please contact a member of the conference staff immediately. You are also encouraged to contact Angela Brown, Senior VP & General Manager of Events, at [angela@linuxfoundation.org](mailto:angela@linuxfoundation.org). + + +## Incident Response + +Our staff has taken incident response training and responds to harassment reports quickly and thoroughly. As referenced above, if a participant engages in harassing behavior, whether in-person or virtually, the conference organizers may take any action they deem appropriate, ranging from issuance of a warning to the offending individual to expulsion from the conference with no refund, depending on the circumstances. The Linux Foundation reserves the right to exclude any participant found to be engaging in harassing behavior from participating in any further Linux Foundation events, trainings or other activities. + +Conference staff will also provide support to victims, including, but not limited to: +- Providing an Escort +- Contacting Hotel/Venue Security or Local Law Enforcement +- Briefing Key Event Staff For Response/Victim Assistance +- And otherwise assisting those experiencing harassment to ensure that they feel safe for the duration of the conference. + + +## Health and Safety Requirements + +It is necessary for all attendees to cooperate and protect one another. For this reason, The Linux Foundation's events may have health and safety requirements (the "Health and Safety Requirements"). The specific requirements may vary from event to event, and will be communicated in writing prior to and during the event. + +If an attendee fails to comply with any of the Health and Safety Requirements, The Linux Foundation may (but is not obligated to) take appropriate corrective action, which could include immediate removal from the event and venue without a refund, and/or imposing a temporary or permanent ban on an individual's participation in future Linux Foundation events. + + +## Pre-Event Concerns + +If you are planning to attend an upcoming event, whether in-person or virtually and have concerns regarding another individual who may be present, please contact Angela Brown ([angela@linuxfoundation.org](mailto:angela@linuxfoundation.org)). Precautions will be taken to ensure your comfort and safety, including, but not limited to providing an escort, prepping onsite event staff, keeping victim and harasser from attending the same talks/social events and providing onsite contact cell phone numbers for immediate contact. + diff --git a/src/app/conf/2026/code-of-conduct/opengraph-image.tsx b/src/app/conf/2026/code-of-conduct/opengraph-image.tsx new file mode 100644 index 0000000000..84f732fc7c --- /dev/null +++ b/src/app/conf/2026/code-of-conduct/opengraph-image.tsx @@ -0,0 +1,10 @@ +import { SimpleOpengraphImage } from "../components/og-images/simple-opengraph-image" +export { + generateStaticParams, + contentType, + size, +} from "../components/og-images/simple-opengraph-image" + +export default SimpleOpengraphImage.bind(null, { + pageTitle: "Code of Conduct", +}) diff --git a/src/app/conf/2026/code-of-conduct/page.tsx b/src/app/conf/2026/code-of-conduct/page.tsx new file mode 100644 index 0000000000..0bfddd32c2 --- /dev/null +++ b/src/app/conf/2026/code-of-conduct/page.tsx @@ -0,0 +1,99 @@ +import type { Metadata } from "next" +import clsx from "clsx" + +import { Anchor } from "@/app/conf/_design-system/anchor" +import { ServerComponentMarkdown } from "@/app/conf/_components/server-component-markdown" +import { Button } from "@/app/conf/_design-system/button" + +import { NavbarPlaceholder } from "../components/navbar" +import { Hero, HeroStripes } from "../components/hero" +import "../resources/prose.css" + +import markdown from "./code-of-conduct.mdx?raw" + +export const metadata: Metadata = { + title: "Code of Conduct | GraphQLConf 2026", +} + +const components = { + a: (props: React.AnchorHTMLAttributes) => { + return ( + + ) + }, + ul: (props: React.HTMLAttributes) => { + return
    + }, + Callout: (props: React.HTMLAttributes) => { + return ( +
    + ) + }, +} + +export default function ResourcesPage() { + return ( + <> + + + } + > + + +
    +
    + { + return ( +
    + + {mdx({ components })} +
    + ) + }} + /> +
    +
    + + ) +} diff --git a/src/app/conf/2026/components/become-a-sponsor/blur-blob.webp b/src/app/conf/2026/components/become-a-sponsor/blur-blob.webp new file mode 100644 index 0000000000..af6299d6bd Binary files /dev/null and b/src/app/conf/2026/components/become-a-sponsor/blur-blob.webp differ diff --git a/src/app/conf/2026/components/become-a-sponsor/index.tsx b/src/app/conf/2026/components/become-a-sponsor/index.tsx new file mode 100644 index 0000000000..74b0bab2b9 --- /dev/null +++ b/src/app/conf/2026/components/become-a-sponsor/index.tsx @@ -0,0 +1,127 @@ +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 ( +
    + +
    +
    +
    +

    Become a Sponsor

    +

    + Connect with the global GraphQL community and showcase your brand + to industry leaders and decision-makers. +

    +
    + {/* TODO + + */} +
    +
    + + + + + + +
    +
    +
    + ) +} + +function DefinitionListItem({ + className, + term, + definition, +}: { + className?: string + term: string + definition: string +}) { + return ( +
    +
    + {term} +
    +
    {definition}
    +
    + ) +} + +function Stripes() { + return ( +
    + +
    + ) +} diff --git a/src/app/conf/2026/components/call-for-proposals.tsx b/src/app/conf/2026/components/call-for-proposals.tsx new file mode 100644 index 0000000000..499033bc74 --- /dev/null +++ b/src/app/conf/2026/components/call-for-proposals.tsx @@ -0,0 +1,529 @@ +"use client" + +import clsx from "clsx" +import { useState, useEffect, Fragment } from "react" +import Link from "next/link" + +import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" +import { Button } from "@/app/conf/_design-system/button" + +function DatesTab() { + return ( + + + + + + + + + ) +} + +function DefinitionListItem({ + className, + term, + definition, +}: { + className?: string + term: string + definition: string +}) { + return ( +
    +
    + {term} +
    +
    + {definition} +
    +
    + ) +} +function TopicsTab() { + return ( +
    +

    Suggested Topics

    +
      +
    • GraphQL Working Group
    • +
        +
      • + GraphQL Specification (including incremental delivery, nullability) +
      • +
      • GraphQL-over-HTTP specification
      • +
      • Federation specification
      • +
      • + Reference software (GraphQL.js, graphql-http, GraphiQL and LSP) +
      • +
      +
    • GraphQL in Production
    • +
        +
      • Case studies
      • +
      • Federation and Distributed Systems
      • +
      • + Schema evolution (including backwards compatibility and versioning) +
      • +
      • Security
      • +
      • Scaling
      • +
      • Observability, telemetry and tracing
      • +
      • Integrating AI and LLMs
      • +
      +
    • Developer Experience
    • +
        +
      • Frontend
      • +
      • Backend
      • +
      • Patterns and community trends
      • +
      • DX with AI and LLMs
      • +
      • Testing
      • +
      • Documentation
      • +
      +
    +
    + ) +} + +function NotesTab() { + return ( +
    +

    Important Notes

    +
      +
    • + All speakers are required to adhere to our{" "} + + Code of Conduct + + . We also highly recommend that speakers take our online{" "} + + Inclusive Speaker Orientation Course + + . +
    • +
    • + Panel submissions must include the names of all participants in the + initial submission to be considered. In an effort to promote speaker + diversity, The Linux Foundation does not accept submissions with + all-male panels, and speakers must not all be from the same company. +
    • +
    • + Complimentary Passes For Speakers – One complimentary pass for the + event will be provided for each accepted speaker. +
    • +
    • + Avoid sales pitches and discussing unlicensed or potentially + closed-source technologies when preparing your proposal; these talks + are almost always rejected due to the fact that they take away from + the integrity of our events, and are rarely well-received by + conference attendees. +
    • +
    • + All accepted speakers are required to submit their slides prior to the + event. +
    • +
    +

    Preparing to Submit Your Proposal

    +

    + While it is not our intention to provide you with strict instructions on + how to prepare your proposal, we hope you will take a moment to review + the following guidelines that we have put together to help you prepare + the best submission possible. To get started, here are three things that + you should consider before submitting your proposal: +

    +
      +
    • What are you hoping to get from your presentation?
    • +
    • What do you expect the audience to gain from your presentation?
    • +
    • How will your presentation help better the ecosystem?
    • +
    +

    + There are plenty of ways to give a presentation about projects and + technologies without focusing on company-specific efforts. Remember the + things to consider that we mentioned above when writing your proposal + and think of ways to make it interesting for attendees while still + letting you share your experiences, educate the community about an + issue, or generate interest in a project. +

    +

    How to Give a Great Talk

    +

    + We want to make sure submitters receive resources to help put together a + great submission and if accepted, give the best presentation possible. + To help with this, we recommend viewing seasoned speaker Dawn Foster's + in-depth talk:{" "} + + Getting Over Your Imposter Syndrome to Become a Conference Speaker + + . +

    +

    + Have More Questions? First Time Submitting? Don't Feel Intimidated +

    +

    + Linux Foundation events are an excellent way to get to know the + community and share your ideas and the work that you are doing and we + strongly encourage first-time speakers to submit talks for our events. + In the instance that you aren't sure about your abstract,{" "} + + reach out to us + {" "} + and we will be more than happy to work with you on your proposal. +

    +
    + ) +} + +function TypesTab() { + return ( + + + + + + + + ) +} + +function ProcessTab() { + return ( +
    +

    The Talk Selection Process

    +

    + The GraphQL Foundation strives to select conference talks based on fair + criteria in a transparent manner. There are three groups involved in the + selection process, each with their own focus to help create an engaging + and balanced conference schedule: +

    +
      +
    • The Technical Steering Committee (TSC)
    • +
    • The new Subject Matter Experts initiative (SMEs)
    • +
    • The Program Committee
    • +
    +

    The Technical Steering Committee

    +

    + The TSC are a group of 11 individuals who are elected to serve a two + year term to provide technical oversight of all GraphQL development + efforts. When evaluating conference talks they{" "} + focus on quality and use the following criteria: +

    +
      +
    • Relevance
    • +
    • Originality
    • +
    • Soundness
    • +
    • Quality of Presentation
    • +
    • Importance
    • +
    +

    Subject Matter Experts

    +

    + This will be a panel of volunteers drawn from industry experts, working + group members, security and observability experts, and maintainers and + contributors to open source GraphQL projects. When evaluating the talks, + they will{" "} + focus on how exciting and engaging the talks are and + use the following criteria: +

    +
      +
    • Subject Content
    • +
    • Originality
    • +
    • Audience Engagement
    • +
    +

    The Program Committee

    +

    + The Program Committee is made up of representatives from the GraphQL + Foundation board and interested members of the GraphQL community who + have had experience organizing conferences. They shape the schedule from + the highest-rated talks, ensuring balance across industries and + affiliations, and also including a range of speaker experience and + demographics, to ensure a varied and well-rounded representation of the + GraphQL ecosystem. +

    +

    + Have More Questions? First Time Submitting? Don't Feel Intimidated +

    +

    + Linux Foundation events are an excellent way to get to know the + community and share your ideas and the work that you are doing and we + strongly encourage first-time speakers to submit talks for our events. + In the instance that you aren't sure about your abstract, reach out to + us and we will be more than happy to work with you on your proposal. +

    +
    + ) +} + +const tabs = { + dates: , + topics: , + types: , + notes: , + process: , +} + +type Tab = keyof typeof tabs + +const tabsInOrder: Tab[] = ["dates", "topics", "types", "notes", "process"] + +export function CallForProposals() { + const [buttonText, setButtonText] = useState("Submit a Proposal") + const [isDisabled, setIsDisabled] = useState(false) + const [activeTab, setActiveTab] = useState("dates") + + useEffect(() => { + const checkDate = () => { + const currentDate = new Date() + const closingDate = new Date("2026-02-01T00:00:00Z") + if (currentDate >= closingDate) { + setButtonText("CFP Closed") + setIsDisabled(true) + } + } + + checkDate() + const timer = setInterval(checkDate, 60000) // Check every minute + + return () => clearInterval(timer) + }, []) + + return ( +
    +
    +
    +

    Call for Proposals

    +

    + Putting on an amazing conference depends on great content, which is + where you come in! Join other GraphQL leaders and community members + as a presenter by submitting to our Call for Proposals (CFP) and + sharing your experience across a wide range of topics. Please click + through all of the tabs below before submitting a proposal. +

    +

    + For any questions regarding the CFP process, please email{" "} + + cfp@linuxfoundation.org + + . +

    +

    + Please be aware that the Linux Foundation uses Sessionize for CFP + submissions. Sessionize is a cloud-based event content management + software designed to be intuitive and user-friendly. If you need + guidance, please review{" "} + + how to submit your session + {" "} + for an event to see step-by-step instructions and helpful + screenshots. +

    + +
    +
    +
    + {tabsInOrder.map((tab, i) => ( + + ))} +
    +
    + {tabsInOrder.map(tab => ( + + +
    + {tabs[tab]} +
    +
    + ))} +
    +
    +
    +
    + ) +} + +interface TabButtonProps + extends Omit, "onFocus"> { + tab: Tab + tabIndex?: number + activeTab: Tab + setActiveTab: (tab: Tab) => void +} + +function TabButton({ + tab, + tabIndex, + activeTab, + setActiveTab, + className, + ...props +}: TabButtonProps) { + return ( + + ) +} + +function arrowsMoveSideways(event: React.KeyboardEvent) { + if (event.key === "ArrowLeft") { + const previousElement = event.currentTarget.previousElementSibling + if (previousElement) { + ;(previousElement as HTMLElement).focus() + } + } else if (event.key === "ArrowRight") { + const nextElement = event.currentTarget.nextElementSibling + if (nextElement) { + ;(nextElement as HTMLElement).focus() + } + } +} + +function DefinitionListBox({ children }: { children: React.ReactNode }) { + return ( +
    +
    + {children} +
    + +
    + ) +} + +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)" + +function Stripes() { + const mask = "linear-gradient(125deg, transparent 68%, hsl(0 0 0 / 0.8))" + return ( +
    +
    +
    +
    + ) +} diff --git a/src/app/conf/2026/components/cta-card-section/index.tsx b/src/app/conf/2026/components/cta-card-section/index.tsx new file mode 100644 index 0000000000..cf7d94fc1e --- /dev/null +++ b/src/app/conf/2026/components/cta-card-section/index.tsx @@ -0,0 +1,66 @@ +import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" + +import logoMask from "./logo-mask.webp" + +export interface CtaCardSectionProps + extends Omit, "title"> { + title: React.ReactNode + description: string + children: React.ReactNode +} + +export function CtaCardSection({ + className, + title: heading, + description, + children, + ...rest +}: CtaCardSectionProps) { + return ( +
    +
    +
    +
    +

    + {heading} +

    +

    + {description} +

    +
    + + {children} +
    + +
    + +
    +
    +
    + ) +} diff --git a/src/app/conf/2026/components/cta-card-section/logo-mask.webp b/src/app/conf/2026/components/cta-card-section/logo-mask.webp new file mode 100644 index 0000000000..8c83251f30 Binary files /dev/null and b/src/app/conf/2026/components/cta-card-section/logo-mask.webp differ diff --git a/src/app/conf/2026/components/footer/blur-bean.webp b/src/app/conf/2026/components/footer/blur-bean.webp new file mode 100644 index 0000000000..899304aa38 Binary files /dev/null and b/src/app/conf/2026/components/footer/blur-bean.webp differ diff --git a/src/app/conf/2026/components/footer/index.tsx b/src/app/conf/2026/components/footer/index.tsx new file mode 100644 index 0000000000..447de91ebc --- /dev/null +++ b/src/app/conf/2026/components/footer/index.tsx @@ -0,0 +1,152 @@ +import NextLink from "next/link" +import { ReactNode } from "react" +import { clsx } from "clsx" + +import { SocialIcons } from "../../../_components/social-icons" +import { StripesDecoration } from "../../../_design-system/stripes-decoration" + +import blurBean from "./blur-bean.webp" + +interface FooterLink { + href: string + children: ReactNode + disabled?: boolean +} + +export function Footer({ + links, + logo, +}: { + links: (FooterLink | FooterLink[])[] + logo: ReactNode +}) { + return ( +
    + +
    + {logo} +
    +

    + + - + +

    +
    Menlo Park, California
    +
    +
    +
      + {links.map((box, i) => ( +
    • + +
    • + ))} +
    +
    +
    +

    + Copyright © {new Date().getFullYear()} The GraphQL Foundation. All + rights reserved. +

    +

    + For web site terms of use, trademark policy and general project + policies please see{" "} + + https://lfprojects.org + + . +

    +
    + +
    +
    + ) +} + +function Stripes() { + return ( +
    + +
    + ) +} + +function FooterBox({ box }: { box: FooterLink | FooterLink[] }) { + if (Array.isArray(box)) { + return ( +
    + {box.map(link => ( + + {link.children} + + ))} +
    + ) + } + + const { href, children, disabled } = box + + return ( + + {children} + + ) +} diff --git a/src/app/conf/2026/components/format-speaker-position.tsx b/src/app/conf/2026/components/format-speaker-position.tsx new file mode 100644 index 0000000000..ca3f0d29b2 --- /dev/null +++ b/src/app/conf/2026/components/format-speaker-position.tsx @@ -0,0 +1,7 @@ +import { SchedSpeaker } from "../../2023/types" + +export function formatSpeakerPosition(speaker: SchedSpeaker) { + return [speaker.company === "-" ? "" : speaker.company, speaker.position] + .filter(Boolean) + .join(", ") +} diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/1.webp b/src/app/conf/2026/components/gallery-strip/images/2023/1.webp new file mode 100644 index 0000000000..d7eea3f82e Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/1.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/10.webp b/src/app/conf/2026/components/gallery-strip/images/2023/10.webp new file mode 100644 index 0000000000..36ea36a797 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/10.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/11.webp b/src/app/conf/2026/components/gallery-strip/images/2023/11.webp new file mode 100644 index 0000000000..a80106071a Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/11.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/12.webp b/src/app/conf/2026/components/gallery-strip/images/2023/12.webp new file mode 100644 index 0000000000..95e7e6408b Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/12.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/13.webp b/src/app/conf/2026/components/gallery-strip/images/2023/13.webp new file mode 100644 index 0000000000..b1e38faf47 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/13.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/14.webp b/src/app/conf/2026/components/gallery-strip/images/2023/14.webp new file mode 100644 index 0000000000..8d690e9e52 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/14.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/2.webp b/src/app/conf/2026/components/gallery-strip/images/2023/2.webp new file mode 100644 index 0000000000..1d74dc6007 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/2.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/3.webp b/src/app/conf/2026/components/gallery-strip/images/2023/3.webp new file mode 100644 index 0000000000..2db9119b0a Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/3.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/4.webp b/src/app/conf/2026/components/gallery-strip/images/2023/4.webp new file mode 100644 index 0000000000..c81d3f61be Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/4.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/5.webp b/src/app/conf/2026/components/gallery-strip/images/2023/5.webp new file mode 100644 index 0000000000..35626d8ed3 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/5.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/6.webp b/src/app/conf/2026/components/gallery-strip/images/2023/6.webp new file mode 100644 index 0000000000..a835ea21d9 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/6.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/7.webp b/src/app/conf/2026/components/gallery-strip/images/2023/7.webp new file mode 100644 index 0000000000..61343ea8d9 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/7.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/8.webp b/src/app/conf/2026/components/gallery-strip/images/2023/8.webp new file mode 100644 index 0000000000..a8fdca040d Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/8.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2023/9.webp b/src/app/conf/2026/components/gallery-strip/images/2023/9.webp new file mode 100644 index 0000000000..388314373d Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2023/9.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/1.webp b/src/app/conf/2026/components/gallery-strip/images/2024/1.webp new file mode 100644 index 0000000000..7861718767 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/1.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/11.webp b/src/app/conf/2026/components/gallery-strip/images/2024/11.webp new file mode 100644 index 0000000000..3a32278ff8 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/11.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/12.webp b/src/app/conf/2026/components/gallery-strip/images/2024/12.webp new file mode 100644 index 0000000000..758904900b Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/12.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/13.webp b/src/app/conf/2026/components/gallery-strip/images/2024/13.webp new file mode 100644 index 0000000000..31d941ec2c Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/13.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/2.webp b/src/app/conf/2026/components/gallery-strip/images/2024/2.webp new file mode 100644 index 0000000000..52c29cb9c3 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/2.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/3.webp b/src/app/conf/2026/components/gallery-strip/images/2024/3.webp new file mode 100644 index 0000000000..bbbd7d2a56 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/3.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/4.webp b/src/app/conf/2026/components/gallery-strip/images/2024/4.webp new file mode 100644 index 0000000000..e24a5d6384 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/4.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/6.webp b/src/app/conf/2026/components/gallery-strip/images/2024/6.webp new file mode 100644 index 0000000000..70d219c412 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/6.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/7.webp b/src/app/conf/2026/components/gallery-strip/images/2024/7.webp new file mode 100644 index 0000000000..b05c4d3d4c Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/7.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/8.webp b/src/app/conf/2026/components/gallery-strip/images/2024/8.webp new file mode 100644 index 0000000000..eebf0c1ff9 Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/8.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/2024/9.webp b/src/app/conf/2026/components/gallery-strip/images/2024/9.webp new file mode 100644 index 0000000000..38d7e8cbac Binary files /dev/null and b/src/app/conf/2026/components/gallery-strip/images/2024/9.webp differ diff --git a/src/app/conf/2026/components/gallery-strip/images/index.ts b/src/app/conf/2026/components/gallery-strip/images/index.ts new file mode 100644 index 0000000000..136ccc282e --- /dev/null +++ b/src/app/conf/2026/components/gallery-strip/images/index.ts @@ -0,0 +1,56 @@ +import image2023_1 from "./2023/1.webp" +import image2023_2 from "./2023/2.webp" +import image2023_3 from "./2023/3.webp" +import image2023_4 from "./2023/4.webp" +import image2023_5 from "./2023/5.webp" +import image2023_6 from "./2023/6.webp" +import image2023_7 from "./2023/7.webp" +import image2023_8 from "./2023/8.webp" +import image2023_9 from "./2023/9.webp" +import image2023_10 from "./2023/10.webp" +import image2023_11 from "./2023/11.webp" +import image2023_12 from "./2023/12.webp" +import image2023_13 from "./2023/13.webp" + +import image2024_1 from "./2024/1.webp" +import image2024_2 from "./2024/2.webp" +import image2024_3 from "./2024/3.webp" +import image2024_4 from "./2024/4.webp" +import image2024_6 from "./2024/6.webp" +import image2024_7 from "./2024/7.webp" +import image2024_8 from "./2024/8.webp" +import image2024_9 from "./2024/9.webp" +import image2024_11 from "./2024/11.webp" +import image2024_12 from "./2024/12.webp" +import image2024_13 from "./2024/13.webp" + +export const imagesByYear = { + "2023": [ + image2023_8, + image2023_11, + image2023_3, + image2023_1, + image2023_13, + image2023_6, + image2023_9, + image2023_4, + image2023_12, + image2023_2, + image2023_10, + image2023_7, + image2023_5, + ], + "2024": [ + image2024_2, + image2024_3, + image2024_12, + image2024_7, + image2024_9, + image2024_6, + image2024_13, + image2024_11, + image2024_8, + image2024_4, + image2024_1, + ], +} diff --git a/src/app/conf/2026/components/gallery-strip/index.tsx b/src/app/conf/2026/components/gallery-strip/index.tsx new file mode 100644 index 0000000000..b81d5ebabc --- /dev/null +++ b/src/app/conf/2026/components/gallery-strip/index.tsx @@ -0,0 +1,84 @@ +"use client" + +import { useState } from "react" +import { clsx } from "clsx" +import Image from "next/image" +import type { StaticImageData } from "next/image" + +import { Marquee } from "@/app/conf/_design-system/marquee" + +import { imagesByYear } from "./images" + +const YEARS = ["2024", "2023"] as const +type Year = (typeof YEARS)[number] + +export interface GalleryStripProps extends React.HTMLAttributes {} + +export function GalleryStrip({ className, ...rest }: GalleryStripProps) { + const [selectedYear, setSelectedYear] = useState("2024") + + return ( +
    +
    + {YEARS.map(year => ( + + ))} +
    + +
    + + {imagesByYear[selectedYear].map((image, i) => { + const key = `${selectedYear}-${i}` + + return + })} + +
    +
    + ) +} + +function GalleryStripImage({ + image, + index, +}: { + image: StaticImageData + index: number +}) { + return ( +
    + +
    + ) +} diff --git a/src/app/conf/2026/components/get-your-ticket/blur-bean.webp b/src/app/conf/2026/components/get-your-ticket/blur-bean.webp new file mode 100644 index 0000000000..cdfd6621dc Binary files /dev/null and b/src/app/conf/2026/components/get-your-ticket/blur-bean.webp differ diff --git a/src/app/conf/2026/components/get-your-ticket/index.tsx b/src/app/conf/2026/components/get-your-ticket/index.tsx new file mode 100644 index 0000000000..a765816322 --- /dev/null +++ b/src/app/conf/2026/components/get-your-ticket/index.tsx @@ -0,0 +1,74 @@ +import { clsx } from "clsx" + +import { ImageLoaded } from "../image-loaded" + +import { TicketPeriods } from "./ticket-periods" + +import blurBean from "./blur-bean.webp" + +export function GetYourTicket({ className }: { className?: string }) { + return ( +
    + +
    +
    +

    + Get your ticket +

    +

    + The registration deadline is 23:59 PT on the respective date. +

    +
    + +
    + +
    +
    +
    + ) +} + +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)" + +function Stripes() { + return ( + +
    +
    + + ) +} diff --git a/src/app/conf/2026/components/get-your-ticket/ticket-period.tsx b/src/app/conf/2026/components/get-your-ticket/ticket-period.tsx new file mode 100644 index 0000000000..cc79d2e902 --- /dev/null +++ b/src/app/conf/2026/components/get-your-ticket/ticket-period.tsx @@ -0,0 +1,91 @@ +import { Button } from "@/app/conf/_design-system/button" +import { clsx } from "clsx" +import { GET_TICKETS_LINK } from "../../links" + +export interface TicketPeriodProps { + price: string + date: Date | [Date, Date] + isLoading?: boolean + soldOut?: boolean + comingSoon?: boolean + name: string +} + +export function TicketPeriod({ + price, + date, + isLoading, + soldOut, + comingSoon, + name, +}: TicketPeriodProps) { + const disabled = soldOut || comingSoon || isLoading + + return ( +
    +
    +

    {name}

    +
    +
    +
    + + {price} + + {/* eslint-disable-next-line tailwindcss/no-custom-classname */} + + {Array.isArray(date) ? ( + <> + +
    + +
    +
    + ) +} + +function Time({ date }: { date: Date }) { + return ( +