diff --git a/package.json b/package.json index 71768eb..407a910 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,16 @@ "type": "module", "private": true, "scripts": { - "predev": "mkdir -p dist && cp src/index.html dist/index.html && cp -r public/* dist/", + "predev": "mkdir -p dist && node scripts/build-html.js && cp -r public/* dist/", "dev": "concurrently \"npm:dev:*\" --names \"JS,CSS,HTML,SERVER\" --prefix-colors \"blue,green,magenta,yellow\"", "dev:js": "node esbuild.config.js --watch", "dev:css": "npx tailwindcss -i ./src/styles/input.css -o ./dist/output.css --watch", - "dev:html": "nodemon --watch src/index.html --exec \"cp src/index.html dist/index.html\"", + "dev:html": "nodemon --watch src/index.html --exec \"node scripts/build-html.js\"", "dev:serve": "serve dist -p 3000 --no-port-switching --no-clipboard", "build": "npm run build:js && npm run build:css && npm run build:html && npm run build:assets", "build:js": "node esbuild.config.js", "build:css": "npx tailwindcss -i ./src/styles/input.css -o ./dist/output.css --minify", - "build:html": "cp src/index.html dist/index.html", + "build:html": "node scripts/build-html.js", "build:assets": "cp -r public/* dist/ && touch dist/.nojekyll", "preview": "serve dist -p 4173", "clean": "rm -rf dist && mkdir -p dist" diff --git a/public/images/background/background_top_right_san_jose.jpg b/public/images/background/background_top_right_san_jose.jpg new file mode 100644 index 0000000..52e980f Binary files /dev/null and b/public/images/background/background_top_right_san_jose.jpg differ diff --git a/public/images/developer_week_logo.png b/public/images/developer_week_logo.png new file mode 100644 index 0000000..36d0fdc Binary files /dev/null and b/public/images/developer_week_logo.png differ diff --git a/public/images/developer_week_logo.svg b/public/images/developer_week_logo.svg new file mode 100644 index 0000000..826d826 --- /dev/null +++ b/public/images/developer_week_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/open_dropdown.svg b/public/images/open_dropdown.svg new file mode 100644 index 0000000..34f3628 --- /dev/null +++ b/public/images/open_dropdown.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/opengraph/opengraph_dark_san_jose.jpg b/public/images/opengraph/opengraph_dark_san_jose.jpg new file mode 100644 index 0000000..df2d06b Binary files /dev/null and b/public/images/opengraph/opengraph_dark_san_jose.jpg differ diff --git a/public/images/opengraph/opengraph_light_san_jose.jpg b/public/images/opengraph/opengraph_light_san_jose.jpg new file mode 100644 index 0000000..d4f0eb2 Binary files /dev/null and b/public/images/opengraph/opengraph_light_san_jose.jpg differ diff --git a/public/images/speakers/AdityaRohit.jpg b/public/images/speakers/AdityaRohit.jpg new file mode 100644 index 0000000..7a9be66 Binary files /dev/null and b/public/images/speakers/AdityaRohit.jpg differ diff --git a/public/images/speakers/Budhaditya.jpg b/public/images/speakers/Budhaditya.jpg new file mode 100644 index 0000000..78fbe2c Binary files /dev/null and b/public/images/speakers/Budhaditya.jpg differ diff --git a/public/images/speakers/Kuldeepak.jpg b/public/images/speakers/Kuldeepak.jpg new file mode 100644 index 0000000..0863d77 Binary files /dev/null and b/public/images/speakers/Kuldeepak.jpg differ diff --git a/public/images/speakers/Nuwan.jpg b/public/images/speakers/Nuwan.jpg new file mode 100644 index 0000000..7d59f4d Binary files /dev/null and b/public/images/speakers/Nuwan.jpg differ diff --git a/public/images/speakers/Sumit.jpg b/public/images/speakers/Sumit.jpg new file mode 100644 index 0000000..e7498f9 Binary files /dev/null and b/public/images/speakers/Sumit.jpg differ diff --git a/public/paris2025/.nojekyll b/public/paris2025/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/public/paris2025/images/api_masters.png b/public/paris2025/images/api_masters.png new file mode 100644 index 0000000..012f6d0 Binary files /dev/null and b/public/paris2025/images/api_masters.png differ diff --git a/public/paris2025/images/background/background_top_right.jpg b/public/paris2025/images/background/background_top_right.jpg new file mode 100644 index 0000000..da96130 Binary files /dev/null and b/public/paris2025/images/background/background_top_right.jpg differ diff --git a/public/paris2025/images/big_tilde.svg b/public/paris2025/images/big_tilde.svg new file mode 100644 index 0000000..eba3665 --- /dev/null +++ b/public/paris2025/images/big_tilde.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/paris2025/images/bluesky.svg b/public/paris2025/images/bluesky.svg new file mode 100644 index 0000000..005ea7f --- /dev/null +++ b/public/paris2025/images/bluesky.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/paris2025/images/break-fest.svg b/public/paris2025/images/break-fest.svg new file mode 100644 index 0000000..ded43fe --- /dev/null +++ b/public/paris2025/images/break-fest.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/paris2025/images/favicons/favicon-140x140.png b/public/paris2025/images/favicons/favicon-140x140.png new file mode 100644 index 0000000..aee1f95 Binary files /dev/null and b/public/paris2025/images/favicons/favicon-140x140.png differ diff --git a/public/paris2025/images/favicons/favicon-300x300.png b/public/paris2025/images/favicons/favicon-300x300.png new file mode 100644 index 0000000..907565f Binary files /dev/null and b/public/paris2025/images/favicons/favicon-300x300.png differ diff --git a/public/paris2025/images/favicons/favicon.png b/public/paris2025/images/favicons/favicon.png new file mode 100644 index 0000000..af7390c Binary files /dev/null and b/public/paris2025/images/favicons/favicon.png differ diff --git a/public/paris2025/images/img_hosted_by_fost.png b/public/paris2025/images/img_hosted_by_fost.png new file mode 100644 index 0000000..22d0865 Binary files /dev/null and b/public/paris2025/images/img_hosted_by_fost.png differ diff --git a/public/paris2025/images/linkedin_logo.svg b/public/paris2025/images/linkedin_logo.svg new file mode 100644 index 0000000..a307825 --- /dev/null +++ b/public/paris2025/images/linkedin_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/paris2025/images/linkedin_overlay.svg b/public/paris2025/images/linkedin_overlay.svg new file mode 100644 index 0000000..379467f --- /dev/null +++ b/public/paris2025/images/linkedin_overlay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/paris2025/images/logo.svg b/public/paris2025/images/logo.svg new file mode 100644 index 0000000..e002bf4 --- /dev/null +++ b/public/paris2025/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/paris2025/images/openapi_initiative_logo.svg b/public/paris2025/images/openapi_initiative_logo.svg new file mode 100644 index 0000000..f28eb08 --- /dev/null +++ b/public/paris2025/images/openapi_initiative_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/paris2025/images/opengraph/opengraph_dark.jpg b/public/paris2025/images/opengraph/opengraph_dark.jpg new file mode 100644 index 0000000..628decb Binary files /dev/null and b/public/paris2025/images/opengraph/opengraph_dark.jpg differ diff --git a/public/paris2025/images/opengraph/opengraph_light.jpg b/public/paris2025/images/opengraph/opengraph_light.jpg new file mode 100644 index 0000000..bd91c1d Binary files /dev/null and b/public/paris2025/images/opengraph/opengraph_light.jpg differ diff --git a/public/paris2025/images/photos/photo_row_1_1.jpg b/public/paris2025/images/photos/photo_row_1_1.jpg new file mode 100644 index 0000000..ce73538 Binary files /dev/null and b/public/paris2025/images/photos/photo_row_1_1.jpg differ diff --git a/public/paris2025/images/photos/photo_row_1_2.jpg b/public/paris2025/images/photos/photo_row_1_2.jpg new file mode 100644 index 0000000..1c7c5db Binary files /dev/null and b/public/paris2025/images/photos/photo_row_1_2.jpg differ diff --git a/public/paris2025/images/photos/photo_row_1_3.jpg b/public/paris2025/images/photos/photo_row_1_3.jpg new file mode 100644 index 0000000..3ad22dc Binary files /dev/null and b/public/paris2025/images/photos/photo_row_1_3.jpg differ diff --git a/public/paris2025/images/photos/photo_row_1_4.jpg b/public/paris2025/images/photos/photo_row_1_4.jpg new file mode 100644 index 0000000..f1e679b Binary files /dev/null and b/public/paris2025/images/photos/photo_row_1_4.jpg differ diff --git a/public/paris2025/images/photos/photo_row_2_1.jpg b/public/paris2025/images/photos/photo_row_2_1.jpg new file mode 100644 index 0000000..3567ec0 Binary files /dev/null and b/public/paris2025/images/photos/photo_row_2_1.jpg differ diff --git a/public/paris2025/images/photos/photo_row_2_2.jpg b/public/paris2025/images/photos/photo_row_2_2.jpg new file mode 100644 index 0000000..11f2ff7 Binary files /dev/null and b/public/paris2025/images/photos/photo_row_2_2.jpg differ diff --git a/public/paris2025/images/photos/photo_row_2_3.jpg b/public/paris2025/images/photos/photo_row_2_3.jpg new file mode 100644 index 0000000..6f71917 Binary files /dev/null and b/public/paris2025/images/photos/photo_row_2_3.jpg differ diff --git a/public/paris2025/images/photos/photo_row_2_4.jpg b/public/paris2025/images/photos/photo_row_2_4.jpg new file mode 100644 index 0000000..762c8ef Binary files /dev/null and b/public/paris2025/images/photos/photo_row_2_4.jpg differ diff --git a/public/paris2025/images/photos/photo_row_3_1.jpg b/public/paris2025/images/photos/photo_row_3_1.jpg new file mode 100644 index 0000000..d41449c Binary files /dev/null and b/public/paris2025/images/photos/photo_row_3_1.jpg differ diff --git a/public/paris2025/images/photos/photo_row_3_2.jpg b/public/paris2025/images/photos/photo_row_3_2.jpg new file mode 100644 index 0000000..fc1377a Binary files /dev/null and b/public/paris2025/images/photos/photo_row_3_2.jpg differ diff --git a/public/paris2025/images/photos/photo_row_3_3.jpg b/public/paris2025/images/photos/photo_row_3_3.jpg new file mode 100644 index 0000000..e00ad1f Binary files /dev/null and b/public/paris2025/images/photos/photo_row_3_3.jpg differ diff --git a/public/paris2025/images/photos/photo_row_3_4.jpg b/public/paris2025/images/photos/photo_row_3_4.jpg new file mode 100644 index 0000000..89a5664 Binary files /dev/null and b/public/paris2025/images/photos/photo_row_3_4.jpg differ diff --git a/public/paris2025/images/plus.svg b/public/paris2025/images/plus.svg new file mode 100644 index 0000000..5997b34 --- /dev/null +++ b/public/paris2025/images/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/paris2025/images/speakers/Badr.jpg b/public/paris2025/images/speakers/Badr.jpg new file mode 100644 index 0000000..84db78e Binary files /dev/null and b/public/paris2025/images/speakers/Badr.jpg differ diff --git a/public/paris2025/images/speakers/Chris.jpg b/public/paris2025/images/speakers/Chris.jpg new file mode 100644 index 0000000..350ea7e Binary files /dev/null and b/public/paris2025/images/speakers/Chris.jpg differ diff --git a/public/paris2025/images/speakers/Dan.jpg b/public/paris2025/images/speakers/Dan.jpg new file mode 100644 index 0000000..85a9953 Binary files /dev/null and b/public/paris2025/images/speakers/Dan.jpg differ diff --git a/public/paris2025/images/speakers/Dimitri.jpg b/public/paris2025/images/speakers/Dimitri.jpg new file mode 100644 index 0000000..95b0941 Binary files /dev/null and b/public/paris2025/images/speakers/Dimitri.jpg differ diff --git a/public/paris2025/images/speakers/Emmanuel.jpg b/public/paris2025/images/speakers/Emmanuel.jpg new file mode 100644 index 0000000..bce0341 Binary files /dev/null and b/public/paris2025/images/speakers/Emmanuel.jpg differ diff --git a/public/paris2025/images/speakers/Erik.jpg b/public/paris2025/images/speakers/Erik.jpg new file mode 100644 index 0000000..092eaae Binary files /dev/null and b/public/paris2025/images/speakers/Erik.jpg differ diff --git a/public/paris2025/images/speakers/Frank.jpg b/public/paris2025/images/speakers/Frank.jpg new file mode 100644 index 0000000..ef647f0 Binary files /dev/null and b/public/paris2025/images/speakers/Frank.jpg differ diff --git a/public/paris2025/images/speakers/Lorna.jpg b/public/paris2025/images/speakers/Lorna.jpg new file mode 100644 index 0000000..9fa57cc Binary files /dev/null and b/public/paris2025/images/speakers/Lorna.jpg differ diff --git a/public/paris2025/images/speakers/Marjukka.jpg b/public/paris2025/images/speakers/Marjukka.jpg new file mode 100644 index 0000000..c191525 Binary files /dev/null and b/public/paris2025/images/speakers/Marjukka.jpg differ diff --git a/public/paris2025/images/speakers/Miguel.jpg b/public/paris2025/images/speakers/Miguel.jpg new file mode 100644 index 0000000..457155b Binary files /dev/null and b/public/paris2025/images/speakers/Miguel.jpg differ diff --git a/public/paris2025/images/speakers/Naresh.jpg b/public/paris2025/images/speakers/Naresh.jpg new file mode 100644 index 0000000..d6a303b Binary files /dev/null and b/public/paris2025/images/speakers/Naresh.jpg differ diff --git a/public/paris2025/images/speakers/Rahul.jpg b/public/paris2025/images/speakers/Rahul.jpg new file mode 100644 index 0000000..8b8e430 Binary files /dev/null and b/public/paris2025/images/speakers/Rahul.jpg differ diff --git a/public/paris2025/images/speakers/Simon.jpg b/public/paris2025/images/speakers/Simon.jpg new file mode 100644 index 0000000..8f11227 Binary files /dev/null and b/public/paris2025/images/speakers/Simon.jpg differ diff --git a/public/paris2025/images/youtube_logo.svg b/public/paris2025/images/youtube_logo.svg new file mode 100644 index 0000000..60bd531 --- /dev/null +++ b/public/paris2025/images/youtube_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/paris2025/index.html b/public/paris2025/index.html new file mode 100644 index 0000000..b1737dd --- /dev/null +++ b/public/paris2025/index.html @@ -0,0 +1,544 @@ + + + + + + + + + + + OpenAPI Conference Paris 2025 | 11 December, 2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ +
+
+ + OpenAPI Conference Logo + +
+
+ + Hosted by Fost Logo + +
+
+ + +
+
+ OpenAPI + Conference + in Paris +
+
+ + + + + +
+ + +
+
+ 11 December, 2025 +
+ +
+
+
+ Add to
+ calendar +
+
+
+ + + + +
+ + +
+
+
+ CNIT Forest, Paris +
+
+ 2 Pl. de la Defense, 92092 Puteaux, France +
+
+
+ + + +
+
+
+ + +
+
+
+ +
+
Sold Out
+
+ + + +
+
+
+ + +
+
+ + + +
+ + + +
+ + +
+ +
+ + +
+
+
+
+

+ Agenda +

+
+
+
+
+ + + +
+
+
+
+
+ + +
+
+
+
+

+ Previous Events +

+
+
+
+
+ + + +
+
+
+ + +
+ + +
+
+ + + +
+
+ + + + + + + + diff --git a/public/paris2025/main.js b/public/paris2025/main.js new file mode 100644 index 0000000..ffd8fd3 --- /dev/null +++ b/public/paris2025/main.js @@ -0,0 +1,1589 @@ +var __defProp = Object.defineProperty; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + +// src/config.js +var ASSET_VERSION = "1"; +function asset(path) { + if (path.startsWith("/")) { + path = "." + path; + } else if (!path.startsWith("./") && !path.startsWith("../")) { + path = "./" + path; + } + return `${path}?v=${ASSET_VERSION}`; +} + +// src/components/SocialIcons.js +var SocialIcons = class { + constructor() { + this.linkedInUrl = "https://www.linkedin.com/company/open-api-initiative/"; + this.youtubeUrl = "https://www.youtube.com/@OpenApi"; + this.blueskyUrl = "https://bsky.app/profile/openapis.org"; + } + /** + * Render the social icons HTML + * @returns {string} HTML string + */ + render() { + return ` +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ `; + } + /** + * Mount component to a DOM element + * @param {HTMLElement} container - Container element + */ + mount(container) { + if (container) { + container.innerHTML = this.render(); + } + } +}; + +// src/components/CalendarPopup.js +var CalendarPopup = class { + constructor() { + this.isOpen = false; + this.container = null; + this.eventTitle = "OpenAPI Conference Paris 2025"; + this.eventDescription = "Join us at the OpenAPI Conference in Paris. More info: https://conference.openapis.org"; + this.eventLocation = "CNIT Forest, 2 Pl. de la Defense, 92092 Puteaux, France"; + this.startDate = "2025-12-11T09:15:00"; + this.endDate = "2025-12-11T18:00:00"; + this.googleStart = "20251211T091500"; + this.googleEnd = "20251211T180000"; + this.googleCalendarUrl = this.buildGoogleCalendarUrl(); + this.outlookUrl = this.buildOutlookUrl(); + this.icons = { + google: "M19.5 3h-3V1.5h-1.5V3h-6V1.5H7.5V3h-3C3.675 3 3 3.675 3 4.5v15c0 .825.675 1.5 1.5 1.5h15c.825 0 1.5-.675 1.5-1.5v-15c0-.825-.675-1.5-1.5-1.5zm0 16.5h-15V9h15v10.5zM6 10.5h3v3H6v-3zm4.5 0h3v3h-3v-3zm4.5 0h3v3h-3v-3z", + apple: "M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2zm-7 5h5v5h-5v-5z", + outlook: "M21.5 3h-9.13c-.41 0-.8.17-1.08.46L7.2 7.54c-.28.28-.45.67-.45 1.08v9.13c0 .83.67 1.5 1.5 1.5h13.25c.83 0 1.5-.67 1.5-1.5V4.5c0-.83-.67-1.5-1.5-1.5zm0 14.25H8.25V8.62l4.08-4.12h9.17v12.75z", + download: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" + }; + } + /** + * Build Google Calendar URL + */ + buildGoogleCalendarUrl() { + return `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent( + this.eventTitle + )}&dates=${this.googleStart}/${this.googleEnd}&details=${encodeURIComponent( + this.eventDescription + )}&location=${encodeURIComponent(this.eventLocation)}`; + } + /** + * Build Outlook URL + */ + buildOutlookUrl() { + return `https://outlook.office.com/calendar/0/deeplink/compose?subject=${encodeURIComponent( + this.eventTitle + )}&body=${encodeURIComponent(this.eventDescription)}&startdt=${this.startDate}&enddt=${this.endDate}&location=${encodeURIComponent(this.eventLocation)}`; + } + /** + * Generate ICS file content + */ + generateICS() { + const uid = "openapi-conference-2025@openapis.org"; + const dtstamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").split(".")[0] + "Z"; + return `BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//OpenAPI Conference//EN +BEGIN:VEVENT +UID:${uid} +DTSTAMP:${dtstamp} +DTSTART:20251211T091500 +DTEND:20251211T180000 +SUMMARY:${this.eventTitle} +DESCRIPTION:${this.eventDescription} +LOCATION:${this.eventLocation} +END:VEVENT +END:VCALENDAR`; + } + /** + * Download ICS file + */ + downloadICS() { + const ics = this.generateICS(); + const blob = new Blob([ics], { type: "text/calendar;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = "openapi-conference.ics"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + this.close(); + } + /** + * Open popup + */ + open() { + this.isOpen = true; + this.render(); + document.body.style.overflow = "hidden"; + const popup = document.querySelector("[data-calendar-popup]"); + if (popup) { + popup.focus(); + } + } + /** + * Close popup + */ + close() { + this.isOpen = false; + if (this.container) { + this.container.remove(); + this.container = null; + } + document.body.style.overflow = ""; + } + /** + * Handle backdrop click + */ + handleBackdropClick(e) { + if (e.target === e.currentTarget) { + this.close(); + } + } + /** + * Handle escape key + */ + handleEscapeKey(e) { + if (e.key === "Escape" && this.isOpen) { + this.close(); + } + } + /** + * Render popup HTML + */ + render() { + if (!this.isOpen) return; + const existing = document.querySelector("[data-calendar-popup]"); + if (existing) { + existing.remove(); + } + this.container = document.createElement("div"); + this.container.setAttribute("data-calendar-popup", ""); + this.container.innerHTML = ` + + + `; + document.body.appendChild(this.container); + const backdrop = this.container.querySelector("[data-backdrop]"); + const closeBtn = this.container.querySelector("[data-close-btn]"); + const downloadButtons = this.container.querySelectorAll( + "[data-download-ics], [data-download-ics-alt]" + ); + backdrop.addEventListener("click", (e) => this.handleBackdropClick(e)); + closeBtn.addEventListener("click", () => this.close()); + this.escapeHandler = (e) => this.handleEscapeKey(e); + document.addEventListener("keydown", this.escapeHandler); + downloadButtons.forEach((btn) => { + btn.addEventListener("click", (e) => { + e.preventDefault(); + this.downloadICS(); + }); + }); + } + /** + * Initialize the popup (add event listeners to trigger buttons) + */ + init() { + const triggers = document.querySelectorAll("[data-calendar-trigger]"); + triggers.forEach((trigger) => { + trigger.addEventListener("click", (e) => { + e.preventDefault(); + this.open(); + }); + trigger.addEventListener("keydown", (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + this.open(); + } + }); + }); + } + /** + * Cleanup when popup is closed + */ + destroy() { + if (this.escapeHandler) { + document.removeEventListener("keydown", this.escapeHandler); + } + this.close(); + } +}; + +// src/components/AgendaRenderer.js +var AgendaRenderer = class { + constructor(sections) { + this.sections = sections; + this.onItemClick = null; + } + /** + * Get all clickable items (items with speakers) flattened + */ + getClickableItems() { + const items = []; + this.sections.forEach((section) => { + section.items.forEach((item) => { + if (item.speakers && item.speakers.length > 0) { + items.push(item); + } + }); + }); + return items; + } + /** + * Calculate percentage widths for two blocks based on their title lengths + * CRITICAL: Treats "TBD" as length 20 (not 3) for proper width allocation + */ + calculateBlockPercents(title1, title2, bonus = 1.15) { + const length1 = title1 === "TBD" ? 20 : title1.length; + const length2 = title2 === "TBD" ? 20 : title2.length; + let weight1 = Math.sqrt(length1); + let weight2 = Math.sqrt(length2); + if (weight1 > weight2) weight1 *= bonus; + else if (weight2 > weight1) weight2 *= bonus; + const totalWeight = weight1 + weight2; + const width1 = Math.round(weight1 / totalWeight * 1e3) / 10; + const width2 = Math.round((100 - width1) * 10) / 10; + return { width1: width1 + "%", width2: width2 + "%" }; + } + /** + * Calculate percentage widths for three blocks based on their title lengths + * CRITICAL: Treats "TBD" as length 20 (not 3) for proper width allocation + */ + calculateThreeBlockPercents(title1, title2, title3, bonus = 1.15) { + const length1 = title1 === "TBD" ? 20 : title1.length; + const length2 = title2 === "TBD" ? 20 : title2.length; + const length3 = title3 === "TBD" ? 20 : title3.length; + let weight1 = Math.sqrt(length1); + let weight2 = Math.sqrt(length2); + let weight3 = Math.sqrt(length3); + const maxWeight = Math.max(weight1, weight2, weight3); + if (weight1 === maxWeight) weight1 *= bonus; + else if (weight2 === maxWeight) weight2 *= bonus; + else if (weight3 === maxWeight) weight3 *= bonus; + const totalWeight = weight1 + weight2 + weight3; + const width1 = Math.round(weight1 / totalWeight * 1e3) / 10; + const width2 = Math.round(weight2 / totalWeight * 1e3) / 10; + const width3 = Math.round((100 - width1 - width2) * 10) / 10; + return { width1: width1 + "%", width2: width2 + "%", width3: width3 + "%" }; + } + /** + * Chunk array into pairs + */ + chunkIntoPairs(array) { + const pairs = []; + for (let i = 0; i < array.length; i += 2) { + pairs.push(array.slice(i, i + 2)); + } + return pairs; + } + /** + * Render speaker info (single speaker) + */ + renderSpeakerInfo(speaker) { + const tscBadge = speaker.isTscMember ? `TSC` : ""; + const oaiBadge = speaker.isOaiMember ? `OAI` : ""; + return ` +
+ +
+ ${speaker.avatar ? `${speaker.name}` : `
`} +
+ + +
+
+ ${speaker.name}${tscBadge}${oaiBadge} +
+ ${speaker.company ? `
${speaker.company}
` : ""} +
+
+ `; + } + /** + * Render two speakers overlapping + */ + renderTwoSpeakers(speakers) { + return ` +
+
+
+ ${speakers[0].avatar ? `${speakers[0].name}` : `
`} +
+
+ ${speakers[1].avatar ? `${speakers[1].name}` : `
`} +
+
+ +
+
+ ${speakers[0].name} +
+
+ ${speakers[1].name} +
+
+
+ `; + } + /** + * Render agenda item card + */ + renderAgendaItem(item, isLastInRow = false) { + const hoverClass = item.disableHover ? "" : "hover:bg-primary group"; + const borderClass = isLastInRow ? "" : "md:border-r"; + const isClickable = item.speakers && item.speakers.length > 0; + const clickableClass = isClickable ? "cursor-pointer" : ""; + const dataItemId = isClickable ? `data-item-id="${item.id}"` : ""; + return ` +
+ +
+
+ ${item.time} + ${item.category ? ` + / + + ${item.category} + + ` : ""} +
+
+ + +
+ ${item.title} +
+ + +
+ +
+ ${item.speakers && item.speakers.length === 2 ? this.renderTwoSpeakers(item.speakers) : item.speakers && item.speakers.length > 0 ? item.speakers.map((speaker) => this.renderSpeakerInfo(speaker)).join("") : item.badge ? `
+ ${item.badge} +
` : ""} +
+ + + ${item.icon ? ` +
+ +
+ ` : ""} +
+
+ `; + } + /** + * Render section header + */ + renderSectionHeader(section, activeBorder = false) { + const borderTopClass = activeBorder ? "border-t" : ""; + return ` +
+
+ ${section.title} +
+ ${section.timeRange ? ` +
+ ${section.timeRange} +
+ ` : ""} +
+ `; + } + /** + * Render a section with items + */ + renderSection(section, sectionIndex) { + let itemsHtml = ""; + if (sectionIndex === 0 && section.items.length >= 4) { + const widths = this.calculateThreeBlockPercents( + section.items[0].title, + section.items[1].title, + section.items[2].title + ); + itemsHtml = ` +
+
+ ${this.renderAgendaItem(section.items[0])} +
+
+ ${this.renderAgendaItem(section.items[1])} +
+
+ ${this.renderAgendaItem(section.items[2], true)} +
+
+ `; + if (section.items.length > 3) { + const remainingWidths = this.calculateBlockPercents( + section.items[3].title, + "" + ); + itemsHtml += ` +
+ ${this.renderAgendaItem(section.items[3], true)} +
+ `; + } + } else { + const pairs = this.chunkIntoPairs(section.items); + const pairsHtml = pairs.map((pair) => { + if (pair.length === 2) { + const widths = this.calculateBlockPercents( + pair[0].title, + pair[1].title + ); + return ` +
+
+ ${this.renderAgendaItem(pair[0])} +
+
+ ${this.renderAgendaItem(pair[1], true)} +
+
+ `; + } else { + return ` +
+ ${this.renderAgendaItem(pair[0], true)} +
+ `; + } + }).join(""); + itemsHtml = pairsHtml; + } + return ` +
+ ${this.renderSectionHeader(section, sectionIndex === 0)} + ${itemsHtml} +
+ `; + } + /** + * Render all sections + */ + render() { + const sectionsHtml = this.sections.map((section, index) => this.renderSection(section, index)).join(""); + return sectionsHtml; + } + /** + * Initialize click handlers for agenda items + */ + initItemClickHandlers() { + const clickableItems = document.querySelectorAll("[data-item-id]"); + const allItems = this.getClickableItems(); + clickableItems.forEach((element) => { + element.addEventListener("click", () => { + const itemId = element.getAttribute("data-item-id"); + const item = allItems.find((i) => i.id === itemId); + const index = allItems.findIndex((i) => i.id === itemId); + if (item && this.onItemClick) { + this.onItemClick(item, index, allItems); + } + }); + }); + } + /** + * Mount to a DOM element + */ + mount(container) { + if (!container) { + console.error("AgendaRenderer: Container element not found"); + return; + } + container.innerHTML = this.render(); + this.initItemClickHandlers(); + } +}; + +// src/components/AgendaModal.js +var AgendaModal = class { + constructor() { + this.isOpen = false; + this.container = null; + this.currentItem = null; + this.currentIndex = 0; + this.allItems = []; + this.escapeHandler = null; + this.touchStartX = 0; + this.scrollPosition = 0; + } + /** + * Open modal with item data + * @param {Object} item - The agenda item to display + * @param {number} index - Index of the item in the allItems array + * @param {Array} allItems - All agenda items for navigation + */ + open(item, index, allItems) { + this.currentItem = item; + this.currentIndex = index; + this.allItems = allItems; + this.isOpen = true; + this.render(); + this.scrollPosition = window.scrollY; + document.body.style.position = "fixed"; + document.body.style.top = `-${this.scrollPosition}px`; + document.body.style.width = "100%"; + document.body.style.overflow = "hidden"; + const modal = document.querySelector("[data-agenda-modal]"); + if (modal) { + modal.focus(); + } + } + /** + * Close modal + */ + close() { + this.isOpen = false; + if (this.container) { + this.container.remove(); + this.container = null; + } + document.body.style.position = ""; + document.body.style.top = ""; + document.body.style.width = ""; + document.body.style.overflow = ""; + window.scrollTo(0, this.scrollPosition); + if (this.escapeHandler) { + document.removeEventListener("keydown", this.escapeHandler); + this.escapeHandler = null; + } + } + /** + * Navigate to previous item + */ + prev() { + if (this.currentIndex > 0) { + const newIndex = this.currentIndex - 1; + const newItem = this.allItems[newIndex]; + this.currentItem = newItem; + this.currentIndex = newIndex; + this.render(); + } + } + /** + * Navigate to next item + */ + next() { + if (this.currentIndex < this.allItems.length - 1) { + const newIndex = this.currentIndex + 1; + const newItem = this.allItems[newIndex]; + this.currentItem = newItem; + this.currentIndex = newIndex; + this.render(); + } + } + /** + * Handle backdrop click + */ + handleBackdropClick(e) { + if (e.target === e.currentTarget) { + this.close(); + } + } + /** + * Handle escape key + */ + handleEscapeKey(e) { + if (e.key === "Escape" && this.isOpen) { + this.close(); + } + } + /** + * Render speaker section + */ + renderSpeaker(speaker, item) { + const tscBadge = speaker.isTscMember ? `TSC` : ""; + const oaiBadge = speaker.isOaiMember ? `OAI` : ""; + const linkedinIcon = speaker.linkedin ? ` + + + + ` : ""; + const slidesButton = speaker.slidesUrl ? ` + View Slides + ` : ""; + const linkedinButton = speaker.linkedin ? ` + + + + LinkedIn + ` : ""; + const slidesMobileButton = speaker.slidesUrl ? ` + View Slides + ` : ""; + return ` +
+ + + + +
+ ${speaker.name} +
+ ${speaker.name} + ${tscBadge} + ${oaiBadge} +
+ ${speaker.job || speaker.company ? `
${[ + speaker.job, + speaker.company + ].filter(Boolean).join(" / ")}
` : ""} +
+ ${linkedinButton} + ${slidesMobileButton} +
+
+
+ `; + } + /** + * Render modal HTML + */ + render() { + if (!this.isOpen || !this.currentItem) return; + const existing = document.querySelector("[data-agenda-modal]"); + if (existing) { + existing.remove(); + } + const item = this.currentItem; + const hasPrev = this.currentIndex > 0; + const hasNext = this.currentIndex < this.allItems.length - 1; + const nextItem = hasNext ? this.allItems[this.currentIndex + 1] : null; + const timeRange = nextItem ? `${item.time} \u2014 ${nextItem.time}` : item.time; + const speakersHtml = item.speakers && item.speakers.length > 0 ? item.speakers.map((s) => this.renderSpeaker(s, item)).join("") : ""; + this.container = document.createElement("div"); + this.container.setAttribute("data-agenda-modal", ""); + this.container.innerHTML = ` + + + `; + document.body.appendChild(this.container); + const backdrop = this.container.querySelector("[data-backdrop]"); + const closeBtn = this.container.querySelector("[data-close-btn]"); + const prevBtn = this.container.querySelector("[data-prev-btn]"); + const nextBtn = this.container.querySelector("[data-next-btn]"); + backdrop.addEventListener("click", (e) => { + if (e.target === backdrop) { + this.close(); + } + }); + closeBtn.addEventListener("click", () => this.close()); + if (prevBtn && !prevBtn.disabled) { + prevBtn.addEventListener("click", () => this.prev()); + } + if (nextBtn && !nextBtn.disabled) { + nextBtn.addEventListener("click", () => this.next()); + } + this.escapeHandler = (e) => this.handleEscapeKey(e); + document.addEventListener("keydown", this.escapeHandler); + const modal = this.container.querySelector("[data-backdrop] > div"); + let touchStartY = 0; + modal.addEventListener( + "touchstart", + (e) => { + this.touchStartX = e.touches[0].clientX; + touchStartY = e.touches[0].clientY; + }, + { passive: true } + ); + modal.addEventListener( + "touchend", + (e) => { + const touchEndX = e.changedTouches[0].clientX; + const touchEndY = e.changedTouches[0].clientY; + const diffX = this.touchStartX - touchEndX; + const diffY = touchStartY - touchEndY; + const threshold = 50; + if (Math.abs(diffX) > threshold && Math.abs(diffX) > Math.abs(diffY)) { + if (diffX > 0) { + this.next(); + } else { + this.prev(); + } + } + }, + { passive: true } + ); + } + destroy() { + if (this.escapeHandler) { + document.removeEventListener("keydown", this.escapeHandler); + } + this.close(); + } +}; + +// src/components/DualHeaderSection.js +var DualHeaderSection = class { + constructor(options = {}) { + this.title = options.title || ""; + this.subtitle = options.subtitle || ""; + this.backgroundColor = options.backgroundColor || "white"; + this.imageUrl = options.imageUrl || null; + this.iconType = options.iconType || null; + this.imageAlt = options.imageAlt || ""; + this.href = options.href || "#"; + } + /** + * Determine CSS classes based on background color + */ + getColorClasses() { + const textColor = this.backgroundColor === "white" ? "text-text-on-green" : "text-text-primary"; + const subtitleColor = this.backgroundColor === "white" ? "text-primary-gray-on-white" : "text-text-secondary"; + const bgClass = this.backgroundColor === "white" ? "bg-white" : "bg-black"; + return { textColor, subtitleColor, bgClass }; + } + /** + * Render the icon based on type + */ + renderIcon() { + if (this.iconType === "plus") { + return ` +
+ Plus icon +
+ `; + } else if (this.iconType === "arrow-down") { + return ` +
+ + + +
+ `; + } + return ""; + } + /** + * Render the image or icon section + */ + renderMedia() { + if (this.imageUrl) { + return ` + ${this.imageAlt} + `; + } else if (this.iconType) { + return this.renderIcon(); + } + return ""; + } + /** + * Create the component HTML + */ + render() { + const { textColor, subtitleColor, bgClass } = this.getColorClasses(); + const subtitleHtml = this.subtitle ? `
+ ${this.subtitle} +
` : ""; + return ` +
+ + +
+
+ ${this.title} +
+ ${subtitleHtml} +
+ + +
+ ${this.renderMedia()} +
+
+
+ `; + } + /** + * Mount component to a container element + */ + mount(container) { + if (!container) { + console.error("DualHeaderSection: Container element not found"); + return; + } + container.innerHTML = this.render(); + } +}; + +// src/components/PreviousEventsGallery.js +var PreviousEventsGallery = class { + constructor() { + __publicField(this, "handleScroll", () => { + if (this.sliderRef) { + const scrollLeft = this.sliderRef.scrollLeft; + const slideWidth = this.sliderRef.clientWidth; + const newSlide = Math.round(scrollLeft / slideWidth); + if (newSlide !== this.currentSlide) { + this.currentSlide = newSlide; + this.updateDots(); + } + } + }); + this.rows = [ + [ + { url: asset("/images/photos/photo_row_1_1.jpg"), alt: "Event 1" }, + { url: asset("/images/photos/photo_row_1_2.jpg"), alt: "Event 2" }, + { url: asset("/images/photos/photo_row_1_3.jpg"), alt: "Event 3" }, + { url: asset("/images/photos/photo_row_1_4.jpg"), alt: "Event 4" } + ], + [ + { url: asset("/images/photos/photo_row_2_1.jpg"), alt: "Event 5" }, + { url: asset("/images/photos/photo_row_2_2.jpg"), alt: "Event 6" }, + { url: asset("/images/photos/photo_row_2_3.jpg"), alt: "Event 7" }, + { url: asset("/images/photos/photo_row_2_4.jpg"), alt: "Event 8" } + ], + [ + { url: asset("/images/photos/photo_row_3_1.jpg"), alt: "Event 9" }, + { url: asset("/images/photos/photo_row_3_2.jpg"), alt: "Event 10" }, + { url: asset("/images/photos/photo_row_3_3.jpg"), alt: "Event 11" }, + { url: asset("/images/photos/photo_row_3_4.jpg"), alt: "Event 12" } + ] + ]; + this.allImages = this.rows.flat(); + this.currentSlide = 0; + this.sliderRef = null; + this.container = null; + } + goToSlide(index) { + if (this.sliderRef) { + const slideWidth = this.sliderRef.clientWidth; + this.sliderRef.scrollTo({ + left: index * slideWidth, + behavior: "smooth" + }); + } + } + updateDots() { + const dots = this.container.querySelectorAll("[data-dot]"); + dots.forEach((dot, index) => { + if (index === this.currentSlide) { + dot.style.backgroundColor = "#47c552"; + } else { + dot.style.backgroundColor = "#333"; + } + }); + } + renderMobileSlider() { + const slidesHtml = this.allImages.map( + (image) => ` +
+
+
+ ` + ).join(""); + const dotsHtml = this.allImages.map( + (image, index) => ` + + ` + ).join(""); + return ` +
+ +
+ ${slidesHtml} +
+ + +
+ + +
+ ${dotsHtml} +
+
+ `; + } + renderDesktopGrid() { + const rowsHtml = this.rows.map((row) => { + const imagesHtml = row.map( + (image) => ` +
+ ` + ).join(""); + return `
${imagesHtml}
`; + }).join(""); + return ` + + `; + } + render() { + return ` +
+ +
+ ${this.renderMobileSlider()} + ${this.renderDesktopGrid()} +
+
+ `; + } + attachEventListeners() { + this.sliderRef = this.container.querySelector("[data-slider]"); + if (this.sliderRef) { + this.sliderRef.addEventListener("scroll", this.handleScroll); + } + const dots = this.container.querySelectorAll("[data-dot]"); + dots.forEach((dot, index) => { + dot.addEventListener("click", () => this.goToSlide(index)); + }); + } + mount(container) { + if (!container) { + console.error("PreviousEventsGallery: Container element not found"); + return; + } + this.container = container; + container.innerHTML = this.render(); + this.attachEventListeners(); + } + destroy() { + if (this.sliderRef) { + this.sliderRef.removeEventListener("scroll", this.handleScroll); + } + } +}; + +// src/data/agenda.js +var agendaSections = [ + { + id: "foundations", + title: "Foundations", + timeRange: "08:30 \u2014 10:40", + items: [ + { + id: "breakfast", + time: "08:30", + category: "", + title: "Executive Breakfast", + badge: "Invite Only", + speakers: [], + icon: asset("/images/break-fest.svg"), + disableHover: true + }, + { + id: "1", + time: "09:15", + category: "Foundations", + title: "Conference Welcome and OpenAPI in the Age of AI", + description: "Welcome to the OpenAPI Conference! We have an exciting program to share, with presentations talking about new specifications as well as presentations that dive into practices, applications, and an outlook of things are going. We also we have a brief look of where OpenAPI is situated in the age of AI and MCP, and how things are going to develope after the recent publication of version 3.2 of the specification.", + speakers: [ + { + name: "Erik Wilde", + job: "Head of Enterprise Strategy", + company: "Jentic", + avatar: asset("/images/speakers/Erik.jpg"), + linkedin: "https://www.linkedin.com/in/erikwilde/", + isOaiMember: true + } + ] + }, + { + id: "2", + time: "09:45", + category: "Foundations", + title: "What's new in OpenAPI 3.2", + description: "Keeping up with all the new standards all the time can be hard work! Instead, come to this session and get an overview of what's new in the OpenAPI 3.2 release so you know what to expect when it's time to upgrade. You'll hear about support for more HTTP methods, multipart formats, and for handling the full query string as one input. There's a big upgrade to tags, allowing nesting, multiple tag types, and more metadata - replacing the extensions you're probably already using with tags. Also included are document identities, security scheme additions, and upgraded support for XML-format APIs. Come along to learn more and prepare for the future!", + speakers: [ + { + name: "Lorna Mitchell", + job: "API Architect", + company: "TM Forum", + avatar: asset("/images/speakers/Lorna.jpg"), + linkedin: "https://www.linkedin.com/in/lornajane/", + isTscMember: true, + isOaiMember: true + } + ] + }, + { + id: "3", + time: "10:15", + category: "Foundations", + title: "Data Contracts: Treating Data as APIs", + description: "APIs brought order to software development through contracts, lifecycle management, and clear ownership. Yet in data, chaos often reigns: schema drift, silent breakages, and no shared language between producers and consumers. Enter the Open Data Contract Standard (ODCS), an open initiative under the Linux Foundation that applies API thinking to data. Think of OpenAPI, but for data: a contract-first approach that enables robust data pipelines, clearly defined interfaces, and enforceable guarantees across teams. This talk will show how ODCS enables API-like discipline in data sharing, supports contract-driven development for data products, and introduces schema validation and lifecycle management into data workflows. These capabilities lay the foundation for scalable data marketplaces within organizations, making data assets discoverable, reliable, and reusable. As an outlook, we'll explore the emerging Open Data Product Standard (ODPS), which builds on ODCS by grouping contracts into coherent, reusable building blocks. Together, ODCS and ODPS pave the way for a composable data architecture that aligns with data mesh and data-as-a-product principles.", + speakers: [ + { + name: "Dr. Simon Harrer", + job: "Co-Founder and CEO", + company: "Entropy Data", + avatar: asset("/images/speakers/Simon.jpg"), + linkedin: "https://www.linkedin.com/in/simonharrer/" + } + ] + } + ] + }, + { + id: "practices", + title: "Practices", + timeRange: "11:00 \u2014 12:55", + items: [ + { + id: "4", + time: "11:00", + category: "Practices", + title: "From Zero to Spec-Hero: Eliminating Lean Wastes When Adopting OpenAPI", + description: "Many organisations recognise the value of the OpenAPI Specification yet struggle to make it part of their everyday API delivery. In this session you'll learn how to treat OpenAPI not as a checkbox, but as a Lean instrument for eliminating waste in API design, implementation and consumption. We'll unpack the six major barriers that keep teams from transitioning, map each barrier to a Lean waste, and then walk through a practical adoption roadmap\u2014starting from one API, embedding spec-first thinking, applying governance and tooling, and measuring the outcomes. If you're ready to move from spec-ignored to spec-embedded, this talk shows you how.", + speakers: [ + { + name: "Marjukka Niinioja", + job: "Founding Partner", + company: "Osaango", + avatar: asset("/images/speakers/Marjukka.jpg"), + linkedin: "https://www.linkedin.com/in/marjukkaniinioja/" + } + ] + }, + { + id: "5", + time: "11:30", + category: "Practices", + title: "How the Dutch Government Uses an OpenAPI-First Approach to Leverage Developer Experience", + description: "The Dutch government recently launched a new OpenAPI-first API Register, designed to make public sector APIs easier to discover, understand, and reuse. Unlike traditional catalogs, the register is built around OpenAPI specifications as the single source of truth. This enables automated validation, consistent documentation, and machine-readable contracts right from the start. To further ensure quality and consistency, the register applies the Dutch API Strategy's API Design Rules. These rules standardize everything from security schemes to error handling, so developers know what to expect when working with APIs from different ministries or municipalities. The result is a more predictable and streamlined developer experience. The register also looks beyond single APIs. By supporting the emerging Arazzo standard, it can describe how multiple APIs interact to deliver complete government use cases \u2014 for example, registering a new business or applying for permits. This brings context and guidance to developers who need to orchestrate across domains.", + speakers: [ + { + name: "Dimitri van Hees", + company: "Government of the Netherlands", + avatar: asset("/images/speakers/Dimitri.jpg"), + linkedin: "https://www.linkedin.com/in/dimitrivanhees/" + } + ] + }, + { + id: "6", + time: "12:00", + category: "Practices", + title: "From REST to Events: API Workflow Testing and Mocking with a Single Arazzo Spec", + description: "APIs rarely work in isolation. Real-world usage involves multiple steps across both synchronous REST calls and asynchronous events, where the outcome of each step determines the journey a particular interaction takes. While testing individual endpoints is necessary, it's not sufficient. It is equally important to validate how those endpoints and events work together as part of a real workflow. Enter the Arazzo Specification V1.1, which describes complete workflows including inputs, outputs, step dependencies, and success/failure criteria, across OpenAPI (REST) and AsyncAPI (events). In this talk, we'll demonstrate how you can leverage Arazzo to drive end-to-end API workflow testing and mocking in a completely no-code manner.", + speakers: [ + { + name: "Naresh Jain", + job: "Founder, CEO", + company: "Specmatic", + avatar: asset("/images/speakers/Naresh.jpg"), + linkedin: "https://www.linkedin.com/in/nareshjain/" + } + ] + }, + { + id: "7", + time: "12:30", + category: "Practices", + title: "You may have OpenAPI, but is it AI-Ready?", + description: "I have worked with and reviewed thousands of APIs across startups, enterprises, and global platforms. Almost all shipped an OpenAPI Description. Yet through the lens of AI systems and intelligent agents, most failed a simple test. They were designed for humans (often badly), not machines. In this session I will share what I have learned from helping organisations make their APIs truly AI-ready, breaking down the six dimensions that determine whether an API can be understood, reasoned over, discovered, and safely executed by intelligent systems. These include foundational compliance, developer experience, semantic clarity, agent usability, security, and AI discoverability. Using the Jentic API Scoring Framework as a visual guide, I will show how to assess AI-readiness, where most teams stumble, and the simple changes that dramatically improve both human and machine understanding.", + speakers: [ + { + name: "Frank Kilcommins", + job: "Head of Enterprise Architecture", + company: "Jentic", + avatar: asset("/images/speakers/Frank.jpg"), + linkedin: "https://www.linkedin.com/in/frank-kilcommins", + isOaiMember: true + } + ] + } + ] + }, + { + id: "applications", + title: "Applications", + timeRange: "14:00 \u2014 15:25", + items: [ + { + id: "8", + time: "14:00", + category: "Applications", + title: "What's All The Fuss About TypeSpec?", + description: "Ok, so you know OpenAPI and you love OpenAPI. You cannot imagine any other means to describe your APIs. You understand Operations Objects, Security Schemes, and the role Schema Objects play in well-described request and response payloads. You trust it, and so does your developer community. Enter TypeSpec, the new kid on the block for modelling APIs. In your ongoing love affair with OpenAPI, why should you care? In this session we'll take a look at TypeSpec from the angle of an ardent OpenAPI fan and look at what TypeSpec can actually do for you.", + speakers: [ + { + name: "Chris Wood", + job: "Principal Architect", + company: "Ozone API", + avatar: asset("/images/speakers/Chris.jpg"), + linkedin: "https://www.linkedin.com/in/sensiblewood/", + isOaiMember: true, + isOaiMember: true + } + ] + }, + { + id: "9", + time: "14:30", + category: "Applications", + title: "Control Surfaces in OpenAPI: Designing Specs for Task, Plan, and Agent Modes", + description: "Your API is a control panel \u2014 but what controls should your OpenAPI specification expose? The answer depends on whether you're building for tasks, plans, or agents. A simple task needs input schemas and error responses. A multi-step plan requires lifecycle endpoints (pause/resume) and progress schemas. An autonomous agent demands budget parameters, approval callbacks, and emergency stop operations. Get the spec wrong, and you'll frustrate users trying to pause an atomic operation or micromanage a self-optimizing system. This talk explores how different AI operational modes\u2014task, plan, and agent\u2014require fundamentally different API control surfaces, and how to express these in OpenAPI. Through real-world specification examples and anti-patterns, you'll learn what endpoints, parameters, and schemas each mode needs, why mode mismatches cause design pain, and how to structure your OpenAPI specifications to expose the right level of control. Whether you're documenting a simple REST endpoint or a complex autonomous system, understanding control surfaces will help you design more intuitive and maintainable OpenAPI specifications.", + speakers: [ + { + name: "Miguel Quintero", + job: "Technical Trainer", + company: "Postman", + avatar: asset("/images/speakers/Miguel.jpg"), + linkedin: "https://www.linkedin.com/in/miguel-quintero-a558531/", + isTscMember: true, + isOaiMember: true + } + ] + }, + { + id: "10", + time: "15:00", + category: "Applications", + title: "Spec-First API Designs Without Codegen", + description: "The talk is about keeping the two things that matter at the centre: The users and the spec, the OpenAPI spec and how to design your code around it. It talks about the various real life challenges of not doing this and things like generating code and how popular open source tooling can address this. It gives a practical way to approach this problem and work with teams at scale.", + speakers: [ + { + name: "Rahul D\xE9", + job: "VP, Platform and Site Reliability Engineering, Public Cloud", + company: "Citi", + avatar: asset("/images/speakers/Rahul.jpg"), + linkedin: "https://www.linkedin.com/in/lispyclouds" + } + ] + } + ] + }, + { + id: "looking-glass", + title: "The Looking Glass", + timeRange: "15:55 \u2014 16:50", + items: [ + { + id: "11", + time: "15:55", + category: "The Looking Glass", + title: "OpenAPI and Spring-Boot 4 - What's New?", + description: "Spring Boot remains the most widely used Java framework for modern application development, powering millions of applications globally. With Spring Boot 4, the framework enters a new era of performance, cloud-native support, and developer productivity. This talk will showcase the key innovations in Spring Boot 4 that make it a powerful choice for building scalable APIs. A major focus will be on OpenAPI and its integration via springdoc-openapi, a community-driven project that now exceeds 30 million downloads per month. We'll dive into the core features of springdoc-openapi, including support for Scalar project in addition to swagger-ui, Spring MVC, WebFlux, GraalVM, Kotlin. You'll also learn how to leverage advanced features like actuator integration, Javadoc reuse for API descriptions, HATEOAS, Data REST, and OAuth2 security.", + speakers: [ + { + name: "Badr Nass Lahsen", + job: "Lead Cloud and Security Architect", + company: "CyberArk", + avatar: asset("/images/speakers/Badr.jpg"), + linkedin: "https://www.linkedin.com/in/nasslahsen/" + } + ] + }, + { + id: "12", + time: "16:25", + category: "The Looking Glass", + title: "Is OpenAPI still relevant in the age of AI?", + description: "Count the times when you said 'OpenAPI' and others heard 'OpenAI'... Just when API Design, machine-readable API documentation, and OpenAPI have finally been normalized and gained traction, AI and agents throw a wrench into the works. At a time when you can vibecode an API in minutes and instantly stand up an MCP server, how useful is it to write and maintain OpenAPI documents? Join me to examine where and how OpenAPI remains relevant for your organization & product delivery and where it's no longer that useful (and what we could do instead).", + speakers: [ + { + name: "Emmanuel Paraskakis", + job: "Founder", + company: "Level 250", + avatar: asset("/images/speakers/Emmanuel.jpg"), + linkedin: "https://www.linkedin.com/in/emmanuelparaskakis/" + } + ] + } + ] + } +]; + +// src/main.js +function init() { + const headerSocialContainer = document.getElementById("header-social-icons"); + if (headerSocialContainer) { + const headerSocial = new SocialIcons(); + headerSocial.mount(headerSocialContainer); + } + const footerSocialMobile = document.getElementById( + "footer-social-icons-mobile" + ); + if (footerSocialMobile) { + const footerSocial = new SocialIcons(); + footerSocial.mount(footerSocialMobile); + } + const footerSocialDesktop = document.getElementById( + "footer-social-icons-desktop" + ); + if (footerSocialDesktop) { + const footerSocial = new SocialIcons(); + footerSocial.mount(footerSocialDesktop); + } + const calendarPopup = new CalendarPopup(); + calendarPopup.init(); + const agendaModal = new AgendaModal(); + const agendaContainer = document.getElementById("agenda-container"); + if (agendaContainer) { + const agendaRenderer = new AgendaRenderer(agendaSections); + agendaRenderer.onItemClick = (item, index, allItems) => { + agendaModal.open(item, index, allItems); + }; + agendaRenderer.mount(agendaContainer); + } + const learnMoreContainer = document.getElementById("learn-more-section"); + if (learnMoreContainer) { + const learnMore = new DualHeaderSection({ + title: "Want to learn more?", + subtitle: "Check out Masterclasses by API Masters on December 10th", + imageUrl: asset("/images/api_masters.png"), + imageAlt: "API Masters Logo", + href: "https://apimasters.fr/our-masterclasses/entry/353/", + backgroundColor: "white" + }); + learnMore.mount(learnMoreContainer); + } + const galleryContainer = document.getElementById("previous-events-gallery"); + if (galleryContainer) { + const gallery = new PreviousEventsGallery(); + gallery.mount(galleryContainer); + } + const subscribeContainer = document.getElementById("subscribe-section"); + if (subscribeContainer) { + const subscribe = new DualHeaderSection({ + title: "Subscribe", + subtitle: "For OpenAPI Initiative Updates", + iconType: "plus", + href: "https://www.openapis.org/#footer-outer", + backgroundColor: "white" + }); + subscribe.mount(subscribeContainer); + } +} +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); +} else { + init(); +} +export { + init +}; +//# sourceMappingURL=main.js.map diff --git a/public/paris2025/main.js.map b/public/paris2025/main.js.map new file mode 100644 index 0000000..59a7378 --- /dev/null +++ b/public/paris2025/main.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../src/config.js", "../src/components/SocialIcons.js", "../src/components/CalendarPopup.js", "../src/components/AgendaRenderer.js", "../src/components/AgendaModal.js", "../src/components/DualHeaderSection.js", "../src/components/PreviousEventsGallery.js", "../src/data/agenda.js", "../src/main.js"], + "sourcesContent": ["/**\n * Configuration and Helper Functions\n *\n * Provides base path handling for assets\n */\n\n// Asset version for cache busting - increment when deploying changes\nconst ASSET_VERSION = \"1\";\n\n// BASE_PATH kept for backwards compatibility if used elsewhere\nconst BASE_PATH = import.meta.env.BASE_PATH || \"\";\n\n/**\n * Asset path helper - returns relative paths for portability\n *\n * @param {string} path - Asset path (e.g., /images/logo.svg)\n * @returns {string} - Relative asset path (e.g., ./images/logo.svg?v=1)\n *\n * @example\n * asset('/images/logo.svg') // Returns './images/logo.svg?v=1'\n */\nexport function asset(path) {\n // Convert to relative path for portability\n if (path.startsWith(\"/\")) {\n path = \".\" + path;\n } else if (!path.startsWith(\"./\") && !path.startsWith(\"../\")) {\n path = \"./\" + path;\n }\n return `${path}?v=${ASSET_VERSION}`;\n}\n\n/**\n * Configuration object\n */\nexport const config = {\n BASE_PATH,\n EVENT_DATE: \"2025-12-11T09:15:00\",\n EVENT_LOCATION: \"CNIT Forest, 2 Pl. de la Defense, 92092 Puteaux, France\",\n SOCIAL_LINKS: {\n linkedin: \"https://www.linkedin.com/company/openapis-org\",\n youtube: \"https://www.youtube.com/@OpenAPIsOrg\",\n openapis: \"https://openapis.org\",\n },\n};\n", "/**\n * SocialIcons Component\n *\n * Renders LinkedIn, YouTube, and Bluesky social media icons\n * Reusable in header and footer\n */\n\nexport class SocialIcons {\n constructor() {\n this.linkedInUrl = \"https://www.linkedin.com/company/open-api-initiative/\";\n this.youtubeUrl = \"https://www.youtube.com/@OpenApi\";\n this.blueskyUrl = \"https://bsky.app/profile/openapis.org\";\n }\n\n /**\n * Render the social icons HTML\n * @returns {string} HTML string\n */\n render() {\n return `\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n
\n `;\n }\n\n /**\n * Mount component to a DOM element\n * @param {HTMLElement} container - Container element\n */\n mount(container) {\n if (container) {\n container.innerHTML = this.render();\n }\n }\n}\n", "/**\n * CalendarPopup Component\n *\n * Modal popup for adding event to calendar\n * Supports Google Calendar, Outlook, Apple Calendar, and .ics download\n */\n\nexport class CalendarPopup {\n constructor() {\n this.isOpen = false;\n this.container = null;\n\n // Event details\n this.eventTitle = \"OpenAPI Conference Paris 2025\";\n this.eventDescription =\n \"Join us at the OpenAPI Conference in Paris. More info: https://conference.openapis.org\";\n this.eventLocation =\n \"CNIT Forest, 2 Pl. de la Defense, 92092 Puteaux, France\";\n this.startDate = \"2025-12-11T09:15:00\";\n this.endDate = \"2025-12-11T18:00:00\";\n\n // Format dates for different calendars\n this.googleStart = \"20251211T091500\";\n this.googleEnd = \"20251211T180000\";\n\n // Pre-calculate URLs\n this.googleCalendarUrl = this.buildGoogleCalendarUrl();\n this.outlookUrl = this.buildOutlookUrl();\n\n // Calendar option icons (SVG paths)\n this.icons = {\n google:\n \"M19.5 3h-3V1.5h-1.5V3h-6V1.5H7.5V3h-3C3.675 3 3 3.675 3 4.5v15c0 .825.675 1.5 1.5 1.5h15c.825 0 1.5-.675 1.5-1.5v-15c0-.825-.675-1.5-1.5-1.5zm0 16.5h-15V9h15v10.5zM6 10.5h3v3H6v-3zm4.5 0h3v3h-3v-3zm4.5 0h3v3h-3v-3z\",\n apple:\n \"M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2zm-7 5h5v5h-5v-5z\",\n outlook:\n \"M21.5 3h-9.13c-.41 0-.8.17-1.08.46L7.2 7.54c-.28.28-.45.67-.45 1.08v9.13c0 .83.67 1.5 1.5 1.5h13.25c.83 0 1.5-.67 1.5-1.5V4.5c0-.83-.67-1.5-1.5-1.5zm0 14.25H8.25V8.62l4.08-4.12h9.17v12.75z\",\n download: \"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\",\n };\n }\n\n /**\n * Build Google Calendar URL\n */\n buildGoogleCalendarUrl() {\n return `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(\n this.eventTitle\n )}&dates=${this.googleStart}/${this.googleEnd}&details=${encodeURIComponent(\n this.eventDescription\n )}&location=${encodeURIComponent(this.eventLocation)}`;\n }\n\n /**\n * Build Outlook URL\n */\n buildOutlookUrl() {\n return `https://outlook.office.com/calendar/0/deeplink/compose?subject=${encodeURIComponent(\n this.eventTitle\n )}&body=${encodeURIComponent(this.eventDescription)}&startdt=${\n this.startDate\n }&enddt=${this.endDate}&location=${encodeURIComponent(this.eventLocation)}`;\n }\n\n /**\n * Generate ICS file content\n */\n generateICS() {\n const uid = \"openapi-conference-2025@openapis.org\";\n const dtstamp =\n new Date().toISOString().replace(/[-:]/g, \"\").split(\".\")[0] + \"Z\";\n\n return `BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//OpenAPI Conference//EN\nBEGIN:VEVENT\nUID:${uid}\nDTSTAMP:${dtstamp}\nDTSTART:20251211T091500\nDTEND:20251211T180000\nSUMMARY:${this.eventTitle}\nDESCRIPTION:${this.eventDescription}\nLOCATION:${this.eventLocation}\nEND:VEVENT\nEND:VCALENDAR`;\n }\n\n /**\n * Download ICS file\n */\n downloadICS() {\n const ics = this.generateICS();\n const blob = new Blob([ics], { type: \"text/calendar;charset=utf-8\" });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = \"openapi-conference.ics\";\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n this.close();\n }\n\n /**\n * Open popup\n */\n open() {\n this.isOpen = true;\n this.render();\n\n // Prevent body scroll\n document.body.style.overflow = \"hidden\";\n\n // Focus trap\n const popup = document.querySelector(\"[data-calendar-popup]\");\n if (popup) {\n popup.focus();\n }\n }\n\n /**\n * Close popup\n */\n close() {\n this.isOpen = false;\n if (this.container) {\n this.container.remove();\n this.container = null;\n }\n\n // Restore body scroll\n document.body.style.overflow = \"\";\n }\n\n /**\n * Handle backdrop click\n */\n handleBackdropClick(e) {\n if (e.target === e.currentTarget) {\n this.close();\n }\n }\n\n /**\n * Handle escape key\n */\n handleEscapeKey(e) {\n if (e.key === \"Escape\" && this.isOpen) {\n this.close();\n }\n }\n\n /**\n * Render popup HTML\n */\n render() {\n if (!this.isOpen) return;\n\n // Remove existing popup\n const existing = document.querySelector(\"[data-calendar-popup]\");\n if (existing) {\n existing.remove();\n }\n\n // Create container\n this.container = document.createElement(\"div\");\n this.container.setAttribute(\"data-calendar-popup\", \"\");\n this.container.innerHTML = `\n \n \n \n \n \n
\n
\n Add to Calendar\n
\n \n \n \n \n \n
\n\n \n
\n \n \n \n \n \n Google Calendar\n \n\n \n \n \n \n \n Apple Calendar\n \n\n \n \n \n \n \n Outlook\n \n\n \n \n \n \n \n Download .ics\n \n
\n \n \n `;\n\n // Add to body\n document.body.appendChild(this.container);\n\n // Add event listeners\n const backdrop = this.container.querySelector(\"[data-backdrop]\");\n const closeBtn = this.container.querySelector(\"[data-close-btn]\");\n const downloadButtons = this.container.querySelectorAll(\n \"[data-download-ics], [data-download-ics-alt]\"\n );\n\n backdrop.addEventListener(\"click\", (e) => this.handleBackdropClick(e));\n closeBtn.addEventListener(\"click\", () => this.close());\n\n // Escape key listener\n this.escapeHandler = (e) => this.handleEscapeKey(e);\n document.addEventListener(\"keydown\", this.escapeHandler);\n\n downloadButtons.forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n e.preventDefault();\n this.downloadICS();\n });\n });\n }\n\n /**\n * Initialize the popup (add event listeners to trigger buttons)\n */\n init() {\n // Find all calendar trigger buttons\n const triggers = document.querySelectorAll(\"[data-calendar-trigger]\");\n triggers.forEach((trigger) => {\n trigger.addEventListener(\"click\", (e) => {\n e.preventDefault();\n this.open();\n });\n\n // Keyboard support for calendar button\n trigger.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n this.open();\n }\n });\n });\n }\n\n /**\n * Cleanup when popup is closed\n */\n destroy() {\n if (this.escapeHandler) {\n document.removeEventListener(\"keydown\", this.escapeHandler);\n }\n this.close();\n }\n}\n", "/**\n * AgendaRenderer Component\n *\n * Renders agenda sections with dynamic width calculations\n * Includes special handling for \"TBD\" items (treats as length 20)\n */\n\nexport class AgendaRenderer {\n constructor(sections) {\n this.sections = sections;\n this.onItemClick = null;\n }\n\n /**\n * Get all clickable items (items with speakers) flattened\n */\n getClickableItems() {\n const items = [];\n this.sections.forEach((section) => {\n section.items.forEach((item) => {\n if (item.speakers && item.speakers.length > 0) {\n items.push(item);\n }\n });\n });\n return items;\n }\n\n /**\n * Calculate percentage widths for two blocks based on their title lengths\n * CRITICAL: Treats \"TBD\" as length 20 (not 3) for proper width allocation\n */\n calculateBlockPercents(title1, title2, bonus = 1.15) {\n // Treat \"TBD\" as if it were longer to give it more width\n const length1 = title1 === \"TBD\" ? 20 : title1.length;\n const length2 = title2 === \"TBD\" ? 20 : title2.length;\n\n // Compute the \"weight\" of each title using the square root\n let weight1 = Math.sqrt(length1);\n let weight2 = Math.sqrt(length2);\n\n // Apply bonus to the longer block\n if (weight1 > weight2) weight1 *= bonus;\n else if (weight2 > weight1) weight2 *= bonus;\n\n // Normalize and convert to percentage, rounded to 0.1%\n const totalWeight = weight1 + weight2;\n const width1 = Math.round((weight1 / totalWeight) * 1000) / 10;\n const width2 = Math.round((100 - width1) * 10) / 10;\n\n return { width1: width1 + \"%\", width2: width2 + \"%\" };\n }\n\n /**\n * Calculate percentage widths for three blocks based on their title lengths\n * CRITICAL: Treats \"TBD\" as length 20 (not 3) for proper width allocation\n */\n calculateThreeBlockPercents(title1, title2, title3, bonus = 1.15) {\n // Treat \"TBD\" as if it were longer to give it more width\n const length1 = title1 === \"TBD\" ? 20 : title1.length;\n const length2 = title2 === \"TBD\" ? 20 : title2.length;\n const length3 = title3 === \"TBD\" ? 20 : title3.length;\n\n // Compute the \"weight\" of each title using the square root\n let weight1 = Math.sqrt(length1);\n let weight2 = Math.sqrt(length2);\n let weight3 = Math.sqrt(length3);\n\n // Find the maximum weight\n const maxWeight = Math.max(weight1, weight2, weight3);\n\n // Apply bonus to the longest block\n if (weight1 === maxWeight) weight1 *= bonus;\n else if (weight2 === maxWeight) weight2 *= bonus;\n else if (weight3 === maxWeight) weight3 *= bonus;\n\n // Normalize and convert to percentage, rounded to 0.1%\n const totalWeight = weight1 + weight2 + weight3;\n const width1 = Math.round((weight1 / totalWeight) * 1000) / 10;\n const width2 = Math.round((weight2 / totalWeight) * 1000) / 10;\n const width3 = Math.round((100 - width1 - width2) * 10) / 10;\n\n return { width1: width1 + \"%\", width2: width2 + \"%\", width3: width3 + \"%\" };\n }\n\n /**\n * Chunk array into pairs\n */\n chunkIntoPairs(array) {\n const pairs = [];\n for (let i = 0; i < array.length; i += 2) {\n pairs.push(array.slice(i, i + 2));\n }\n return pairs;\n }\n\n /**\n * Render speaker info (single speaker)\n */\n renderSpeakerInfo(speaker) {\n const tscBadge = speaker.isTscMember\n ? `TSC`\n : \"\";\n\n const oaiBadge = speaker.isOaiMember\n ? `OAI`\n : \"\";\n\n return `\n
\n \n
\n ${\n speaker.avatar\n ? `\"${speaker.name}\"`\n : `
`\n }\n
\n\n \n
\n
\n ${speaker.name}${tscBadge}${oaiBadge}\n
\n ${\n speaker.company\n ? `
${speaker.company}
`\n : \"\"\n }\n
\n
\n `;\n }\n\n /**\n * Render two speakers overlapping\n */\n renderTwoSpeakers(speakers) {\n return `\n
\n
\n
\n ${\n speakers[0].avatar\n ? `\"${speakers[0].name}\"`\n : `
`\n }\n
\n
\n ${\n speakers[1].avatar\n ? `\"${speakers[1].name}\"`\n : `
`\n }\n
\n
\n \n
\n
\n ${speakers[0].name}\n
\n
\n ${speakers[1].name}\n
\n
\n
\n `;\n }\n\n /**\n * Render agenda item card\n */\n renderAgendaItem(item, isLastInRow = false) {\n const hoverClass = item.disableHover ? \"\" : \"hover:bg-primary group\";\n const borderClass = isLastInRow ? \"\" : \"md:border-r\";\n const isClickable = item.speakers && item.speakers.length > 0;\n const clickableClass = isClickable ? \"cursor-pointer\" : \"\";\n const dataItemId = isClickable ? `data-item-id=\"${item.id}\"` : \"\";\n\n return `\n
\n \n
\n
\n ${item.time}\n ${\n item.category\n ? `\n /\n \n ${item.category}\n \n `\n : \"\"\n }\n
\n
\n\n \n
\n ${item.title}\n
\n\n \n
\n \n
\n ${\n item.speakers && item.speakers.length === 2\n ? this.renderTwoSpeakers(item.speakers)\n : item.speakers && item.speakers.length > 0\n ? item.speakers\n .map((speaker) => this.renderSpeakerInfo(speaker))\n .join(\"\")\n : item.badge\n ? `
\n ${item.badge}\n
`\n : \"\"\n }\n
\n\n \n ${\n item.icon\n ? `\n
\n \"\"\n
\n `\n : \"\"\n }\n
\n
\n `;\n }\n\n /**\n * Render section header\n */\n renderSectionHeader(section, activeBorder = false) {\n const borderTopClass = activeBorder ? \"border-t\" : \"\";\n\n return `\n
\n
\n ${section.title}\n
\n ${\n section.timeRange\n ? `\n
\n ${section.timeRange}\n
\n `\n : \"\"\n }\n
\n `;\n }\n\n /**\n * Render a section with items\n */\n renderSection(section, sectionIndex) {\n let itemsHtml = \"\";\n\n // First section: 3 items in one row\n if (sectionIndex === 0 && section.items.length >= 4) {\n const widths = this.calculateThreeBlockPercents(\n section.items[0].title,\n section.items[1].title,\n section.items[2].title\n );\n\n itemsHtml = `\n
\n
\n ${this.renderAgendaItem(section.items[0])}\n
\n
\n ${this.renderAgendaItem(section.items[1])}\n
\n
\n ${this.renderAgendaItem(section.items[2], true)}\n
\n
\n `;\n\n // Render remaining item (item[3]) if exists\n if (section.items.length > 3) {\n const remainingWidths = this.calculateBlockPercents(\n section.items[3].title,\n \"\"\n );\n\n itemsHtml += `\n
\n ${this.renderAgendaItem(section.items[3], true)}\n
\n `;\n }\n } else {\n // Other sections: pairs layout\n const pairs = this.chunkIntoPairs(section.items);\n const pairsHtml = pairs\n .map((pair) => {\n if (pair.length === 2) {\n const widths = this.calculateBlockPercents(\n pair[0].title,\n pair[1].title\n );\n return `\n
\n
\n ${this.renderAgendaItem(pair[0])}\n
\n
\n ${this.renderAgendaItem(pair[1], true)}\n
\n
\n `;\n } else {\n // Single item (odd number)\n return `\n
\n ${this.renderAgendaItem(pair[0], true)}\n
\n `;\n }\n })\n .join(\"\");\n\n itemsHtml = pairsHtml;\n }\n\n return `\n
\n ${this.renderSectionHeader(section, sectionIndex === 0)}\n ${itemsHtml}\n
\n `;\n }\n\n /**\n * Render all sections\n */\n render() {\n const sectionsHtml = this.sections\n .map((section, index) => this.renderSection(section, index))\n .join(\"\");\n\n return sectionsHtml;\n }\n\n /**\n * Initialize click handlers for agenda items\n */\n initItemClickHandlers() {\n const clickableItems = document.querySelectorAll(\"[data-item-id]\");\n const allItems = this.getClickableItems();\n\n clickableItems.forEach((element) => {\n element.addEventListener(\"click\", () => {\n const itemId = element.getAttribute(\"data-item-id\");\n const item = allItems.find((i) => i.id === itemId);\n const index = allItems.findIndex((i) => i.id === itemId);\n\n if (item && this.onItemClick) {\n this.onItemClick(item, index, allItems);\n }\n });\n });\n }\n\n /**\n * Mount to a DOM element\n */\n mount(container) {\n if (!container) {\n console.error(\"AgendaRenderer: Container element not found\");\n return;\n }\n container.innerHTML = this.render();\n this.initItemClickHandlers();\n }\n}\n", "/**\n * AgendaModal Component\n *\n * Modal popup for displaying agenda item details\n * Responsive design: fullscreen on mobile, centered modal on tablet+\n */\n\nexport class AgendaModal {\n constructor() {\n this.isOpen = false;\n this.container = null;\n this.currentItem = null;\n this.currentIndex = 0;\n this.allItems = [];\n this.escapeHandler = null;\n this.touchStartX = 0;\n this.scrollPosition = 0;\n }\n\n /**\n * Open modal with item data\n * @param {Object} item - The agenda item to display\n * @param {number} index - Index of the item in the allItems array\n * @param {Array} allItems - All agenda items for navigation\n */\n open(item, index, allItems) {\n this.currentItem = item;\n this.currentIndex = index;\n this.allItems = allItems;\n this.isOpen = true;\n this.render();\n\n // Prevent body scroll\n this.scrollPosition = window.scrollY;\n document.body.style.position = \"fixed\";\n document.body.style.top = `-${this.scrollPosition}px`;\n document.body.style.width = \"100%\";\n document.body.style.overflow = \"hidden\";\n\n // Focus the modal\n const modal = document.querySelector(\"[data-agenda-modal]\");\n if (modal) {\n modal.focus();\n }\n }\n\n /**\n * Close modal\n */\n close() {\n this.isOpen = false;\n if (this.container) {\n this.container.remove();\n this.container = null;\n }\n\n // Restore body scroll\n document.body.style.position = \"\";\n document.body.style.top = \"\";\n document.body.style.width = \"\";\n document.body.style.overflow = \"\";\n window.scrollTo(0, this.scrollPosition);\n\n // Remove escape handler\n if (this.escapeHandler) {\n document.removeEventListener(\"keydown\", this.escapeHandler);\n this.escapeHandler = null;\n }\n }\n\n /**\n * Navigate to previous item\n */\n prev() {\n if (this.currentIndex > 0) {\n const newIndex = this.currentIndex - 1;\n const newItem = this.allItems[newIndex];\n this.currentItem = newItem;\n this.currentIndex = newIndex;\n this.render();\n }\n }\n\n /**\n * Navigate to next item\n */\n next() {\n if (this.currentIndex < this.allItems.length - 1) {\n const newIndex = this.currentIndex + 1;\n const newItem = this.allItems[newIndex];\n this.currentItem = newItem;\n this.currentIndex = newIndex;\n this.render();\n }\n }\n\n /**\n * Handle backdrop click\n */\n handleBackdropClick(e) {\n if (e.target === e.currentTarget) {\n this.close();\n }\n }\n\n /**\n * Handle escape key\n */\n handleEscapeKey(e) {\n if (e.key === \"Escape\" && this.isOpen) {\n this.close();\n }\n }\n\n /**\n * Render speaker section\n */\n renderSpeaker(speaker, item) {\n const tscBadge = speaker.isTscMember\n ? `TSC`\n : \"\";\n\n const oaiBadge = speaker.isOaiMember\n ? `OAI`\n : \"\";\n\n // Desktop LinkedIn icon\n const linkedinIcon = speaker.linkedin\n ? `\n \n \n \n `\n : \"\";\n\n // Desktop View Slides button\n const slidesButton = speaker.slidesUrl\n ? `\n View Slides\n `\n : \"\";\n\n // Mobile LinkedIn button\n const linkedinButton = speaker.linkedin\n ? `\n \n \n \n LinkedIn\n `\n : \"\";\n\n // Mobile View Slides button\n const slidesMobileButton = speaker.slidesUrl\n ? `\n View Slides\n `\n : \"\";\n\n return `\n
\n \n
\n
\n \n
\n
\n ${\n speaker.name\n }\n ${tscBadge}\n ${oaiBadge}\n
\n ${\n speaker.job || speaker.company\n ? `
${[\n speaker.job,\n speaker.company,\n ]\n .filter(Boolean)\n .join(\" / \")}
`\n : \"\"\n }\n
\n
\n
\n ${linkedinIcon}\n ${slidesButton}\n
\n
\n\n \n
\n \n
\n ${\n speaker.name\n }\n ${tscBadge}\n ${oaiBadge}\n
\n ${\n speaker.job || speaker.company\n ? `
${[\n speaker.job,\n speaker.company,\n ]\n .filter(Boolean)\n .join(\" / \")}
`\n : \"\"\n }\n
\n ${linkedinButton}\n ${slidesMobileButton}\n
\n
\n
\n `;\n }\n\n /**\n * Render modal HTML\n */\n render() {\n if (!this.isOpen || !this.currentItem) return;\n\n const existing = document.querySelector(\"[data-agenda-modal]\");\n if (existing) {\n existing.remove();\n }\n\n const item = this.currentItem;\n const hasPrev = this.currentIndex > 0;\n const hasNext = this.currentIndex < this.allItems.length - 1;\n\n // Calculate time range (end time from next item's start time)\n const nextItem = hasNext ? this.allItems[this.currentIndex + 1] : null;\n const timeRange = nextItem ? `${item.time} \u2014 ${nextItem.time}` : item.time;\n\n // Render speakers\n const speakersHtml =\n item.speakers && item.speakers.length > 0\n ? item.speakers.map((s) => this.renderSpeaker(s, item)).join(\"\")\n : \"\";\n\n // Create container\n this.container = document.createElement(\"div\");\n this.container.setAttribute(\"data-agenda-modal\", \"\");\n this.container.innerHTML = `\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n \n
\n ${item.time}${\n item.category\n ? `/${item.category}`\n : \"\"\n }\n
\n\n \n

\n ${item.title}\n

\n\n \n
\n ${timeRange}\n
\n\n \n ${speakersHtml ? `
${speakersHtml}
` : \"\"}\n\n \n ${\n item.description\n ? `

${item.description}

`\n : \"\"\n }\n
\n\n \n
div]:bg-primary [&:has(button:not(:disabled):active)>div]:bg-primary-green-dark\">\n \n \n \n \n \n Previous\n \n
\n \n Next\n \n \n \n \n \n
\n \n \n `;\n\n // Add to body\n document.body.appendChild(this.container);\n\n // Add event listeners\n const backdrop = this.container.querySelector(\"[data-backdrop]\");\n const closeBtn = this.container.querySelector(\"[data-close-btn]\");\n const prevBtn = this.container.querySelector(\"[data-prev-btn]\");\n const nextBtn = this.container.querySelector(\"[data-next-btn]\");\n\n backdrop.addEventListener(\"click\", (e) => {\n if (e.target === backdrop) {\n this.close();\n }\n });\n\n closeBtn.addEventListener(\"click\", () => this.close());\n\n if (prevBtn && !prevBtn.disabled) {\n prevBtn.addEventListener(\"click\", () => this.prev());\n }\n\n if (nextBtn && !nextBtn.disabled) {\n nextBtn.addEventListener(\"click\", () => this.next());\n }\n\n // Escape key listener\n this.escapeHandler = (e) => this.handleEscapeKey(e);\n document.addEventListener(\"keydown\", this.escapeHandler);\n\n // Touch swipe navigation (mobile)\n const modal = this.container.querySelector(\"[data-backdrop] > div\");\n let touchStartY = 0;\n\n modal.addEventListener(\n \"touchstart\",\n (e) => {\n this.touchStartX = e.touches[0].clientX;\n touchStartY = e.touches[0].clientY;\n },\n { passive: true }\n );\n\n modal.addEventListener(\n \"touchend\",\n (e) => {\n const touchEndX = e.changedTouches[0].clientX;\n const touchEndY = e.changedTouches[0].clientY;\n const diffX = this.touchStartX - touchEndX;\n const diffY = touchStartY - touchEndY;\n const threshold = 50;\n\n if (Math.abs(diffX) > threshold && Math.abs(diffX) > Math.abs(diffY)) {\n if (diffX > 0) {\n this.next();\n } else {\n this.prev();\n }\n }\n },\n { passive: true }\n );\n }\n\n destroy() {\n if (this.escapeHandler) {\n document.removeEventListener(\"keydown\", this.escapeHandler);\n }\n this.close();\n }\n}\n", "/**\n * DualHeaderSection Component\n *\n * A clickable promotional section with title, subtitle, and image/icon\n * Used for \"Want to learn more?\" and \"Subscribe\" sections\n */\n\nimport { asset } from \"../config.js\";\n\nexport class DualHeaderSection {\n constructor(options = {}) {\n this.title = options.title || \"\";\n this.subtitle = options.subtitle || \"\";\n this.backgroundColor = options.backgroundColor || \"white\";\n this.imageUrl = options.imageUrl || null;\n this.iconType = options.iconType || null; // 'plus' or 'arrow-down'\n this.imageAlt = options.imageAlt || \"\";\n this.href = options.href || \"#\";\n }\n\n /**\n * Determine CSS classes based on background color\n */\n getColorClasses() {\n const textColor =\n this.backgroundColor === \"white\"\n ? \"text-text-on-green\"\n : \"text-text-primary\";\n const subtitleColor =\n this.backgroundColor === \"white\"\n ? \"text-primary-gray-on-white\"\n : \"text-text-secondary\";\n const bgClass = this.backgroundColor === \"white\" ? \"bg-white\" : \"bg-black\";\n\n return { textColor, subtitleColor, bgClass };\n }\n\n /**\n * Render the icon based on type\n */\n renderIcon() {\n if (this.iconType === \"plus\") {\n return `\n
\n \n
\n `;\n } else if (this.iconType === \"arrow-down\") {\n return `\n
\n \n \n \n
\n `;\n }\n return \"\";\n }\n\n /**\n * Render the image or icon section\n */\n renderMedia() {\n if (this.imageUrl) {\n return `\n \n `;\n } else if (this.iconType) {\n return this.renderIcon();\n }\n return \"\";\n }\n\n /**\n * Create the component HTML\n */\n render() {\n const { textColor, subtitleColor, bgClass } = this.getColorClasses();\n const subtitleHtml = this.subtitle\n ? `
\n ${this.subtitle}\n
`\n : \"\";\n\n return `\n
\n \n \n
\n
\n ${this.title}\n
\n ${subtitleHtml}\n
\n\n \n
\n ${this.renderMedia()}\n
\n \n
\n `;\n }\n\n /**\n * Mount component to a container element\n */\n mount(container) {\n if (!container) {\n console.error(\"DualHeaderSection: Container element not found\");\n return;\n }\n container.innerHTML = this.render();\n }\n}\n", "/**\n * PreviousEventsGallery Component\n *\n * Mobile: Horizontal image slider with dot navigation\n * Desktop: Grid layout (3 rows x 4 columns)\n */\n\nimport { asset } from \"../config.js\";\n\nexport class PreviousEventsGallery {\n constructor() {\n // Image data organized in rows (desktop layout)\n this.rows = [\n [\n { url: asset(\"/images/photos/photo_row_1_1.jpg\"), alt: \"Event 1\" },\n { url: asset(\"/images/photos/photo_row_1_2.jpg\"), alt: \"Event 2\" },\n { url: asset(\"/images/photos/photo_row_1_3.jpg\"), alt: \"Event 3\" },\n { url: asset(\"/images/photos/photo_row_1_4.jpg\"), alt: \"Event 4\" },\n ],\n [\n { url: asset(\"/images/photos/photo_row_2_1.jpg\"), alt: \"Event 5\" },\n { url: asset(\"/images/photos/photo_row_2_2.jpg\"), alt: \"Event 6\" },\n { url: asset(\"/images/photos/photo_row_2_3.jpg\"), alt: \"Event 7\" },\n { url: asset(\"/images/photos/photo_row_2_4.jpg\"), alt: \"Event 8\" },\n ],\n [\n { url: asset(\"/images/photos/photo_row_3_1.jpg\"), alt: \"Event 9\" },\n { url: asset(\"/images/photos/photo_row_3_2.jpg\"), alt: \"Event 10\" },\n { url: asset(\"/images/photos/photo_row_3_3.jpg\"), alt: \"Event 11\" },\n { url: asset(\"/images/photos/photo_row_3_4.jpg\"), alt: \"Event 12\" },\n ],\n ];\n\n // Flat array for mobile slider\n this.allImages = this.rows.flat();\n\n // State\n this.currentSlide = 0;\n this.sliderRef = null;\n this.container = null;\n }\n\n handleScroll = () => {\n if (this.sliderRef) {\n const scrollLeft = this.sliderRef.scrollLeft;\n const slideWidth = this.sliderRef.clientWidth;\n const newSlide = Math.round(scrollLeft / slideWidth);\n\n if (newSlide !== this.currentSlide) {\n this.currentSlide = newSlide;\n this.updateDots();\n }\n }\n };\n\n goToSlide(index) {\n if (this.sliderRef) {\n const slideWidth = this.sliderRef.clientWidth;\n this.sliderRef.scrollTo({\n left: index * slideWidth,\n behavior: \"smooth\",\n });\n }\n }\n\n updateDots() {\n const dots = this.container.querySelectorAll(\"[data-dot]\");\n dots.forEach((dot, index) => {\n if (index === this.currentSlide) {\n dot.style.backgroundColor = \"#47c552\";\n } else {\n dot.style.backgroundColor = \"#333\";\n }\n });\n }\n\n renderMobileSlider() {\n const slidesHtml = this.allImages\n .map(\n (image) => `\n
\n
\n \n `\n )\n .join(\"\");\n\n const dotsHtml = this.allImages\n .map(\n (image, index) => `\n \n `\n )\n .join(\"\");\n\n return `\n
\n \n \n ${slidesHtml}\n
\n\n \n
\n\n \n
\n ${dotsHtml}\n
\n \n `;\n }\n\n renderDesktopGrid() {\n const rowsHtml = this.rows\n .map((row) => {\n const imagesHtml = row\n .map(\n (image) => `\n \n `\n )\n .join(\"\");\n\n return `
${imagesHtml}
`;\n })\n .join(\"\");\n\n return `\n
\n ${rowsHtml}\n
\n `;\n }\n\n render() {\n return `\n
\n \n
\n ${this.renderMobileSlider()}\n ${this.renderDesktopGrid()}\n
\n
\n `;\n }\n\n attachEventListeners() {\n this.sliderRef = this.container.querySelector(\"[data-slider]\");\n\n if (this.sliderRef) {\n this.sliderRef.addEventListener(\"scroll\", this.handleScroll);\n }\n\n const dots = this.container.querySelectorAll(\"[data-dot]\");\n dots.forEach((dot, index) => {\n dot.addEventListener(\"click\", () => this.goToSlide(index));\n });\n }\n\n mount(container) {\n if (!container) {\n console.error(\"PreviousEventsGallery: Container element not found\");\n return;\n }\n\n this.container = container;\n container.innerHTML = this.render();\n this.attachEventListeners();\n }\n\n destroy() {\n if (this.sliderRef) {\n this.sliderRef.removeEventListener(\"scroll\", this.handleScroll);\n }\n }\n}\n", "/**\n * Agenda Data\n *\n * Contains all conference agenda sections and items\n */\n\nimport { asset } from \"../config.js\";\n\nexport const agendaSections = [\n {\n id: \"foundations\",\n title: \"Foundations\",\n timeRange: \"08:30 \u2014 10:40\",\n items: [\n {\n id: \"breakfast\",\n time: \"08:30\",\n category: \"\",\n title: \"Executive Breakfast\",\n badge: \"Invite Only\",\n speakers: [],\n icon: asset(\"/images/break-fest.svg\"),\n disableHover: true,\n },\n {\n id: \"1\",\n time: \"09:15\",\n category: \"Foundations\",\n title: \"Conference Welcome and OpenAPI in the Age of AI\",\n description:\n \"Welcome to the OpenAPI Conference! We have an exciting program to share, with presentations talking about new specifications as well as presentations that dive into practices, applications, and an outlook of things are going. We also we have a brief look of where OpenAPI is situated in the age of AI and MCP, and how things are going to develope after the recent publication of version 3.2 of the specification.\",\n speakers: [\n {\n name: \"Erik Wilde\",\n job: \"Head of Enterprise Strategy\",\n company: \"Jentic\",\n avatar: asset(\"/images/speakers/Erik.jpg\"),\n linkedin: \"https://www.linkedin.com/in/erikwilde/\",\n isOaiMember: true,\n },\n ],\n },\n {\n id: \"2\",\n time: \"09:45\",\n category: \"Foundations\",\n title: \"What's new in OpenAPI 3.2\",\n description:\n \"Keeping up with all the new standards all the time can be hard work! Instead, come to this session and get an overview of what's new in the OpenAPI 3.2 release so you know what to expect when it's time to upgrade. You'll hear about support for more HTTP methods, multipart formats, and for handling the full query string as one input. There's a big upgrade to tags, allowing nesting, multiple tag types, and more metadata - replacing the extensions you're probably already using with tags. Also included are document identities, security scheme additions, and upgraded support for XML-format APIs. Come along to learn more and prepare for the future!\",\n speakers: [\n {\n name: \"Lorna Mitchell\",\n job: \"API Architect\",\n company: \"TM Forum\",\n avatar: asset(\"/images/speakers/Lorna.jpg\"),\n linkedin: \"https://www.linkedin.com/in/lornajane/\",\n isTscMember: true,\n isOaiMember: true,\n },\n ],\n },\n {\n id: \"3\",\n time: \"10:15\",\n category: \"Foundations\",\n title: \"Data Contracts: Treating Data as APIs\",\n description:\n \"APIs brought order to software development through contracts, lifecycle management, and clear ownership. Yet in data, chaos often reigns: schema drift, silent breakages, and no shared language between producers and consumers. Enter the Open Data Contract Standard (ODCS), an open initiative under the Linux Foundation that applies API thinking to data. Think of OpenAPI, but for data: a contract-first approach that enables robust data pipelines, clearly defined interfaces, and enforceable guarantees across teams. This talk will show how ODCS enables API-like discipline in data sharing, supports contract-driven development for data products, and introduces schema validation and lifecycle management into data workflows. These capabilities lay the foundation for scalable data marketplaces within organizations, making data assets discoverable, reliable, and reusable. As an outlook, we'll explore the emerging Open Data Product Standard (ODPS), which builds on ODCS by grouping contracts into coherent, reusable building blocks. Together, ODCS and ODPS pave the way for a composable data architecture that aligns with data mesh and data-as-a-product principles.\",\n speakers: [\n {\n name: \"Dr. Simon Harrer\",\n job: \"Co-Founder and CEO\",\n company: \"Entropy Data\",\n avatar: asset(\"/images/speakers/Simon.jpg\"),\n linkedin: \"https://www.linkedin.com/in/simonharrer/\",\n },\n ],\n },\n ],\n },\n {\n id: \"practices\",\n title: \"Practices\",\n timeRange: \"11:00 \u2014 12:55\",\n items: [\n {\n id: \"4\",\n time: \"11:00\",\n category: \"Practices\",\n title:\n \"From Zero to Spec-Hero: Eliminating Lean Wastes When Adopting OpenAPI\",\n description:\n \"Many organisations recognise the value of the OpenAPI Specification yet struggle to make it part of their everyday API delivery. In this session you'll learn how to treat OpenAPI not as a checkbox, but as a Lean instrument for eliminating waste in API design, implementation and consumption. We'll unpack the six major barriers that keep teams from transitioning, map each barrier to a Lean waste, and then walk through a practical adoption roadmap\u2014starting from one API, embedding spec-first thinking, applying governance and tooling, and measuring the outcomes. If you're ready to move from spec-ignored to spec-embedded, this talk shows you how.\",\n speakers: [\n {\n name: \"Marjukka Niinioja\",\n job: \"Founding Partner\",\n company: \"Osaango\",\n avatar: asset(\"/images/speakers/Marjukka.jpg\"),\n linkedin: \"https://www.linkedin.com/in/marjukkaniinioja/\",\n },\n ],\n },\n {\n id: \"5\",\n time: \"11:30\",\n category: \"Practices\",\n title:\n \"How the Dutch Government Uses an OpenAPI-First Approach to Leverage Developer Experience\",\n description:\n \"The Dutch government recently launched a new OpenAPI-first API Register, designed to make public sector APIs easier to discover, understand, and reuse. Unlike traditional catalogs, the register is built around OpenAPI specifications as the single source of truth. This enables automated validation, consistent documentation, and machine-readable contracts right from the start. To further ensure quality and consistency, the register applies the Dutch API Strategy's API Design Rules. These rules standardize everything from security schemes to error handling, so developers know what to expect when working with APIs from different ministries or municipalities. The result is a more predictable and streamlined developer experience. The register also looks beyond single APIs. By supporting the emerging Arazzo standard, it can describe how multiple APIs interact to deliver complete government use cases \u2014 for example, registering a new business or applying for permits. This brings context and guidance to developers who need to orchestrate across domains.\",\n speakers: [\n {\n name: \"Dimitri van Hees\",\n company: \"Government of the Netherlands\",\n avatar: asset(\"/images/speakers/Dimitri.jpg\"),\n linkedin: \"https://www.linkedin.com/in/dimitrivanhees/\",\n },\n ],\n },\n {\n id: \"6\",\n time: \"12:00\",\n category: \"Practices\",\n title:\n \"From REST to Events: API Workflow Testing and Mocking with a Single Arazzo Spec\",\n description:\n \"APIs rarely work in isolation. Real-world usage involves multiple steps across both synchronous REST calls and asynchronous events, where the outcome of each step determines the journey a particular interaction takes. While testing individual endpoints is necessary, it's not sufficient. It is equally important to validate how those endpoints and events work together as part of a real workflow. Enter the Arazzo Specification V1.1, which describes complete workflows including inputs, outputs, step dependencies, and success/failure criteria, across OpenAPI (REST) and AsyncAPI (events). In this talk, we'll demonstrate how you can leverage Arazzo to drive end-to-end API workflow testing and mocking in a completely no-code manner.\",\n speakers: [\n {\n name: \"Naresh Jain\",\n job: \"Founder, CEO\",\n company: \"Specmatic\",\n avatar: asset(\"/images/speakers/Naresh.jpg\"),\n linkedin: \"https://www.linkedin.com/in/nareshjain/\",\n },\n ],\n },\n {\n id: \"7\",\n time: \"12:30\",\n category: \"Practices\",\n title: \"You may have OpenAPI, but is it AI-Ready?\",\n description:\n \"I have worked with and reviewed thousands of APIs across startups, enterprises, and global platforms. Almost all shipped an OpenAPI Description. Yet through the lens of AI systems and intelligent agents, most failed a simple test. They were designed for humans (often badly), not machines. In this session I will share what I have learned from helping organisations make their APIs truly AI-ready, breaking down the six dimensions that determine whether an API can be understood, reasoned over, discovered, and safely executed by intelligent systems. These include foundational compliance, developer experience, semantic clarity, agent usability, security, and AI discoverability. Using the Jentic API Scoring Framework as a visual guide, I will show how to assess AI-readiness, where most teams stumble, and the simple changes that dramatically improve both human and machine understanding.\",\n speakers: [\n {\n name: \"Frank Kilcommins\",\n job: \"Head of Enterprise Architecture\",\n company: \"Jentic\",\n avatar: asset(\"/images/speakers/Frank.jpg\"),\n linkedin: \"https://www.linkedin.com/in/frank-kilcommins\",\n isOaiMember: true,\n },\n ],\n },\n ],\n },\n {\n id: \"applications\",\n title: \"Applications\",\n timeRange: \"14:00 \u2014 15:25\",\n items: [\n {\n id: \"8\",\n time: \"14:00\",\n category: \"Applications\",\n title: \"What's All The Fuss About TypeSpec?\",\n description:\n \"Ok, so you know OpenAPI and you love OpenAPI. You cannot imagine any other means to describe your APIs. You understand Operations Objects, Security Schemes, and the role Schema Objects play in well-described request and response payloads. You trust it, and so does your developer community. Enter TypeSpec, the new kid on the block for modelling APIs. In your ongoing love affair with OpenAPI, why should you care? In this session we'll take a look at TypeSpec from the angle of an ardent OpenAPI fan and look at what TypeSpec can actually do for you.\",\n speakers: [\n {\n name: \"Chris Wood\",\n job: \"Principal Architect\",\n company: \"Ozone API\",\n avatar: asset(\"/images/speakers/Chris.jpg\"),\n linkedin: \"https://www.linkedin.com/in/sensiblewood/\",\n isOaiMember: true,\n isOaiMember: true,\n },\n ],\n },\n {\n id: \"9\",\n time: \"14:30\",\n category: \"Applications\",\n title:\n \"Control Surfaces in OpenAPI: Designing Specs for Task, Plan, and Agent Modes\",\n description:\n \"Your API is a control panel \u2014 but what controls should your OpenAPI specification expose? The answer depends on whether you're building for tasks, plans, or agents. A simple task needs input schemas and error responses. A multi-step plan requires lifecycle endpoints (pause/resume) and progress schemas. An autonomous agent demands budget parameters, approval callbacks, and emergency stop operations. Get the spec wrong, and you'll frustrate users trying to pause an atomic operation or micromanage a self-optimizing system. This talk explores how different AI operational modes\u2014task, plan, and agent\u2014require fundamentally different API control surfaces, and how to express these in OpenAPI. Through real-world specification examples and anti-patterns, you'll learn what endpoints, parameters, and schemas each mode needs, why mode mismatches cause design pain, and how to structure your OpenAPI specifications to expose the right level of control. Whether you're documenting a simple REST endpoint or a complex autonomous system, understanding control surfaces will help you design more intuitive and maintainable OpenAPI specifications.\",\n speakers: [\n {\n name: \"Miguel Quintero\",\n job: \"Technical Trainer\",\n company: \"Postman\",\n avatar: asset(\"/images/speakers/Miguel.jpg\"),\n linkedin: \"https://www.linkedin.com/in/miguel-quintero-a558531/\",\n isTscMember: true,\n isOaiMember: true,\n },\n ],\n },\n {\n id: \"10\",\n time: \"15:00\",\n category: \"Applications\",\n title: \"Spec-First API Designs Without Codegen\",\n description:\n \"The talk is about keeping the two things that matter at the centre: The users and the spec, the OpenAPI spec and how to design your code around it. It talks about the various real life challenges of not doing this and things like generating code and how popular open source tooling can address this. It gives a practical way to approach this problem and work with teams at scale.\",\n speakers: [\n {\n name: \"Rahul D\u00E9\",\n job: \"VP, Platform and Site Reliability Engineering, Public Cloud\",\n company: \"Citi\",\n avatar: asset(\"/images/speakers/Rahul.jpg\"),\n linkedin: \"https://www.linkedin.com/in/lispyclouds\",\n },\n ],\n },\n ],\n },\n {\n id: \"looking-glass\",\n title: \"The Looking Glass\",\n timeRange: \"15:55 \u2014 16:50\",\n items: [\n {\n id: \"11\",\n time: \"15:55\",\n category: \"The Looking Glass\",\n title: \"OpenAPI and Spring-Boot 4 - What's New?\",\n description:\n \"Spring Boot remains the most widely used Java framework for modern application development, powering millions of applications globally. With Spring Boot 4, the framework enters a new era of performance, cloud-native support, and developer productivity. This talk will showcase the key innovations in Spring Boot 4 that make it a powerful choice for building scalable APIs. A major focus will be on OpenAPI and its integration via springdoc-openapi, a community-driven project that now exceeds 30 million downloads per month. We'll dive into the core features of springdoc-openapi, including support for Scalar project in addition to swagger-ui, Spring MVC, WebFlux, GraalVM, Kotlin. You'll also learn how to leverage advanced features like actuator integration, Javadoc reuse for API descriptions, HATEOAS, Data REST, and OAuth2 security.\",\n speakers: [\n {\n name: \"Badr Nass Lahsen\",\n job: \"Lead Cloud and Security Architect\",\n company: \"CyberArk\",\n avatar: asset(\"/images/speakers/Badr.jpg\"),\n linkedin: \"https://www.linkedin.com/in/nasslahsen/\",\n },\n ],\n },\n {\n id: \"12\",\n time: \"16:25\",\n category: \"The Looking Glass\",\n title: \"Is OpenAPI still relevant in the age of AI?\",\n description:\n \"Count the times when you said 'OpenAPI' and others heard 'OpenAI'... Just when API Design, machine-readable API documentation, and OpenAPI have finally been normalized and gained traction, AI and agents throw a wrench into the works. At a time when you can vibecode an API in minutes and instantly stand up an MCP server, how useful is it to write and maintain OpenAPI documents? Join me to examine where and how OpenAPI remains relevant for your organization & product delivery and where it's no longer that useful (and what we could do instead).\",\n speakers: [\n {\n name: \"Emmanuel Paraskakis\",\n job: \"Founder\",\n company: \"Level 250\",\n avatar: asset(\"/images/speakers/Emmanuel.jpg\"),\n linkedin: \"https://www.linkedin.com/in/emmanuelparaskakis/\",\n },\n ],\n },\n ],\n },\n];\n", "/**\n * Main Entry Point\n *\n * Initializes the application when DOM is ready\n */\n\nimport { asset, config } from \"./config.js\";\nimport { SocialIcons } from \"./components/SocialIcons.js\";\nimport { CountdownTimer } from \"./components/CountdownTimer.js\";\nimport { CalendarPopup } from \"./components/CalendarPopup.js\";\nimport { AgendaRenderer } from \"./components/AgendaRenderer.js\";\nimport { AgendaModal } from \"./components/AgendaModal.js\";\nimport { DualHeaderSection } from \"./components/DualHeaderSection.js\";\nimport { PreviousEventsGallery } from \"./components/PreviousEventsGallery.js\";\nimport { agendaSections } from \"./data/agenda.js\";\n\n/**\n * Initialize application\n */\nfunction init() {\n // Initialize Social Icons (Header)\n const headerSocialContainer = document.getElementById(\"header-social-icons\");\n if (headerSocialContainer) {\n const headerSocial = new SocialIcons();\n headerSocial.mount(headerSocialContainer);\n }\n\n // Initialize Social Icons (Footer Mobile)\n const footerSocialMobile = document.getElementById(\n \"footer-social-icons-mobile\"\n );\n if (footerSocialMobile) {\n const footerSocial = new SocialIcons();\n footerSocial.mount(footerSocialMobile);\n }\n\n // Initialize Social Icons (Footer Desktop)\n const footerSocialDesktop = document.getElementById(\n \"footer-social-icons-desktop\"\n );\n if (footerSocialDesktop) {\n const footerSocial = new SocialIcons();\n footerSocial.mount(footerSocialDesktop);\n }\n\n // Initialize Countdown Timer\n /*\n const countdownContainer = document.getElementById(\"countdown-timer\");\n if (countdownContainer) {\n const countdown = new CountdownTimer(config.EVENT_DATE);\n countdown.mount(countdownContainer);\n }*/\n\n // Initialize Calendar Popup\n const calendarPopup = new CalendarPopup();\n calendarPopup.init();\n\n // Initialize Agenda Modal\n const agendaModal = new AgendaModal();\n\n // Initialize Agenda\n const agendaContainer = document.getElementById(\"agenda-container\");\n if (agendaContainer) {\n const agendaRenderer = new AgendaRenderer(agendaSections);\n\n // Wire up click handler to open modal\n agendaRenderer.onItemClick = (item, index, allItems) => {\n agendaModal.open(item, index, allItems);\n };\n\n agendaRenderer.mount(agendaContainer);\n }\n\n // Initialize \"Want to learn more?\" section\n const learnMoreContainer = document.getElementById(\"learn-more-section\");\n if (learnMoreContainer) {\n const learnMore = new DualHeaderSection({\n title: \"Want to learn more?\",\n subtitle: \"Check out Masterclasses by API Masters on December 10th\",\n imageUrl: asset(\"/images/api_masters.png\"),\n imageAlt: \"API Masters Logo\",\n href: \"https://apimasters.fr/our-masterclasses/entry/353/\",\n backgroundColor: \"white\",\n });\n learnMore.mount(learnMoreContainer);\n }\n\n // Initialize Previous Events Gallery\n const galleryContainer = document.getElementById(\"previous-events-gallery\");\n if (galleryContainer) {\n const gallery = new PreviousEventsGallery();\n gallery.mount(galleryContainer);\n }\n\n // Initialize Subscribe section\n const subscribeContainer = document.getElementById(\"subscribe-section\");\n if (subscribeContainer) {\n const subscribe = new DualHeaderSection({\n title: \"Subscribe\",\n subtitle: \"For OpenAPI Initiative Updates\",\n iconType: \"plus\",\n href: \"https://www.openapis.org/#footer-outer\",\n backgroundColor: \"white\",\n });\n subscribe.mount(subscribeContainer);\n }\n}\n\nif (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", init);\n} else {\n init();\n}\n\nexport { init };\n"], + "mappings": "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", + "names": [] +} diff --git a/public/paris2025/output.css b/public/paris2025/output.css new file mode 100644 index 0000000..113c6cf --- /dev/null +++ b/public/paris2025/output.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-scroll-snap-strictness:proximity;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.8125rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--radius-lg:.5rem;--radius-xl:.75rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--spacing-10\.5:2.625rem;--spacing-13:3.25rem;--spacing-18:4.5rem;--spacing-26:6.5rem;--spacing-30:7.5rem;--spacing-40:10rem;--spacing-44:11rem;--color-primary:#47c552;--color-primary-green-dark:#338a3a;--color-primary-green-muted:#4d7051;--color-primary-gray:#a3a3a3;--color-primary-gray-on-white:#5c5c5c;--color-bg-dark:#000;--color-bg-dark-elevated:#0a0a0a;--color-text-primary:#fff;--color-text-secondary:#ffffff7a;--color-text-on-green:#000;--color-border-primary:#4d4d4d}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.top-6{top:calc(var(--spacing)*6)}.right-6{right:calc(var(--spacing)*6)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-7{bottom:calc(var(--spacing)*7)}.left-0{left:calc(var(--spacing)*0)}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.container{width:100%}@media (min-width:390px){.container{max-width:390px}}@media (min-width:960px){.container{max-width:960px}}@media (min-width:1180px){.container{max-width:1180px}}@media (min-width:1728px){.container{max-width:1728px}}@media (min-width:2440px){.container{max-width:2440px}}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.-mt-6{margin-top:calc(var(--spacing)*-6)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-auto{margin-top:auto}.-mr-1{margin-right:calc(var(--spacing)*-1)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.mb-10{margin-bottom:calc(var(--spacing)*10)}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.aspect-4\/3{aspect-ratio:4/3}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-10\.5{height:var(--spacing-10\.5)}.h-11{height:calc(var(--spacing)*11)}.h-12{height:calc(var(--spacing)*12)}.h-13{height:var(--spacing-13)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-18{height:var(--spacing-18)}.h-20{height:calc(var(--spacing)*20)}.h-\[32px\]{height:32px}.h-\[33px\]{height:33px}.h-\[152px\]{height:152px}.h-\[159px\]{height:159px}.h-\[181px\]{height:181px}.h-\[400px\]{height:400px}.h-\[667px\]{height:667px}.h-\[clamp\(180px\,50vw\+16px\,231px\)\]{height:clamp(180px,50vw + 16px,231px)}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.min-h-\[210px\]{min-height:210px}.min-h-\[300px\]{min-height:300px}.min-h-screen{min-height:100vh}.w-1\/4{width:25%}.w-2{width:calc(var(--spacing)*2)}.w-2\/10{width:20%}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-13{width:var(--spacing-13)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-20{width:calc(var(--spacing)*20)}.w-40{width:var(--spacing-40)}.w-\[67px\]{width:67px}.w-full{width:100%}.w-px{width:1px}.max-w-\[2440px\]{max-width:2440px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.snap-x{scroll-snap-type:x var(--tw-scroll-snap-strictness)}.snap-mandatory{--tw-scroll-snap-strictness:mandatory}.snap-center{scroll-snap-align:center}.grid-cols-\[min\(44px\)_40px_200px\]{grid-template-columns:44px 40px 200px}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing)*.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-2\.5{gap:calc(var(--spacing)*2.5)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-10{gap:calc(var(--spacing)*10)}.gap-x-2{column-gap:calc(var(--spacing)*2)}.gap-x-10{column-gap:calc(var(--spacing)*10)}:where(.-space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*-6)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*-6)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-1{row-gap:calc(var(--spacing)*1)}.gap-y-4{row-gap:calc(var(--spacing)*4)}.gap-y-6{row-gap:calc(var(--spacing)*6)}.overflow-hidden{overflow:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-4xl{border-radius:var(--radius-4xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-b-4xl{border-bottom-right-radius:var(--radius-4xl);border-bottom-left-radius:var(--radius-4xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-border-primary{border-color:var(--color-border-primary)}.border-border-primary\/50{border-color:#4d4d4d80}@supports (color:color-mix(in lab, red, red)){.border-border-primary\/50{border-color:color-mix(in oklab,var(--color-border-primary)50%,transparent)}}.border-primary-green-muted{border-color:var(--color-primary-green-muted)}.bg-\[\#00000070\]{background-color:#00000070}.bg-bg-dark{background-color:var(--color-bg-dark)}.bg-bg-dark-elevated{background-color:var(--color-bg-dark-elevated)}.bg-black{background-color:var(--color-black)}.bg-black\/80{background-color:#000c}@supports (color:color-mix(in lab, red, red)){.bg-black\/80{background-color:color-mix(in oklab,var(--color-black)80%,transparent)}}.bg-border-primary{background-color:var(--color-border-primary)}.bg-primary-gray\/20{background-color:#a3a3a333}@supports (color:color-mix(in lab, red, red)){.bg-primary-gray\/20{background-color:color-mix(in oklab,var(--color-primary-gray)20%,transparent)}}.bg-primary-green-dark{background-color:var(--color-primary-green-dark)}.bg-white{background-color:var(--color-white)}.bg-linear-to-t{--tw-gradient-position:to top}@supports (background-image:linear-gradient(in lab, red, red)){.bg-linear-to-t{--tw-gradient-position:to top in oklab}}.bg-linear-to-t{background-image:linear-gradient(var(--tw-gradient-stops))}.from-black\/80{--tw-gradient-from:#000c}@supports (color:color-mix(in lab, red, red)){.from-black\/80{--tw-gradient-from:color-mix(in oklab,var(--color-black)80%,transparent)}}.from-black\/80{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.bg-cover{background-size:cover}.bg-center{background-position:50%}.bg-no-repeat{background-repeat:no-repeat}.fill-primary-gray{fill:var(--color-primary-gray)}.fill-white\/50{fill:#ffffff80}@supports (color:color-mix(in lab, red, red)){.fill-white\/50{fill:color-mix(in oklab,var(--color-white)50%,transparent)}}.stroke-primary-gray{stroke:var(--color-primary-gray)}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-16{padding:calc(var(--spacing)*16)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pt-12{padding-top:calc(var(--spacing)*12)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pb-12{padding-bottom:calc(var(--spacing)*12)}.pb-px{padding-bottom:1px}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[16px\]{font-size:16px}.text-\[18px\]{font-size:18px}.text-\[clamp\(32px\,10vw\+12px\,55px\)\]{font-size:clamp(32px,10vw + 12px,55px)}.leading-5{--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}.leading-\[1\.05\]{--tw-leading:1.05;line-height:1.05}.leading-\[1\.10\]{--tw-leading:1.1;line-height:1.1}.leading-\[18px\]{--tw-leading:18px;line-height:18px}.leading-\[24px\]{--tw-leading:24px;line-height:24px}.leading-\[28px\]{--tw-leading:28px;line-height:28px}.leading-none{--tw-leading:1;line-height:1}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.2em\]{--tw-tracking:.2em;letter-spacing:.2em}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.wrap-break-word{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.text-bg-dark{color:var(--color-bg-dark)}.text-black{color:var(--color-black)}.text-border-primary{color:var(--color-border-primary)}.text-primary{color:var(--color-primary)}.text-primary-gray{color:var(--color-primary-gray)}.text-primary-gray-on-white{color:var(--color-primary-gray-on-white)}.text-primary-gray\/50{color:#a3a3a380}@supports (color:color-mix(in lab, red, red)){.text-primary-gray\/50{color:color-mix(in oklab,var(--color-primary-gray)50%,transparent)}}.text-primary-green-muted{color:var(--color-primary-green-muted)}.text-text-on-green{color:var(--color-text-on-green)}.text-text-primary{color:var(--color-text-primary)}.text-text-secondary{color:var(--color-text-secondary)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-\[0\.48\]{opacity:.48}.opacity-\[0\.64\]{opacity:.64}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media (hover:hover){.group-hover\:-translate-x-full:is(:where(.group):hover *){--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.group-hover\:fill-primary:is(:where(.group):hover *){fill:var(--color-primary)}.group-hover\:text-text-on-green:is(:where(.group):hover *){color:var(--color-text-on-green)}.group-hover\:opacity-0:is(:where(.group):hover *){opacity:0}.group-hover\/loc\:bg-\[\#00000080\]:is(:where(.group\/loc):hover *){background-color:#00000080}.group-hover\/loc\:bg-primary:is(:where(.group\/loc):hover *){background-color:var(--color-primary)}.group-hover\/loc\:text-black:is(:where(.group\/loc):hover *){color:var(--color-black)}.group-hover\/loc\:text-primary:is(:where(.group\/loc):hover *){color:var(--color-primary)}.group-hover\/loc\:text-white:is(:where(.group\/loc):hover *){color:var(--color-white)}}.group-active\:fill-primary-green-dark:is(:where(.group):active *){fill:var(--color-primary-green-dark)}.group-active\/loc\:bg-\[\#000000a0\]:is(:where(.group\/loc):active *){background-color:#000000a0}@media (hover:hover){.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:cursor-pointer:hover{cursor:pointer}.hover\:border-primary:hover{border-color:var(--color-primary)}.hover\:border-t-primary:hover{border-top-color:var(--color-primary)}.hover\:bg-primary:hover{background-color:var(--color-primary)}.hover\:bg-primary\/10:hover{background-color:#47c5521a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/10:hover{background-color:color-mix(in oklab,var(--color-primary)10%,transparent)}}.hover\:stroke-black:hover{stroke:var(--color-black)}.hover\:text-bg-dark:hover{color:var(--color-bg-dark)}.hover\:text-primary:hover{color:var(--color-primary)}.hover\:text-text-primary:hover{color:var(--color-text-primary)}}.active\:border-bg-dark:active{border-color:var(--color-bg-dark)}.active\:bg-bg-dark:active{background-color:var(--color-bg-dark)}.active\:bg-black:active{background-color:var(--color-black)}.active\:bg-primary-gray:active{background-color:var(--color-primary-gray)}.active\:bg-primary-green-dark:active{background-color:var(--color-primary-green-dark)}.active\:bg-primary\/20:active{background-color:#47c55233}@supports (color:color-mix(in lab, red, red)){.active\:bg-primary\/20:active{background-color:color-mix(in oklab,var(--color-primary)20%,transparent)}}.active\:text-primary-green-dark:active{color:var(--color-primary-green-dark)}.active\:text-white:active{color:var(--color-white)}@media (min-width:390px){.mobile\:h-\[231px\]{height:231px}.mobile\:text-\[55px\]{font-size:55px}}@media (min-width:960px){.tablet\:-mt-1{margin-top:calc(var(--spacing)*-1)}.tablet\:mt-0{margin-top:calc(var(--spacing)*0)}.tablet\:mt-4{margin-top:calc(var(--spacing)*4)}.tablet\:mb-0{margin-bottom:calc(var(--spacing)*0)}.tablet\:mb-6{margin-bottom:calc(var(--spacing)*6)}.tablet\:mb-8{margin-bottom:calc(var(--spacing)*8)}.tablet\:mb-\[22px\]{margin-bottom:22px}.tablet\:ml-4{margin-left:calc(var(--spacing)*4)}.tablet\:block{display:block}.tablet\:flex{display:flex}.tablet\:hidden{display:none}.tablet\:h-5{height:calc(var(--spacing)*5)}.tablet\:h-7{height:calc(var(--spacing)*7)}.tablet\:h-10{height:calc(var(--spacing)*10)}.tablet\:h-11{height:calc(var(--spacing)*11)}.tablet\:h-12{height:calc(var(--spacing)*12)}.tablet\:h-14{height:calc(var(--spacing)*14)}.tablet\:h-16{height:calc(var(--spacing)*16)}.tablet\:h-32{height:calc(var(--spacing)*32)}.tablet\:h-\[42px\]{height:42px}.tablet\:h-\[104px\]{height:104px}.tablet\:h-\[252px\]{height:252px}.tablet\:h-\[clamp\(640px\,50vw\+160px\,900px\)\]{height:clamp(640px,50vw + 160px,900px)}.tablet\:h-\[min\(820px\,85vh\)\]{height:min(820px,85vh)}.tablet\:h-auto{height:auto}.tablet\:h-fit{height:fit-content}.tablet\:h-full{height:100%}.tablet\:min-h-0{min-height:calc(var(--spacing)*0)}.tablet\:w-1\/3{width:33.3333%}.tablet\:w-1\/10{width:10%}.tablet\:w-2\/3{width:66.6667%}.tablet\:w-5{width:calc(var(--spacing)*5)}.tablet\:w-7{width:calc(var(--spacing)*7)}.tablet\:w-9\/10{width:90%}.tablet\:w-10{width:calc(var(--spacing)*10)}.tablet\:w-12{width:calc(var(--spacing)*12)}.tablet\:w-16{width:calc(var(--spacing)*16)}.tablet\:w-24{width:calc(var(--spacing)*24)}.tablet\:w-26{width:var(--spacing-26)}.tablet\:w-44{width:var(--spacing-44)}.tablet\:w-\[160px\]{width:160px}.tablet\:max-w-\[960px\]{max-width:960px}.tablet\:flex-col{flex-direction:column}.tablet\:flex-row{flex-direction:row}.tablet\:justify-between{justify-content:space-between}.tablet\:justify-end{justify-content:flex-end}.tablet\:justify-start{justify-content:flex-start}.tablet\:gap-0{gap:calc(var(--spacing)*0)}.tablet\:gap-8{gap:calc(var(--spacing)*8)}.tablet\:gap-y-1{row-gap:calc(var(--spacing)*1)}.tablet\:rounded-none{border-radius:0}.tablet\:border{border-style:var(--tw-border-style);border-width:1px}.tablet\:border-0{border-style:var(--tw-border-style);border-width:0}.tablet\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.tablet\:border-border-primary{border-color:var(--color-border-primary)}.tablet\:border-r-border-primary{border-right-color:var(--color-border-primary)}.tablet\:border-l-border-primary{border-left-color:var(--color-border-primary)}.tablet\:bg-bg-dark\/94{background-color:#000000f0}@supports (color:color-mix(in lab, red, red)){.tablet\:bg-bg-dark\/94{background-color:color-mix(in oklab,var(--color-bg-dark)94%,transparent)}}.tablet\:bg-transparent{background-color:#0000}.tablet\:p-0{padding:calc(var(--spacing)*0)}.tablet\:p-4{padding:calc(var(--spacing)*4)}.tablet\:p-8{padding:calc(var(--spacing)*8)}.tablet\:px-0{padding-inline:calc(var(--spacing)*0)}.tablet\:px-4{padding-inline:calc(var(--spacing)*4)}.tablet\:px-8{padding-inline:calc(var(--spacing)*8)}.tablet\:px-16{padding-inline:calc(var(--spacing)*16)}.tablet\:py-0{padding-block:calc(var(--spacing)*0)}.tablet\:py-8{padding-block:calc(var(--spacing)*8)}.tablet\:py-12{padding-block:calc(var(--spacing)*12)}.tablet\:pt-0{padding-top:calc(var(--spacing)*0)}.tablet\:pt-4{padding-top:calc(var(--spacing)*4)}.tablet\:pb-11{padding-bottom:calc(var(--spacing)*11)}.tablet\:pb-16{padding-bottom:calc(var(--spacing)*16)}.tablet\:pl-4{padding-left:calc(var(--spacing)*4)}.tablet\:pl-10{padding-left:calc(var(--spacing)*10)}.tablet\:text-left{text-align:left}.tablet\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.tablet\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.tablet\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.tablet\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.tablet\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.tablet\:text-\[11px\]{font-size:11px}.tablet\:text-\[14px\]{font-size:14px}.tablet\:text-\[16px\]{font-size:16px}.tablet\:text-\[44px\]{font-size:44px}.tablet\:text-\[clamp\(80px\,5vw\+32px\,160px\)\]{font-size:clamp(80px,5vw + 32px,160px)}.tablet\:leading-\[52px\]{--tw-leading:52px;line-height:52px}.tablet\:leading-none{--tw-leading:1;line-height:1}.tablet\:text-white{color:var(--color-white)}}@media (min-width:1180px){.desktop\:mb-\[clamp\(43px\,1vh\,91\.5px\)\]{margin-bottom:clamp(43px,1vh,91.5px)}.desktop\:h-16{height:calc(var(--spacing)*16)}.desktop\:h-24{height:calc(var(--spacing)*24)}.desktop\:h-\[52px\]{height:52px}.desktop\:h-\[210px\]{height:210px}.desktop\:h-\[490px\]{height:490px}.desktop\:h-\[max\(min\(100vh\,1116px\)\,744px\)\]{height:max(min(100vh,1116px),744px)}.desktop\:w-24{width:calc(var(--spacing)*24)}.desktop\:w-\[97px\]{width:97px}.desktop\:w-\[243px\]{width:243px}.desktop\:flex-row{flex-direction:row}.desktop\:items-center{align-items:center}.desktop\:justify-between{justify-content:space-between}.desktop\:justify-center{justify-content:center}.desktop\:justify-start{justify-content:flex-start}.desktop\:gap-0{gap:calc(var(--spacing)*0)}.desktop\:gap-2{gap:calc(var(--spacing)*2)}.desktop\:gap-10{gap:calc(var(--spacing)*10)}.desktop\:gap-y-0{row-gap:calc(var(--spacing)*0)}.desktop\:p-8{padding:calc(var(--spacing)*8)}.desktop\:p-16{padding:calc(var(--spacing)*16)}.desktop\:px-16{padding-inline:calc(var(--spacing)*16)}.desktop\:pt-16{padding-top:calc(var(--spacing)*16)}.desktop\:pb-0{padding-bottom:calc(var(--spacing)*0)}.desktop\:pb-10{padding-bottom:calc(var(--spacing)*10)}.desktop\:pl-0{padding-left:calc(var(--spacing)*0)}.desktop\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.desktop\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.desktop\:text-\[11px\]{font-size:11px}.desktop\:text-\[44px\]{font-size:44px}.desktop\:text-\[clamp\(6rem\,5vw\+32px\,8rem\)\]{font-size:clamp(6rem,5vw + 32px,8rem)}.desktop\:text-\[clamp\(32px\,2vw\+16px\,48px\)\]{font-size:clamp(32px,2vw + 16px,48px)}}@media (min-width:1728px){.desktop-xl\:-mb-6{margin-bottom:calc(var(--spacing)*-6)}.desktop-xl\:-ml-6{margin-left:calc(var(--spacing)*-6)}.desktop-xl\:h-7{height:calc(var(--spacing)*7)}.desktop-xl\:h-14{height:calc(var(--spacing)*14)}.desktop-xl\:h-20{height:calc(var(--spacing)*20)}.desktop-xl\:h-24{height:calc(var(--spacing)*24)}.desktop-xl\:h-30{height:var(--spacing-30)}.desktop-xl\:h-64{height:calc(var(--spacing)*64)}.desktop-xl\:h-\[72px\]{height:72px}.desktop-xl\:h-\[max\(min\(100vh\,1117px\)\,900px\)\]{height:max(min(100vh,1117px),900px)}.desktop-xl\:w-7{width:calc(var(--spacing)*7)}.desktop-xl\:w-14{width:calc(var(--spacing)*14)}.desktop-xl\:w-20{width:calc(var(--spacing)*20)}.desktop-xl\:w-24{width:calc(var(--spacing)*24)}.desktop-xl\:w-\[273px\]{width:273px}.desktop-xl\:items-center{align-items:center}.desktop-xl\:p-16{padding:calc(var(--spacing)*16)}.desktop-xl\:pt-0{padding-top:calc(var(--spacing)*0)}.desktop-xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.desktop-xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.desktop-xl\:text-\[44px\]{font-size:44px}.desktop-xl\:text-\[128px\]{font-size:128px}.desktop-xl\:text-\[clamp\(8rem\,5vw\+24px\,11rem\)\]{font-size:clamp(8rem,5vw + 24px,11rem)}.desktop-xl\:leading-\[20px\]{--tw-leading:20px;line-height:20px}.desktop-xl\:leading-\[128px\]{--tw-leading:128px;line-height:128px}.desktop-xl\:leading-\[clamp\(8rem\,5vw\+24px\,11rem\)\]{--tw-leading:clamp(8rem,5vw + 24px,11rem);line-height:clamp(8rem,5vw + 24px,11rem)}}@media (min-width:2440px){.desktop-xxl\:mt-12{margin-top:calc(var(--spacing)*12)}.desktop-xxl\:-mb-12{margin-bottom:calc(var(--spacing)*-12)}.desktop-xxl\:mb-\[43px\]{margin-bottom:43px}.desktop-xxl\:-ml-12{margin-left:calc(var(--spacing)*-12)}.desktop-xxl\:h-6{height:calc(var(--spacing)*6)}.desktop-xxl\:h-12{height:calc(var(--spacing)*12)}.desktop-xxl\:h-18{height:var(--spacing-18)}.desktop-xxl\:h-\[62px\]{height:62px}.desktop-xxl\:h-\[280px\]{height:280px}.desktop-xxl\:h-\[352px\]{height:352px}.desktop-xxl\:h-\[max\(min\(100vh\,1280px\)\,1117px\)\]{height:max(min(100vh,1280px),1117px)}.desktop-xxl\:w-6{width:calc(var(--spacing)*6)}.desktop-xxl\:w-12{width:calc(var(--spacing)*12)}.desktop-xxl\:w-\[119px\]{width:119px}.desktop-xxl\:w-\[273px\]{width:273px}.desktop-xxl\:gap-6{gap:calc(var(--spacing)*6)}.desktop-xxl\:p-24{padding:calc(var(--spacing)*24)}.desktop-xxl\:px-24{padding-inline:calc(var(--spacing)*24)}.desktop-xxl\:pb-8{padding-bottom:calc(var(--spacing)*8)}.desktop-xxl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.desktop-xxl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.desktop-xxl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.desktop-xxl\:text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.desktop-xxl\:text-\[16px\]{font-size:16px}.desktop-xxl\:text-\[28px\]{font-size:28px}.desktop-xxl\:text-\[42px\]{font-size:42px}.desktop-xxl\:text-\[44px\]{font-size:44px}.desktop-xxl\:text-\[180px\]{font-size:180px}.desktop-xxl\:leading-\[10rem\]{--tw-leading:10rem;line-height:10rem}.desktop-xxl\:leading-\[160px\]{--tw-leading:160px;line-height:160px}}@media (min-width:48rem){.md\:flex-row{flex-direction:row}.md\:border-0{border-style:var(--tw-border-style);border-width:0}.md\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.md\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}}.\[\&\:has\(button\:not\(\:disabled\)\:active\)\>div\]\:bg-primary-green-dark:has(button:not(:disabled):active)>div{background-color:var(--color-primary-green-dark)}.\[\&\:has\(button\:not\(\:disabled\)\:hover\)\>div\]\:bg-primary:has(button:not(:disabled):hover)>div{background-color:var(--color-primary)}}@keyframes bg-pan-transform{0%{transform:scale(1.15)translate(-4%)}to{transform:scale(1.15)translate(4%)}}.bg-pan-slow{position:relative;overflow:hidden}.bg-pan-slow:before{content:"";background-image:inherit;will-change:transform;backface-visibility:hidden;pointer-events:none;z-index:0;background-position:50%;background-repeat:no-repeat;background-size:cover;animation:35s ease-in-out infinite alternate bg-pan-transform;position:absolute;inset:0}.bg-pan-slow>*{z-index:1;position:relative}.duration-transition-fast{transition-duration:.15s}.duration-transition-base{transition-duration:.2s}.duration-1500{transition-duration:1.5s}.pair-item-1,.pair-item-2,.three-item-1,.three-item-2,.three-item-3{width:100%}@media (min-width:768px){.pair-item-1,.pair-item-2,.three-item-1,.three-item-2,.three-item-3{width:var(--width-desktop-xxl)}}.py-spacing-md{padding-top:1rem;padding-bottom:1rem}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}a span,button span{font-size:inherit;font-weight:inherit;text-transform:inherit;letter-spacing:inherit;line-height:inherit}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-scroll-snap-strictness{syntax:"*";inherits:false;initial-value:proximity}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file diff --git a/public/paris2025/robots.txt b/public/paris2025/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/public/paris2025/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/scripts/build-html.js b/scripts/build-html.js new file mode 100644 index 0000000..bd3ec1b --- /dev/null +++ b/scripts/build-html.js @@ -0,0 +1,13 @@ +import { readFileSync, writeFileSync, mkdirSync } from 'fs'; + +const baseUrl = process.env.BASE_URL || 'https://conference.openapis.org'; + +// Ensure dist directory exists +mkdirSync('dist', { recursive: true }); + +// Read, replace, and write +let html = readFileSync('src/index.html', 'utf-8'); +html = html.replace(/__BASE_URL__/g, baseUrl); +writeFileSync('dist/index.html', html); + +console.log(`✅ HTML built with BASE_URL=${baseUrl}`); diff --git a/src/components/CalendarPopup.js b/src/components/CalendarPopup.js index 224dd7f..c46e40c 100644 --- a/src/components/CalendarPopup.js +++ b/src/components/CalendarPopup.js @@ -11,17 +11,17 @@ export class CalendarPopup { this.container = null; // Event details - this.eventTitle = "OpenAPI Conference Paris 2025"; + this.eventTitle = "OpenAPI Conference San Jose 2026"; this.eventDescription = - "Join us at the OpenAPI Conference in Paris. More info: https://conference.openapis.org"; + "Join us at the OpenAPI Conference in San Jose. More info: https://conference.openapis.org"; this.eventLocation = - "CNIT Forest, 2 Pl. de la Defense, 92092 Puteaux, France"; - this.startDate = "2025-12-11T09:15:00"; - this.endDate = "2025-12-11T18:00:00"; + "San Jose Convention Center, 150 W San Carlos St, San Jose, CA 95113, USA"; + this.startDate = "2026-02-20T09:00:00"; + this.endDate = "2026-02-20T16:00:00"; // Format dates for different calendars - this.googleStart = "20251211T091500"; - this.googleEnd = "20251211T180000"; + this.googleStart = "20260220T090000"; + this.googleEnd = "20260220T160000"; // Pre-calculate URLs this.googleCalendarUrl = this.buildGoogleCalendarUrl(); @@ -44,9 +44,9 @@ export class CalendarPopup { */ buildGoogleCalendarUrl() { return `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent( - this.eventTitle + this.eventTitle, )}&dates=${this.googleStart}/${this.googleEnd}&details=${encodeURIComponent( - this.eventDescription + this.eventDescription, )}&location=${encodeURIComponent(this.eventLocation)}`; } @@ -55,7 +55,7 @@ export class CalendarPopup { */ buildOutlookUrl() { return `https://outlook.office.com/calendar/0/deeplink/compose?subject=${encodeURIComponent( - this.eventTitle + this.eventTitle, )}&body=${encodeURIComponent(this.eventDescription)}&startdt=${ this.startDate }&enddt=${this.endDate}&location=${encodeURIComponent(this.eventLocation)}`; @@ -65,7 +65,7 @@ export class CalendarPopup { * Generate ICS file content */ generateICS() { - const uid = "openapi-conference-2025@openapis.org"; + const uid = "openapi-conference-2026@openapis.org"; const dtstamp = new Date().toISOString().replace(/[-:]/g, "").split(".")[0] + "Z"; @@ -75,8 +75,8 @@ PRODID:-//OpenAPI Conference//EN BEGIN:VEVENT UID:${uid} DTSTAMP:${dtstamp} -DTSTART:20251211T091500 -DTEND:20251211T180000 +DTSTART:20260220T090000 +DTEND:20260220T160000 SUMMARY:${this.eventTitle} DESCRIPTION:${this.eventDescription} LOCATION:${this.eventLocation} @@ -93,7 +93,7 @@ END:VCALENDAR`; const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; - link.download = "openapi-conference.ics"; + link.download = "openapi-conference-2026.ics"; document.body.appendChild(link); link.click(); document.body.removeChild(link); @@ -262,7 +262,7 @@ END:VCALENDAR`; const backdrop = this.container.querySelector("[data-backdrop]"); const closeBtn = this.container.querySelector("[data-close-btn]"); const downloadButtons = this.container.querySelectorAll( - "[data-download-ics], [data-download-ics-alt]" + "[data-download-ics], [data-download-ics-alt]", ); backdrop.addEventListener("click", (e) => this.handleBackdropClick(e)); diff --git a/src/components/CountdownTimer.js b/src/components/CountdownTimer.js index 112aeab..48a733e 100644 --- a/src/components/CountdownTimer.js +++ b/src/components/CountdownTimer.js @@ -1,14 +1,14 @@ /** * CountdownTimer Component * - * Displays a countdown to December 9, 2025 + * Displays a countdown to February 18, 2026 * Updates every second */ import { asset } from "../config.js"; export class CountdownTimer { - constructor(targetDate = "2025-12-11T09:15:00") { + constructor(targetDate = "2026-02-18T09:15:00") { this.targetDate = new Date(targetDate); this.interval = null; this.days = 0; @@ -27,7 +27,7 @@ export class CountdownTimer { if (diff > 0) { this.days = Math.floor(diff / (1000 * 60 * 60 * 24)); this.hours = Math.floor( - (diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) + (diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60), ); this.minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); this.seconds = Math.floor((diff % (1000 * 60)) / 1000); @@ -62,8 +62,8 @@ export class CountdownTimer { class="inline h-2 mx-3" />
${this.pad(this.hours)}:${this.pad( - this.minutes - )}:${this.pad(this.seconds)}
+ this.minutes, + )}:${this.pad(this.seconds)} `; } @@ -78,7 +78,7 @@ export class CountdownTimer { if (daysEl && timeEl) { daysEl.textContent = this.days; timeEl.textContent = `${this.pad(this.hours)}:${this.pad( - this.minutes + this.minutes, )}:${this.pad(this.seconds)}`; } } diff --git a/src/components/PreviousEventsDropdown.js b/src/components/PreviousEventsDropdown.js new file mode 100644 index 0000000..9aaf2fe --- /dev/null +++ b/src/components/PreviousEventsDropdown.js @@ -0,0 +1,135 @@ +/** + * OtherLocationsDropdown Component + * + * Dropdown for navigating to previous OpenAPI Conference events + */ + +import { previousEvents } from "../data/previousEvents.js"; + +export class OtherLocationsDropdown { + constructor() { + this.isOpen = false; + this.container = null; + this.button = null; + this.menu = null; + } + + /** + * Mount component to DOM + */ + mount() { + this.container = document.getElementById("previous-events-dropdown"); + if (!this.container) return; + + this.render(); + this.attachEventListeners(); + } + + /** + * Render dropdown HTML + */ + render() { + this.container.innerHTML = ` +
+ + +
+ `; + + this.button = document.getElementById("previous-events-button"); + this.menu = document.getElementById("previous-events-menu"); + this.chevron = this.button.querySelector("svg"); + } + + /** + * Attach event listeners + */ + attachEventListeners() { + // Toggle on button click + this.button.addEventListener("click", (e) => { + e.stopPropagation(); + this.toggle(); + }); + + // Close on outside click + document.addEventListener("click", (e) => { + if (this.isOpen && !this.container.contains(e.target)) { + this.close(); + } + }); + + // Close on Escape key + document.addEventListener("keydown", (e) => { + if (e.key === "Escape" && this.isOpen) { + this.close(); + this.button.focus(); + } + }); + } + + /** + * Toggle dropdown + */ + toggle() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + } + + /** + * Open dropdown + */ + open() { + this.isOpen = true; + this.button.setAttribute("aria-expanded", "true"); + this.menu.classList.remove("opacity-0", "invisible"); + this.menu.classList.add("opacity-100", "visible"); + this.chevron.classList.add("rotate-180"); + } + + /** + * Close dropdown + */ + close() { + this.isOpen = false; + this.button.setAttribute("aria-expanded", "false"); + this.menu.classList.add("opacity-0", "invisible"); + this.menu.classList.remove("opacity-100", "visible"); + this.chevron.classList.remove("rotate-180"); + } +} diff --git a/src/config.js b/src/config.js index fcd7705..3fbe68e 100644 --- a/src/config.js +++ b/src/config.js @@ -7,35 +7,26 @@ // Asset version for cache busting - increment when deploying changes const ASSET_VERSION = "1"; -// Determine base path based on environment -// Uses BASE_PATH environment variable from build process -// Development (localhost): empty string (for local dev server) -// Production: set via BASE_PATH env var in build -const BASE_PATH = (() => { - if (typeof window !== "undefined") { - const hostname = window.location.hostname; - const isDevelopment = hostname === "localhost" || hostname === "127.0.0.1"; - return isDevelopment ? "" : import.meta.env.BASE_PATH; - } - return import.meta.env.BASE_PATH; -})(); +// BASE_PATH kept for backwards compatibility if used elsewhere +const BASE_PATH = import.meta.env.BASE_PATH || ""; /** - * Asset path helper + * Asset path helper - returns relative paths for portability * - * @param {string} path - Asset path (must start with /) - * @returns {string} - Full asset path with base + * @param {string} path - Asset path (e.g., /images/logo.svg) + * @returns {string} - Relative asset path (e.g., ./images/logo.svg?v=1) * * @example - * asset('/images/logo.svg') // Returns '/images/logo.svg' in production - * asset('/images/logo.svg') // Returns '/images/logo.svg' in development + * asset('/images/logo.svg') // Returns './images/logo.svg?v=1' */ export function asset(path) { - if (!path.startsWith("/")) { - console.warn(`Asset path should start with /: ${path}`); - return `${BASE_PATH}/${path}?v=${ASSET_VERSION}`; + // Convert to relative path for portability + if (path.startsWith("/")) { + path = "." + path; + } else if (!path.startsWith("./") && !path.startsWith("../")) { + path = "./" + path; } - return `${BASE_PATH}${path}?v=${ASSET_VERSION}`; + return `${path}?v=${ASSET_VERSION}`; } /** @@ -43,8 +34,9 @@ export function asset(path) { */ export const config = { BASE_PATH, - EVENT_DATE: "2025-12-11T09:15:00", - EVENT_LOCATION: "CNIT Forest, 2 Pl. de la Defense, 92092 Puteaux, France", + EVENT_DATE: "2026-02-20T09:00:00", + EVENT_LOCATION: + "San Jose Convention Center, 150 W San Carlos St, San Jose, CA 95113, USA", SOCIAL_LINKS: { linkedin: "https://www.linkedin.com/company/openapis-org", youtube: "https://www.youtube.com/@OpenAPIsOrg", diff --git a/src/data/agenda.js b/src/data/agenda.js index 39727ee..aa650ba 100644 --- a/src/data/agenda.js +++ b/src/data/agenda.js @@ -8,27 +8,27 @@ import { asset } from "../config.js"; export const agendaSections = [ { - id: "foundations", - title: "Foundations", - timeRange: "08:30 — 10:40", + id: "morning-sessions", + title: "Morning Sessions", + timeRange: "09:00 — 12:00", items: [ { - id: "breakfast", - time: "08:30", - category: "", - title: "Executive Breakfast", - badge: "Invite Only", + id: "1", + time: "09:00", + category: "Hackathon", + title: "DeveloperWeek 2026 Hackathon", + description: + "Join 600+ developers building new apps, bots — in person or online. Participants can build any app of their choice and will compete for $12,500+ in cash, products and prizes. Put yourself in the center of the developer world!", speakers: [], - icon: asset("/images/break-fest.svg"), + icon: asset("/images/hackathon.svg"), disableHover: true, }, { - id: "1", - time: "09:15", - category: "Foundations", - title: "Conference Welcome and OpenAPI in the Age of AI", - description: - "Welcome to the OpenAPI Conference! We have an exciting program to share, with presentations talking about new specifications as well as presentations that dive into practices, applications, and an outlook of things are going. We also we have a brief look of where OpenAPI is situated in the age of AI and MCP, and how things are going to develope after the recent publication of version 3.2 of the specification.", + id: "2", + time: "09:30", + category: "OpenAPI Summit", + title: "Opening Keynote", + description: "Coming Soon!", speakers: [ { name: "Erik Wilde", @@ -40,224 +40,213 @@ export const agendaSections = [ }, ], }, - { - id: "2", - time: "09:45", - category: "Foundations", - title: "What's new in OpenAPI 3.2", - description: - "Keeping up with all the new standards all the time can be hard work! Instead, come to this session and get an overview of what's new in the OpenAPI 3.2 release so you know what to expect when it's time to upgrade. You'll hear about support for more HTTP methods, multipart formats, and for handling the full query string as one input. There's a big upgrade to tags, allowing nesting, multiple tag types, and more metadata - replacing the extensions you're probably already using with tags. Also included are document identities, security scheme additions, and upgraded support for XML-format APIs. Come along to learn more and prepare for the future!", - speakers: [ - { - name: "Lorna Mitchell", - job: "API Architect", - company: "TM Forum", - avatar: asset("/images/speakers/Lorna.jpg"), - linkedin: "https://www.linkedin.com/in/lornajane/", - isTscMember: true, - isOaiMember: true, - }, - ], - }, { id: "3", - time: "10:15", - category: "Foundations", - title: "Data Contracts: Treating Data as APIs", + time: "10:00", + category: "OpenAPI Summit", + title: "You May Have OpenAPI, But Is It AI-Ready?", description: - "APIs brought order to software development through contracts, lifecycle management, and clear ownership. Yet in data, chaos often reigns: schema drift, silent breakages, and no shared language between producers and consumers. Enter the Open Data Contract Standard (ODCS), an open initiative under the Linux Foundation that applies API thinking to data. Think of OpenAPI, but for data: a contract-first approach that enables robust data pipelines, clearly defined interfaces, and enforceable guarantees across teams. This talk will show how ODCS enables API-like discipline in data sharing, supports contract-driven development for data products, and introduces schema validation and lifecycle management into data workflows. These capabilities lay the foundation for scalable data marketplaces within organizations, making data assets discoverable, reliable, and reusable. As an outlook, we'll explore the emerging Open Data Product Standard (ODPS), which builds on ODCS by grouping contracts into coherent, reusable building blocks. Together, ODCS and ODPS pave the way for a composable data architecture that aligns with data mesh and data-as-a-product principles.", + "I have worked with and reviewed thousands of APIs across startups, enterprises, and global platforms. Almost all shipped an OpenAPI Description. Yet through the lens of AI systems and intelligent agents, most failed a simple test. They were designed for humans (often badly), not machines. In this session I will share what I have learned from helping organisations make their APIs truly AI-ready, breaking down the six dimensions that determine whether an API can be understood, reasoned over, discovered, and safely executed by intelligent systems. These include foundational compliance, developer experience, semantic clarity, agent usability, security, and AI discoverability. Using the Jentic API Scoring Framework as a visual guide, I will show how to assess AI-readiness, where most teams stumble, and the simple changes that dramatically improve both human and machine understanding.", speakers: [ { - name: "Dr. Simon Harrer", - job: "Co-Founder and CEO", - company: "Entropy Data", - avatar: asset("/images/speakers/Simon.jpg"), - linkedin: "https://www.linkedin.com/in/simonharrer/", + name: "Frank Kilcommins", + job: "Head of Enterprise Architecture", + company: "Jentic", + avatar: asset("/images/speakers/Frank.jpg"), + linkedin: "https://www.linkedin.com/in/frank-kilcommins", + isOaiMember: true, }, ], }, - ], - }, - { - id: "practices", - title: "Practices", - timeRange: "11:00 — 12:55", - items: [ { id: "4", - time: "11:00", - category: "Practices", + time: "10:30", + category: "OpenAPI Summit", title: - "From Zero to Spec-Hero: Eliminating Lean Wastes When Adopting OpenAPI", + "The API-to-AI Pipeline: Building the Composable Backbone of AI-Ready Platforms", description: - "Many organisations recognise the value of the OpenAPI Specification yet struggle to make it part of their everyday API delivery. In this session you'll learn how to treat OpenAPI not as a checkbox, but as a Lean instrument for eliminating waste in API design, implementation and consumption. We'll unpack the six major barriers that keep teams from transitioning, map each barrier to a Lean waste, and then walk through a practical adoption roadmap—starting from one API, embedding spec-first thinking, applying governance and tooling, and measuring the outcomes. If you're ready to move from spec-ignored to spec-embedded, this talk shows you how.", + "95% of all AI initiatives failed across organizations in 2025! Why? Lack of AI-readiness, underestimating the complexities of the AI value chain and treating MCP as the entire AI strategy! Yes, every organization wants to be “AI-ready,” but few realize that the journey starts long before the first LLM prompt is written — it begins with APIs and API management. However, rigid, inflexible, and proprietary API platforms only compound the problem since they can't keep up or cater to the evolving needs of the business. So, in this workshop, we’ll explore how open standards like OpenAPI, OAuth, and OpenTelemetry can help you build a composable API layer that’s secure, observable, and built for scale. We’ll then turn your APIs into AI-ready tools using the Model Context Protocol (MCP), connecting them to LLMs, data sources, and other services to create AI-native applications. Key takeaways: 1. Why APIs are the first step toward AI-readiness. 2. How OAS, OAuth, and Otel work together to build strong foundations. 3. What the Model Context Protocol (MCP) unlocks for developers building AI-native applications Expect practical examples, clear patterns, and a few “aha” moments about where APIs fit in the AI era.", speakers: [ { - name: "Marjukka Niinioja", - job: "Founding Partner", - company: "Osaango", - avatar: asset("/images/speakers/Marjukka.jpg"), - linkedin: "https://www.linkedin.com/in/marjukkaniinioja/", + name: "Kuldeepak Angrish", + job: "Technology Leader", + company: "", + avatar: asset("/images/speakers/Kuldeepak.jpg"), + }, + { + name: "Budhaditya Bhattacharya", + job: "Enterprise Architect", + company: "", + avatar: asset("/images/speakers/Budhaditya.jpg"), }, ], }, { id: "5", - time: "11:30", - category: "Practices", + time: "11:00", + category: "OpenAPI Summit", title: - "How the Dutch Government Uses an OpenAPI-First Approach to Leverage Developer Experience", + "10x Boost in API Development Velocity Using Practical AI Tooling", description: - "The Dutch government recently launched a new OpenAPI-first API Register, designed to make public sector APIs easier to discover, understand, and reuse. Unlike traditional catalogs, the register is built around OpenAPI specifications as the single source of truth. This enables automated validation, consistent documentation, and machine-readable contracts right from the start. To further ensure quality and consistency, the register applies the Dutch API Strategy's API Design Rules. These rules standardize everything from security schemes to error handling, so developers know what to expect when working with APIs from different ministries or municipalities. The result is a more predictable and streamlined developer experience. The register also looks beyond single APIs. By supporting the emerging Arazzo standard, it can describe how multiple APIs interact to deliver complete government use cases — for example, registering a new business or applying for permits. This brings context and guidance to developers who need to orchestrate across domains.", + "This session focuses on accelerating development velocity using Generative AI tooling. In this session, we will use various AI tools to aid with API design, development / code generation, APIs unit/integration testing, and observability. We’ll outline various stages in API development lifecycle, and highlight practical examples of automated help using Microsoft Copilot with GPT 5 and Cursor AI with Claude Sonnet. We’ll cover generating refined API specs, developer documentation, data contracts and general API service scaffolding, unit and integration test code, and injecting observability hooks to establish granular alerts and dashboards.", speakers: [ { - name: "Dimitri van Hees", - company: "Government of the Netherlands", - avatar: asset("/images/speakers/Dimitri.jpg"), - linkedin: "https://www.linkedin.com/in/dimitrivanhees/", + name: "Sumit Amar", + job: "API Development Expert", + company: "", + avatar: asset("/images/speakers/Sumit.jpg"), }, ], }, { id: "6", - time: "12:00", - category: "Practices", + time: "11:30", + category: "OpenAPI Summit", title: - "From REST to Events: API Workflow Testing and Mocking with a Single Arazzo Spec", + "The New Developer Stack: Integrating AI into the Core of Enterprise Systems", description: - "APIs rarely work in isolation. Real-world usage involves multiple steps across both synchronous REST calls and asynchronous events, where the outcome of each step determines the journey a particular interaction takes. While testing individual endpoints is necessary, it's not sufficient. It is equally important to validate how those endpoints and events work together as part of a real workflow. Enter the Arazzo Specification V1.1, which describes complete workflows including inputs, outputs, step dependencies, and success/failure criteria, across OpenAPI (REST) and AsyncAPI (events). In this talk, we'll demonstrate how you can leverage Arazzo to drive end-to-end API workflow testing and mocking in a completely no-code manner.", + "AI is no longer a sidecar, it’s the new runtime for enterprise software. This talk explores how to embed AI capabilities within existing platforms and micro-services. We’ll cover architecture evolution patterns, API design for AI endpoints, and change management strategies for teams transitioning from traditional systems to AI-native infrastructure. Whether you’re building for internal developers or customer-facing apps, this session provides a roadmap for sustainable AI adoption. Key takeaways: - Patterns for integrating LLMs and APIs with enterprise systems - How to align AI initiatives with platform goals - Strategies to balance innovation speed with governance.", speakers: [ { - name: "Naresh Jain", - job: "Founder, CEO", - company: "Specmatic", - avatar: asset("/images/speakers/Naresh.jpg"), - linkedin: "https://www.linkedin.com/in/nareshjain/", + name: "Manideep Galala", + job: "Enterprise Systems Architect", + company: "", + avatar: asset("/images/speakers/Manideep.jpg"), }, - ], - }, - { - id: "7", - time: "12:30", - category: "Practices", - title: "You may have OpenAPI, but is it AI-Ready?", - description: - "I have worked with and reviewed thousands of APIs across startups, enterprises, and global platforms. Almost all shipped an OpenAPI Description. Yet through the lens of AI systems and intelligent agents, most failed a simple test. They were designed for humans (often badly), not machines. In this session I will share what I have learned from helping organisations make their APIs truly AI-ready, breaking down the six dimensions that determine whether an API can be understood, reasoned over, discovered, and safely executed by intelligent systems. These include foundational compliance, developer experience, semantic clarity, agent usability, security, and AI discoverability. Using the Jentic API Scoring Framework as a visual guide, I will show how to assess AI-readiness, where most teams stumble, and the simple changes that dramatically improve both human and machine understanding.", - speakers: [ { - name: "Frank Kilcommins", - job: "Head of Enterprise Architecture", - company: "Jentic", - avatar: asset("/images/speakers/Frank.jpg"), - linkedin: "https://www.linkedin.com/in/frank-kilcommins", - isOaiMember: true, + name: "Aditya Tangirala", + job: "Enterprise Systems Engineer", + company: "", + avatar: asset("/images/speakers/Aditya.jpg"), }, ], }, ], }, { - id: "applications", - title: "Applications", - timeRange: "14:00 — 15:25", + id: "mid-day-sessions", + title: "Mid-Day Sessions", + timeRange: "12:00 — 15:00", items: [ + { + id: "7", + time: "12:00", + category: "Break", + title: "Lunch Break", + description: + "Grab lunch at the on-site concessions and use this time to mingle with our exhibitors. It’s a great opportunity to network, explore new products, and recharge before the afternoon sessions.", + speakers: [], + icon: asset("/images/break-fest.svg"), + disableHover: true, + }, { id: "8", - time: "14:00", - category: "Applications", - title: "What's All The Fuss About TypeSpec?", + time: "13:00", + category: "OpenAPI Summit", + title: + "Bridging SDKs, OpenAPI, and AI: A Unified Schema for LLM-Safe API Chunking", description: - "Ok, so you know OpenAPI and you love OpenAPI. You cannot imagine any other means to describe your APIs. You understand Operations Objects, Security Schemes, and the role Schema Objects play in well-described request and response payloads. You trust it, and so does your developer community. Enter TypeSpec, the new kid on the block for modelling APIs. In your ongoing love affair with OpenAPI, why should you care? In this session we'll take a look at TypeSpec from the angle of an ardent OpenAPI fan and look at what TypeSpec can actually do for you.", + "API teams today work with multiple representations of the same system: SDKs encode behavior and types in code, OpenAPI describes surface contracts, and documentation explains intent in prose. These artifacts evolve independently, resulting in fragmented semantics and no single source of truth. This fragmentation becomes especially problematic when APIs are consumed by LLMs. Common approaches such as simply chunking OpenAPI files, SDK code, or documentation, often split schemas, workflows, and error semantics across boundaries that destroy context. The result is brittle reasoning and hallucinations, even when the underlying API is well-designed. In this talk, we focus on two practical concepts: A unified intermediate schema that preserves semantic intent across SDKs and OpenAPI, acting as a shared backbone for tooling and automation LLM-safe chunking, where schemas, types, workflows, and errors are structured into stable, meaningful units that models can reason over without losing context Drawing from real-world, multi-language API codebases (Node.js, Python, Go, Java, TypeScript, etc), we discuss why existing representations fall short, what properties such a unified schema must have, and how it enables both accurate OpenAPI generation and more reliable LLM-driven use cases.", speakers: [ { - name: "Chris Wood", - job: "Principal Architect", - company: "Ozone API", - avatar: asset("/images/speakers/Chris.jpg"), - linkedin: "https://www.linkedin.com/in/sensiblewood/", - isOaiMember: true, - isOaiMember: true, + name: "Aditya Rohit", + job: "SDK & API Architect", + company: "", + avatar: asset("/images/speakers/AdityaRohit.jpg"), }, ], }, { id: "9", - time: "14:30", - category: "Applications", - title: - "Control Surfaces in OpenAPI: Designing Specs for Task, Plan, and Agent Modes", - description: - "Your API is a control panel — but what controls should your OpenAPI specification expose? The answer depends on whether you're building for tasks, plans, or agents. A simple task needs input schemas and error responses. A multi-step plan requires lifecycle endpoints (pause/resume) and progress schemas. An autonomous agent demands budget parameters, approval callbacks, and emergency stop operations. Get the spec wrong, and you'll frustrate users trying to pause an atomic operation or micromanage a self-optimizing system. This talk explores how different AI operational modes—task, plan, and agent—require fundamentally different API control surfaces, and how to express these in OpenAPI. Through real-world specification examples and anti-patterns, you'll learn what endpoints, parameters, and schemas each mode needs, why mode mismatches cause design pain, and how to structure your OpenAPI specifications to expose the right level of control. Whether you're documenting a simple REST endpoint or a complex autonomous system, understanding control surfaces will help you design more intuitive and maintainable OpenAPI specifications.", + time: "13:30", + category: "OpenAPI Summit", + title: "Coming Soon!", + description: "OpenAPI Summit: Coming Soon!", speakers: [ { - name: "Miguel Quintero", - job: "Technical Trainer", - company: "Postman", - avatar: asset("/images/speakers/Miguel.jpg"), - linkedin: "https://www.linkedin.com/in/miguel-quintero-a558531/", - isTscMember: true, - isOaiMember: true, + name: "Emmanuel Paraskakis", + job: "Founder", + company: "Level 250", + avatar: asset("/images/speakers/Emmanuel.jpg"), + linkedin: "https://www.linkedin.com/in/emmanuelparaskakis/", }, ], }, { id: "10", - time: "15:00", - category: "Applications", - title: "Spec-First API Designs Without Codegen", + time: "14:00", + category: "OpenAPI Summit", + title: "APIs Weren't Built for AI: Now What?", description: - "The talk is about keeping the two things that matter at the centre: The users and the spec, the OpenAPI spec and how to design your code around it. It talks about the various real life challenges of not doing this and things like generating code and how popular open source tooling can address this. It gives a practical way to approach this problem and work with teams at scale.", + "APIs power modern digital systems. But they were designed for human developers building deterministic applications. Today, those APIs are increasingly consumed by AI-driven and agentic systems that are probabilistic, non-deterministic, and autonomous by nature. This mismatch introduces real risks: runaway costs, unexpected behavior, security gaps, and brittle integrations. Making APIs “AI-ready” requires more than adding an OpenAPI description or exposing an endpoint to an LLM. It demands rethinking APIs across multiple dimensions: how they are designed, how they are consumed, and how they are governed at runtime. In this session, we explore why traditional API assumptions break down in AI-driven environments and what architectural changes are required to address them. We’ll examine design patterns for AI-friendly APIs, challenges introduced by agentic consumption, and the critical role of API gateways, policies, and observability in controlling risk. AI-ready APIs aren’t accidental, they’re intentional. This talk shows what that intent looks like in practice.", speakers: [ { - name: "Rahul Dé", - job: "VP, Platform and Site Reliability Engineering, Public Cloud", - company: "Citi", - avatar: asset("/images/speakers/Rahul.jpg"), - linkedin: "https://www.linkedin.com/in/lispyclouds", + name: "Nuwan Dias", + job: "API Platform Expert", + company: "", + avatar: asset("/images/speakers/Nuwan.jpg"), }, ], }, - ], - }, - { - id: "looking-glass", - title: "The Looking Glass", - timeRange: "15:55 — 16:50", - items: [ { id: "11", - time: "15:55", - category: "The Looking Glass", - title: "OpenAPI and Spring-Boot 4 - What's New?", - description: - "Spring Boot remains the most widely used Java framework for modern application development, powering millions of applications globally. With Spring Boot 4, the framework enters a new era of performance, cloud-native support, and developer productivity. This talk will showcase the key innovations in Spring Boot 4 that make it a powerful choice for building scalable APIs. A major focus will be on OpenAPI and its integration via springdoc-openapi, a community-driven project that now exceeds 30 million downloads per month. We'll dive into the core features of springdoc-openapi, including support for Scalar project in addition to swagger-ui, Spring MVC, WebFlux, GraalVM, Kotlin. You'll also learn how to leverage advanced features like actuator integration, Javadoc reuse for API descriptions, HATEOAS, Data REST, and OAuth2 security.", + time: "14:30", + category: "OpenAPI Summit", + title: "Coming Soon!", + description: "Coming Soon!", speakers: [ { - name: "Badr Nass Lahsen", - job: "Lead Cloud and Security Architect", - company: "CyberArk", - avatar: asset("/images/speakers/Badr.jpg"), - linkedin: "https://www.linkedin.com/in/nasslahsen/", + name: "Speaker Coming Soon", + job: "", + company: "", + avatar: "", }, ], }, + ], + }, + { + id: "afternoon-sessions", + title: "Afternoon Sessions", + timeRange: "14:30 — 15:30", + items: [ { id: "12", - time: "16:25", - category: "The Looking Glass", - title: "Is OpenAPI still relevant in the age of AI?", + time: "14:30", + category: "Break", + title: "Networking Break", description: - "Count the times when you said 'OpenAPI' and others heard 'OpenAI'... Just when API Design, machine-readable API documentation, and OpenAPI have finally been normalized and gained traction, AI and agents throw a wrench into the works. At a time when you can vibecode an API in minutes and instantly stand up an MCP server, how useful is it to write and maintain OpenAPI documents? Join me to examine where and how OpenAPI remains relevant for your organization & product delivery and where it's no longer that useful (and what we could do instead).", + "Afternoon networking break in the San Jose Convention Center Expo Hall. Last chance to connect with speakers and attendees before the closing sessions.", + speakers: [], + icon: asset("/images/break-fest.svg"), + disableHover: true, + }, + { + id: "13", + time: "15:00", + category: "OpenAPI Summit", + title: "Coming Soon", + description: "Coming Soon!", speakers: [ { - name: "Emmanuel Paraskakis", - job: "Founder", - company: "Level 250", - avatar: asset("/images/speakers/Emmanuel.jpg"), - linkedin: "https://www.linkedin.com/in/emmanuelparaskakis/", + name: "Speaker Coming Soon", + job: "", + company: "", + avatar: "", }, ], }, + { + id: "14", + time: "15:30", + category: "Closing", + title: "Closing Ceremony", + description: + "Official DeveloperWeek Closing Ceremony. Join us for closing remarks and a celebration of the day's achievements.", + speakers: [], + icon: asset("/images/closing.svg"), + disableHover: true, + }, ], }, ]; diff --git a/src/data/previousEvents.js b/src/data/previousEvents.js new file mode 100644 index 0000000..9832327 --- /dev/null +++ b/src/data/previousEvents.js @@ -0,0 +1,14 @@ +/** + * Other locations Data + * + * List of previous OpenAPI Conference events for the dropdown + */ + +export const previousEvents = [ + { + name: "OpenAPI
Conference in
Paris", + key: "paris-2025", + url: "./paris2025/", + date: "11 December, 2025", + }, +]; diff --git a/src/index.html b/src/index.html index 5212c75..3d3c98c 100644 --- a/src/index.html +++ b/src/index.html @@ -1,4 +1,4 @@ - + @@ -8,51 +8,59 @@ - OpenAPI Conference Paris 2025 | 11 December, 2025 + OpenAPI Conference San Jose 2026 | 20 February, 2026 - + - + - + + + + + +