diff --git a/package.json b/package.json index a72a37aad2..9d11ac361a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "analyze": "ANALYZE=true next build", "build": "next build", "check:links": "lychee --verbose --no-progress './src/pages/**/*.mdx' --base https://graphql.org", + "conference-kit": "tsx scripts/render-conference-kit-banners.ts", "dev": "next", "format": "pnpm format:check --write", "format:check": "prettier --cache --check .", @@ -74,6 +75,7 @@ "playwright-core": "^1.54.2", "postcss": "^8.4.49", "postcss-import": "^16.1.1", + "qrcode": "^1.5.4", "react": "^18.3.1", "react-dom": "^18.3.1", "react-medium-image-zoom": "5.2.13", @@ -104,6 +106,7 @@ "@types/hast": "3.0.4", "@types/jsdom": "^21.1.7", "@types/node": "^22.10.5", + "@types/qrcode": "^1.5.6", "@types/react": "^18.3.23", "@types/rss": "0.0.32", "@types/string-similarity": "^4.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7927aabf0..d5abe88ba5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -173,6 +173,9 @@ importers: postcss-import: specifier: ^16.1.1 version: 16.1.1(postcss@8.5.6) + qrcode: + specifier: ^1.5.4 + version: 1.5.4 react: specifier: ^18.3.1 version: 18.3.1 @@ -249,6 +252,9 @@ importers: '@types/node': specifier: ^22.10.5 version: 22.19.6 + '@types/qrcode': + specifier: ^1.5.6 + version: 1.5.6 '@types/react': specifier: ^18.3.23 version: 18.3.27 @@ -2491,6 +2497,9 @@ packages: '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + '@types/react@18.3.27': resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} @@ -2648,10 +2657,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.2.0: - resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==} - engines: {node: '>=12'} - ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} @@ -2882,6 +2887,10 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} @@ -2962,6 +2971,9 @@ packages: resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} engines: {node: '>=18'} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -3327,6 +3339,10 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -3377,6 +3393,9 @@ packages: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3722,6 +3741,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -3849,11 +3872,12 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -4541,6 +4565,10 @@ packages: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -5138,6 +5166,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -5150,10 +5182,18 @@ packages: resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} engines: {node: '>=18'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -5290,6 +5330,10 @@ packages: engines: {node: '>=18'} hasBin: true + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -5479,6 +5523,11 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -5674,6 +5723,9 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -5819,6 +5871,9 @@ packages: server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -6023,10 +6078,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - strip-ansi@7.1.2: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} @@ -6550,6 +6601,9 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which-typed-array@1.1.19: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} @@ -6624,6 +6678,9 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -6636,10 +6693,18 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -9271,9 +9336,14 @@ snapshots: '@types/node@25.0.8': dependencies: undici-types: 7.16.0 + optional: true '@types/prop-types@15.7.15': {} + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 22.19.6 + '@types/react@18.3.27': dependencies: '@types/prop-types': 15.7.15 @@ -9300,7 +9370,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.0.8 + '@types/node': 22.19.6 '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': dependencies: @@ -9454,8 +9524,6 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.2.0: {} - ansi-regex@6.2.2: {} ansi-styles@4.3.0: @@ -9708,6 +9776,8 @@ snapshots: camelcase-css@2.0.1: {} + camelcase@5.3.1: {} + camelcase@6.3.0: {} caniuse-lite@1.0.30001754: {} @@ -9814,6 +9884,12 @@ snapshots: is-wsl: 3.1.0 is64bit: 2.0.0 + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -10200,6 +10276,8 @@ snapshots: dependencies: ms: 2.1.3 + decamelize@1.2.0: {} + decimal.js@10.6.0: {} decode-named-character-reference@1.2.0: @@ -10242,6 +10320,8 @@ snapshots: diff@5.2.0: {} + dijkstrajs@1.0.3: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -10767,6 +10847,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -11678,6 +11763,10 @@ snapshots: pkg-types: 2.3.0 quansync: 0.2.11 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -12630,6 +12719,10 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -12642,10 +12735,16 @@ snapshots: dependencies: yocto-queue: 1.2.1 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} package-manager-detector@1.3.0: {} @@ -12791,6 +12890,8 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + pngjs@5.0.0: {} + points-on-curve@0.2.0: {} points-on-path@0.2.1: @@ -12913,6 +13014,12 @@ snapshots: punycode@2.3.1: {} + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -13224,6 +13331,8 @@ snapshots: require-directory@2.1.1: {} + require-main-filename@2.0.0: {} + reselect@5.1.1: {} resolve-from@4.0.0: {} @@ -13378,6 +13487,8 @@ snapshots: server-only@0.0.1: {} + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -13598,7 +13709,7 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 10.4.0 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 string-width@7.2.0: dependencies: @@ -13668,10 +13779,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.2.0 - strip-ansi@7.1.2: dependencies: ansi-regex: 6.2.2 @@ -13959,7 +14066,8 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.16.0: {} + undici-types@7.16.0: + optional: true unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -14321,6 +14429,8 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 + which-module@2.0.1: {} + which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 @@ -14379,14 +14489,35 @@ snapshots: xmlchars@2.2.0: {} + y18n@4.0.3: {} + y18n@5.0.8: {} yallist@3.1.1: {} yaml@2.8.1: {} + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + yargs-parser@21.1.1: {} + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yargs@17.7.2: dependencies: cliui: 8.0.1 diff --git a/public/conference-kit/conference-kit.zip b/public/conference-kit/conference-kit.zip new file mode 100644 index 0000000000..31f3f24c5b Binary files /dev/null and b/public/conference-kit/conference-kit.zip differ diff --git a/scripts/render-conference-kit-banners.ts b/scripts/render-conference-kit-banners.ts new file mode 100644 index 0000000000..f0519396ec --- /dev/null +++ b/scripts/render-conference-kit-banners.ts @@ -0,0 +1,91 @@ +/** + * Renders the /conf/conference-kit/ banners as high-res PNGs and packages + * them into public/conference-kit/conference-kit.zip. Expects a dev/prod server to + * already be running on $URL (default http://localhost:3000) — invoke after + * `pnpm dev` is up, or against a `pnpm start` instance. Override SCALE for + * print-grade output (e.g. SCALE=6 → 3600×8472 ≈ 100 dpi at 850×2000 mm). + * + * Only the zip is written to public/. Loose PNGs land in a tmp dir and are + * cleaned up so the served directory stays minimal. + */ + +import path from "node:path" +import os from "node:os" +import { mkdir, mkdtemp, rm } from "node:fs/promises" +import { execFile } from "node:child_process" +import { promisify } from "node:util" +import { chromium } from "playwright" + +const exec = promisify(execFile) + +const URL = process.env.URL ?? "http://localhost:3000" +const PAGE = `${URL}/conf/conference-kit/` +const PUBLIC_DIR = path.resolve(process.cwd(), "public/conference-kit") +const SCALE = Number(process.env.SCALE ?? 4) + +type ColorScheme = "light" | "dark" + +const BANNERS: ReadonlyArray<{ slug: string; colorScheme: ColorScheme }> = [ + { slug: "amsterdam", colorScheme: "light" }, + // The language banner's inline `getCity` snippet uses shiki's dark token + // colors; next-themes flips html.dark when prefers-color-scheme matches. + { slug: "language", colorScheme: "dark" }, + { slug: "ai-hero", colorScheme: "light" }, +] + +async function main() { + await mkdir(PUBLIC_DIR, { recursive: true }) + const tmpDir = await mkdtemp(path.join(os.tmpdir(), "conference-kit-")) + + try { + const browser = await chromium.launch() + const context = await browser.newContext({ + deviceScaleFactor: SCALE, + // Wide enough that both banners render side-by-side without wrapping; + // the screenshot itself is scoped to a single banner element. + viewport: { width: 1600, height: 1600 }, + colorScheme: "light", + }) + const page = await context.newPage() + + console.log(`[conference-kit] loading ${PAGE}`) + await page.goto(PAGE, { waitUntil: "networkidle" }) + await page.evaluate(() => document.fonts.ready) + + for (const { slug, colorScheme } of BANNERS) { + await page.emulateMedia({ colorScheme }) + // next-themes listens for the prefers-color-scheme change and toggles + // html.dark; wait for that to settle before screenshotting. + await page.waitForFunction( + expected => + document.documentElement.classList.contains("dark") === + (expected === "dark"), + colorScheme, + { timeout: 2000 }, + ) + const target = page.locator(`[data-print-banner="${slug}"]`) + await target.waitFor({ state: "visible" }) + await target.screenshot({ path: path.join(tmpDir, `${slug}.png`) }) + } + + await browser.close() + + const zipPath = path.join(PUBLIC_DIR, "conference-kit.zip") + await rm(zipPath, { force: true }) + await exec("zip", [ + "-j", + zipPath, + ...BANNERS.map(b => path.join(tmpDir, `${b.slug}.png`)), + ]) + console.log( + `[conference-kit] wrote ${path.relative(process.cwd(), zipPath)}`, + ) + } finally { + await rm(tmpDir, { recursive: true, force: true }) + } +} + +main().catch(err => { + console.error(err) + process.exit(1) +}) diff --git a/src/app/conf/conference-kit/_components/ai-hero-banner.tsx b/src/app/conf/conference-kit/_components/ai-hero-banner.tsx new file mode 100644 index 0000000000..e5209fc50f --- /dev/null +++ b/src/app/conf/conference-kit/_components/ai-hero-banner.tsx @@ -0,0 +1,88 @@ +import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" +import CheckIcon from "@/app/conf/_design-system/pixelarticons/check.svg?svgr" +import logoMask from "@/app/conf/2026/components/cta-card-section/logo-mask.webp" +import { GraphQLWordmarkLogo } from "@/icons" + +import { BannerFrame } from "./banner-frame" +import { BannerTrustedFooter } from "./trusted-logos" + +const bullets = [ + "Typed schemas LLMs can reason about", + "Frictionless distributed development", + "One contract for UIs and autonomous agents", +] + +export function AiHeroBanner() { + return ( + +
+ +
+ +
+

+ The API language for humans and agents +

+ + +
+ +
+
+ +
+ + + + ) +} diff --git a/src/app/conf/conference-kit/_components/amsterdam-banner.tsx b/src/app/conf/conference-kit/_components/amsterdam-banner.tsx new file mode 100644 index 0000000000..03c22f850e --- /dev/null +++ b/src/app/conf/conference-kit/_components/amsterdam-banner.tsx @@ -0,0 +1,123 @@ +import { CalendarIcon } from "@/app/conf/_design-system/pixelarticons/calendar-icon" +import { PinIcon } from "@/app/conf/_design-system/pixelarticons/pin-icon" +import { Tag } from "@/app/conf/_design-system/tag" +import fostLogo from "@/app/day/2026/assets/fost-logo.avif" +import amsterdamImage from "./amsterdam.png" + +import { BannerFrame } from "./banner-frame" +import { QRCodeSVG } from "./qr-code" +import { BlobStripes } from "./blob-stripes" +import Image from "next/image" +import { GraphQLWordmarkLogo } from "@/icons" + +export function AmsterdamBanner() { + return ( + + + +
+ +
+ +

+ GraphQL Day +
+ Amsterdam +
+ 2026 +

+ +
+

+ Community-organized GraphQL events at conferences worldwide. +

+
+ talks + demos + community +
+
+ +
+
+
+ Amsterdam canal houses +
+
+
+ +
Jun 9–10, 2026
+
+
+ +
+ Amsterdam, +
+ The Netherlands +
+
+
+
+ + hosted at + + FOST +
+
+
+ +
+
+
+ Visit the event +
+
+ graphql.org/day/amsterdam +
+
+
+ +
+
+ + ) +} diff --git a/src/app/conf/conference-kit/_components/amsterdam-skyline.tsx b/src/app/conf/conference-kit/_components/amsterdam-skyline.tsx new file mode 100644 index 0000000000..851fe16f8b --- /dev/null +++ b/src/app/conf/conference-kit/_components/amsterdam-skyline.tsx @@ -0,0 +1,96 @@ +import clsx from "clsx" + +export interface AmsterdamSkylineProps { + className?: string + moonClassName?: string +} + +export function AmsterdamSkyline({ + className, + moonClassName, +}: AmsterdamSkylineProps) { + return ( + + + + {/* stepped gable */} + + + + + {/* bell gable */} + + + + + + {/* neck gable */} + + + + + {/* peaked */} + + + + + {/* taller stepped */} + + + + + + + + {/* bell */} + + + + + + {/* stepped */} + + + + + {/* simple */} + + + + {/* bridge arches */} + + + + + + + + {/* tiny boat */} + + + + {/* water ripples */} + + + + + ) +} diff --git a/src/app/conf/conference-kit/_components/amsterdam.png b/src/app/conf/conference-kit/_components/amsterdam.png new file mode 100644 index 0000000000..6574eccf8b Binary files /dev/null and b/src/app/conf/conference-kit/_components/amsterdam.png differ diff --git a/src/app/conf/conference-kit/_components/banner-frame.tsx b/src/app/conf/conference-kit/_components/banner-frame.tsx new file mode 100644 index 0000000000..af6e379e80 --- /dev/null +++ b/src/app/conf/conference-kit/_components/banner-frame.tsx @@ -0,0 +1,40 @@ +import clsx from "clsx" + +export const BANNER_W = 600 +export const BANNER_H = 1412 + +export interface BannerFrameProps extends React.HTMLAttributes { + children: React.ReactNode + caption: string + /** Stable id used for the rendered PNG filename and as the print target. */ + slug: string +} + +export function BannerFrame({ + children, + caption, + slug, + className, + style, + ...rest +}: BannerFrameProps) { + return ( +
+
+ {children} +
+
+ {caption} + · 850 × 2000 mm · 1:2.353 +
+
+ ) +} diff --git a/src/app/conf/conference-kit/_components/blob-stripes.tsx b/src/app/conf/conference-kit/_components/blob-stripes.tsx new file mode 100644 index 0000000000..d075d6af03 --- /dev/null +++ b/src/app/conf/conference-kit/_components/blob-stripes.tsx @@ -0,0 +1,53 @@ +import clsx from "clsx" + +import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" +// Most other repo blobs are wide-and-flat — the use-cases bean is the only +// near-square one (1421×1138, ratio 1.25), so it doesn't get stretched into +// a skinny strip when scaled up on a tall portrait banner. +import blurBlob from "@/components/index-page/use-cases/blur-bean.webp" + +export interface BlobStripesProps { + className?: string + evenClassName?: string + oddClassName?: string + stripeWidth?: string + angle?: string + /** mask-position passed through. */ + position?: string + /** mask-size passed through. */ + size?: string +} + +export function BlobStripes({ + className, + evenClassName, + oddClassName, + stripeWidth, + angle, + position = "center", + size = "140%", +}: BlobStripesProps) { + return ( +
+ +
+ ) +} diff --git a/src/app/conf/conference-kit/_components/city-query-snippet.tsx b/src/app/conf/conference-kit/_components/city-query-snippet.tsx new file mode 100644 index 0000000000..a378b3205f --- /dev/null +++ b/src/app/conf/conference-kit/_components/city-query-snippet.tsx @@ -0,0 +1,27 @@ +"use client" + +import type { ComponentPropsWithoutRef } from "react" +import { Code } from "nextra/components" + +import { Pre } from "@/components/pre" + +import _CityQuery from "./city-query.mdx" + +// Mirrors the components-map override the landing-page Wires illustration +// uses for its API-gateway query: render the snippet frameless (no header, +// no border, no bg) so the parent's .highlightsQuery class can extend the +// per-line nth-child highlight overlays flush to the snippet's edges. +const framelessSnippetComponents = { + pre: (props: ComponentPropsWithoutRef) => ( +
+  ),
+  code: Code,
+}
+
+export function CityQuerySnippet() {
+  return <_CityQuery components={framelessSnippetComponents} />
+}
diff --git a/src/app/conf/conference-kit/_components/city-query.mdx b/src/app/conf/conference-kit/_components/city-query.mdx
new file mode 100644
index 0000000000..ff554c21ef
--- /dev/null
+++ b/src/app/conf/conference-kit/_components/city-query.mdx
@@ -0,0 +1,11 @@
+```graphql word-wrap=false
+query getCity($city: String) {
+  cities(name: $city) {
+    population
+    weather {
+      temperature
+      precipitation
+    }
+  }
+}
+```
diff --git a/src/app/conf/conference-kit/_components/language-banner.tsx b/src/app/conf/conference-kit/_components/language-banner.tsx
new file mode 100644
index 0000000000..a9f7fec092
--- /dev/null
+++ b/src/app/conf/conference-kit/_components/language-banner.tsx
@@ -0,0 +1,115 @@
+import { GraphQLWordmarkLogo } from "@/icons"
+import { MobileDiagram } from "@/components/index-page/what-is-graphql/wires"
+import wiresStyles from "@/components/index-page/what-is-graphql/wires.module.css"
+
+import { BannerFrame } from "./banner-frame"
+import { BlobStripes } from "./blob-stripes"
+import { CityQuerySnippet } from "./city-query-snippet"
+import { QRCodeSVG } from "./qr-code"
+import { TRUSTED_LOGOS } from "./trusted-logos"
+
+export function LanguageBanner() {
+  return (
+    
+      
+
+      
+ +
+ schema-first + + typesafe + + flexible + + fast +
+
+ +

+ The query +
+ language +
+ for your API +

+ +

+ An open-standard query language and runtime that lets clients ask for + exactly the data they need — and nothing more. +

+ +
+ +
+
+ +
+
+
+ +
+
+
+ Learn GraphQL +
+
+ graphql.org/learn +
+
+
+ +
+
+ +
+
+ Trusted in production +
+
+ {TRUSTED_LOGOS.map(({ name, Component, height }) => ( +
+ +
+ ))} +
+
+
+ ) +} diff --git a/src/app/conf/conference-kit/_components/qr-code.tsx b/src/app/conf/conference-kit/_components/qr-code.tsx new file mode 100644 index 0000000000..bc839a91fd --- /dev/null +++ b/src/app/conf/conference-kit/_components/qr-code.tsx @@ -0,0 +1,41 @@ +"use client" + +import QRCode from "qrcode" + +export interface QRCodeSVGProps { + value: string + size?: number + color?: string + /** ECC level: L=7%, M=15% (default), Q=25%, H=30%. Higher = bigger module count. */ + errorCorrectionLevel?: "L" | "M" | "Q" | "H" +} + +export function QRCodeSVG({ + value, + size = 56, + color = "currentColor", + errorCorrectionLevel = "M", +}: QRCodeSVGProps) { + const qr = QRCode.create(value, { errorCorrectionLevel }) + const N = qr.modules.size + const cells: [number, number][] = [] + for (let r = 0; r < N; r++) { + for (let c = 0; c < N; c++) { + if (qr.modules.get(r, c)) cells.push([r, c]) + } + } + return ( + + ) +} diff --git a/src/app/conf/conference-kit/_components/trusted-logos.tsx b/src/app/conf/conference-kit/_components/trusted-logos.tsx new file mode 100644 index 0000000000..1337b125ac --- /dev/null +++ b/src/app/conf/conference-kit/_components/trusted-logos.tsx @@ -0,0 +1,61 @@ +import MetaLockup from "@/components/index-page/trusted-by/logos/Meta.svg?svgr" +import IBMWordmark from "@/components/index-page/trusted-by/logos/IBM.svg?svgr" +import AirBnBLockup from "@/components/index-page/trusted-by/logos/AirBnB.svg?svgr" +import IntuitWordmark from "@/components/index-page/trusted-by/logos/Intuit.svg?svgr" +import AWSLogo from "@/components/index-page/trusted-by/logos/AWS.svg?svgr" +import PayPalWordmark from "@/components/index-page/trusted-by/logos/PayPal.svg?svgr" +import NewYorkTimesWordmark from "@/components/index-page/trusted-by/logos/NewYorkTimes.svg?svgr" +import StarbucksWordmark from "@/components/index-page/trusted-by/logos/Starbucks.svg?svgr" +import GitHubLockup from "@/components/index-page/trusted-by/logos/GitHub.svg?svgr" +import ShopifyMonotoneLockup from "@/components/index-page/trusted-by/logos/ShopifyMonotone.svg?svgr" + +interface TrustedLogo { + name: string + Component: React.FC> + // Optical heights tuned by eye so wordmarks share a consistent cap height + // despite differing viewBox aspect ratios. + height: number +} + +export const TRUSTED_LOGOS: TrustedLogo[] = [ + { name: "Meta", Component: MetaLockup, height: 20 }, + { name: "AWS", Component: AWSLogo, height: 24 }, + { name: "Airbnb", Component: AirBnBLockup, height: 22 }, + { name: "Intuit", Component: IntuitWordmark, height: 22 }, + { name: "IBM", Component: IBMWordmark, height: 22 }, + { name: "PayPal", Component: PayPalWordmark, height: 22 }, + { name: "New York Times", Component: NewYorkTimesWordmark, height: 18 }, + { name: "Starbucks", Component: StarbucksWordmark, height: 24 }, + { name: "Shopify", Component: ShopifyMonotoneLockup, height: 22 }, + { name: "GitHub", Component: GitHubLockup, height: 26 }, +] + +// Logos collapsed to a single white silhouette via brightness(0) invert(1). +// We can't fix every per-element fill on third-party SVGs, so we forfeit +// brand color in exchange for predictable contrast on dark/gradient banners. +export function BannerTrustedFooter() { + return ( +
+
+ Trusted in production +
+
+ {TRUSTED_LOGOS.map(({ name, Component, height }) => ( +
+ +
+ ))} +
+
+ ) +} diff --git a/src/app/conf/conference-kit/layout.tsx b/src/app/conf/conference-kit/layout.tsx new file mode 100644 index 0000000000..46de85c467 --- /dev/null +++ b/src/app/conf/conference-kit/layout.tsx @@ -0,0 +1,59 @@ +import { ReactElement, ReactNode } from "react" +import { Metadata } from "next" +import { ThemeProvider } from "next-themes" + +import { NewFontsStyleTag } from "../../fonts" +import "../../colors.css" + +import { Navbar } from "../2026/components/navbar" +import { Footer } from "../2026/components/footer" +import { GraphQLConfLogoLink } from "../2026/components/graphql-conf-logo-link" +import { GALLERY_LINK } from "../2026/links" + +export const metadata: Metadata = { + title: { + absolute: "Conference Kit | GraphQL", + template: "%s | GraphQL Conference Kit", + }, + description: + "Print-ready roll-up banners for GraphQL Foundation conferences and community events.", +} + +export default function ConferenceKitLayout({ + children, +}: { + children: ReactNode +}): ReactElement { + return ( + <> + + + +
{children}
+
+