diff --git a/.env.example b/.env.example index d7d9fc14..2693a05a 100644 --- a/.env.example +++ b/.env.example @@ -1,49 +1,18 @@ -# required. redis storage credentials for hit counter's server component (app/notes/[slug]/counter.tsx) and API -# endpoint. currently set automatically by Vercel's Upstash integration. -# https://upstash.com/docs/redis/sdks/ts/getstarted -# https://vercel.com/marketplace/upstash -KV_REST_API_URL= -KV_REST_API_TOKEN= +############### +# IMPORTANT! +# See lib/env.ts for the list of required environment variables and how to get them. +############### -# required. used for /projects grid, built with ISR. only needs the "public_repo" scope since we don't need/want to -# showcase any private repositories, obviously. -# https://github.com/settings/tokens/new?scopes=public_repo GITHUB_TOKEN= - -# optional. privacy-friendly tracking via Umami, either managed or self-hosted. -# this ID can be found in Settings > Websites > Edit > Details. -NEXT_PUBLIC_UMAMI_WEBSITE_ID= -# optional. the base URL of a self-hosted Umami instance (including https://) to proxy requests to. if the website ID is -# set but this isn't, the managed Umami Cloud endpoint is used. -# https://umami.is/docs/bypass-ad-blockers -NEXT_PUBLIC_UMAMI_URL= - -# optional. enables comments on blog posts via GitHub discussions. -# https://giscus.app/ -NEXT_PUBLIC_GISCUS_REPO_ID= +KV_REST_API_TOKEN= +KV_REST_API_URL= NEXT_PUBLIC_GISCUS_CATEGORY_ID= - -# required for production. sends contact form submissions via a server action (see app/contact/actions.ts). may be set -# automatically by Vercel's Resend integration. -# https://resend.com/api-keys -# https://vercel.com/integrations/resend -RESEND_API_KEY= -# required. the destination email for contact form submissions. -RESEND_TO_EMAIL= -# optional, but will throw a warning. send submissions from an approved domain (or subdomain) on the resend account. -# defaults to onboarding@resend.dev. -# sender's real email is passed via a Reply-To header, so setting this makes zero difference to the user. -# https://resend.com/domains -RESEND_FROM_EMAIL= - -# required for production. site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the captcha widget. -# falls back to testing keys if not set or in dev environment: -# https://developers.cloudflare.com/turnstile/troubleshooting/testing/ -NEXT_PUBLIC_TURNSTILE_SITE_KEY= -# used for backend validation of turnstile result. -TURNSTILE_SECRET_KEY= - -# optional. sets "Onion-Location" response header to advertise a hidden service for the site; browsers like Brave and -# Tor Browser will automatically pick this up and offer to redirect users to it. -# https://community.torproject.org/onion-services/advanced/onion-location/ +NEXT_PUBLIC_GISCUS_REPO_ID= NEXT_PUBLIC_ONION_DOMAIN= +NEXT_PUBLIC_TURNSTILE_SITE_KEY= +NEXT_PUBLIC_UMAMI_URL= +NEXT_PUBLIC_UMAMI_WEBSITE_ID= +RESEND_API_KEY= +RESEND_FROM_EMAIL= +RESEND_TO_EMAIL= +TURNSTILE_SECRET_KEY= diff --git a/app/layout.tsx b/app/layout.tsx index 3ea7547e..a9f908f4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,10 +4,10 @@ import { ThemeProvider, ThemeScript } from "../contexts/ThemeContext"; import Header from "../components/Header"; import Footer from "../components/Footer"; import { SkipToContentLink, SkipToContentTarget } from "../components/SkipToContent"; +import { defaultMetadata } from "../lib/helpers/metadata"; import { setRootCssVariables } from "../lib/helpers/styles"; import * as config from "../lib/config"; import { BASE_URL, MAX_WIDTH } from "../lib/config/constants"; -import defaultMetadata from "../lib/config/seo"; import type { Metadata } from "next"; import type { Person, WebSite } from "schema-dts"; diff --git a/app/avatar.jpg b/app/selfie.jpg similarity index 100% rename from app/avatar.jpg rename to app/selfie.jpg diff --git a/components/Header/Header.tsx b/components/Header/Header.tsx index 734a9f8d..46ae5d3a 100644 --- a/components/Header/Header.tsx +++ b/components/Header/Header.tsx @@ -7,7 +7,7 @@ import type { ComponentPropsWithoutRef } from "react"; import styles from "./Header.module.css"; -import avatarImg from "../../app/avatar.jpg"; +import avatarImg from "../../app/selfie.jpg"; export type HeaderProps = ComponentPropsWithoutRef<"header">; diff --git a/lib/config/constants.ts b/lib/config/constants.ts index 65793fb1..c373cf18 100644 --- a/lib/config/constants.ts +++ b/lib/config/constants.ts @@ -3,7 +3,7 @@ export const POSTS_DIR = "notes"; // path to an image used in various places to represent the site, relative to project root // IMPORTANT: must be included in next.config.ts under "outputFileTracingIncludes" -export const AVATAR_PATH = "app/avatar.jpg"; +export const AVATAR_PATH = "app/selfie.jpg"; // maximum width of content wrapper (e.g. for images) in pixels export const MAX_WIDTH = 865; diff --git a/lib/config/seo.ts b/lib/config/seo.ts deleted file mode 100644 index 19039171..00000000 --- a/lib/config/seo.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as config from "."; -import { BASE_URL } from "./constants"; -import type { Metadata } from "next"; - -const defaultMetadata: Metadata = { - metadataBase: new URL(BASE_URL), - title: { - template: `%s – ${config.siteName}`, - default: `${config.siteName} – ${config.shortDescription}`, - }, - description: config.longDescription, - openGraph: { - siteName: config.siteName, - title: { - template: "%s", - default: `${config.siteName} – ${config.shortDescription}`, - }, - url: "/", - locale: config.siteLocale?.replace("-", "_"), - type: "website", - }, - twitter: { - creator: `@${config.authorSocial?.twitter}`, - }, - alternates: { - canonical: "/", - types: { - "application/rss+xml": [ - { - title: `${config.siteName} (RSS)`, - url: "/feed.xml", - }, - ], - "application/atom+xml": [ - { - title: `${config.siteName} (Atom)`, - url: "/feed.atom", - }, - ], - }, - }, - other: { - humans: "/humans.txt", - }, -}; - -export default defaultMetadata; diff --git a/lib/env.ts b/lib/env.ts index b14ec9fb..38accf77 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -3,22 +3,110 @@ import { vercel } from "@t3-oss/env-nextjs/presets-valibot"; import * as v from "valibot"; export const env = createEnv({ - extends: [vercel()], + extends: [ + // NOTE: Some assumptions are sprinkled throughout the code that this site is being deployed on Vercel. If not, find + // and replace `env.VERCEL_` (especially `VERCEL_ENV` and `VERCEL_PROJECT_PRODUCTION_URL`) with more appropriate + // variables. + vercel(), + ], server: { + /** + * Required. GitHub API token used for /projects grid. Only needs the `public_repo` scope since we don't need/want + * to change anything, obviously. + * + * @see https://github.com/settings/tokens/new?scopes=public_repo + */ GITHUB_TOKEN: v.optional(v.pipe(v.string(), v.startsWith("ghp_"))), + + /** + * Required. Redis storage credentials for hit counter's server component (app/notes/[slug]/counter.tsx) and API + * endpoint. Currently set automatically by Vercel's Upstash integration. + * + * @see https://upstash.com/docs/redis/sdks/ts/getstarted + * @see https://vercel.com/marketplace/upstash + */ KV_REST_API_TOKEN: v.string(), + /** + * Required. Redis storage credentials for hit counter's server component (app/notes/[slug]/counter.tsx) and API + * endpoint. Currently set automatically by Vercel's Upstash integration. + * + * @see https://upstash.com/docs/redis/sdks/ts/getstarted + * @see https://vercel.com/marketplace/upstash + */ KV_REST_API_URL: v.pipe(v.string(), v.url(), v.startsWith("https://"), v.endsWith(".upstash.io")), + + /** + * Required. Uses Resend API to send contact form submissions via a server action (see app/contact/actions.ts). May + * be set automatically by Vercel's Resend integration. + * + * @see https://resend.com/api-keys + * @see https://vercel.com/integrations/resend + */ RESEND_API_KEY: v.pipe(v.string(), v.startsWith("re_")), - RESEND_FROM_EMAIL: v.optional(v.pipe(v.string(), v.email())), + /** + * Optional, but will throw a warning if unset. Use an approved domain (or subdomain) on the Resend account to send + * submissions from. Sender's real email is passed via a Reply-To header, so setting this makes zero difference to + * the user, only for deliverability success. Defaults to `onboarding@resend.dev`. + * + * @see https://resend.com/domains + */ + RESEND_FROM_EMAIL: v.optional(v.pipe(v.string(), v.email()), "onboarding@resend.dev"), + /** + * Required. The destination email for contact form submissions. + */ RESEND_TO_EMAIL: v.pipe(v.string(), v.email()), - TURNSTILE_SECRET_KEY: v.optional(v.string()), + + /** + * Required. Secret for Cloudflare `siteverify` API to validate a form's turnstile result on the backend. + * + * @see https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ + */ + TURNSTILE_SECRET_KEY: v.optional(v.string(), "1x0000000000000000000000000000000AA"), }, client: { + /** + * Optional. Enables comments on blog posts via GitHub discussions. + * + * @see https://giscus.app/ + */ NEXT_PUBLIC_GISCUS_CATEGORY_ID: v.optional(v.string()), + /** + * Optional. Enables comments on blog posts via GitHub discussions. + * + * @see https://giscus.app/ + */ NEXT_PUBLIC_GISCUS_REPO_ID: v.optional(v.string()), + + /** + * Optional. Sets an `Onion-Location` header in responses to advertise a URL for the same page but hosted on a + * hidden service on the Tor network. Browsers like Brave and Tor Browser will automatically pick this up and offer + * to redirect users to it. + * + * @see https://community.torproject.org/onion-services/advanced/onion-location/ + */ NEXT_PUBLIC_ONION_DOMAIN: v.optional(v.pipe(v.string(), v.endsWith(".onion"))), - NEXT_PUBLIC_TURNSTILE_SITE_KEY: v.optional(v.string()), - NEXT_PUBLIC_UMAMI_URL: v.optional(v.pipe(v.string(), v.url())), + + /** + * Required. Site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the captcha widget. Falls back to + * testing keys if not set or in dev environment. + * + * @see https://developers.cloudflare.com/turnstile/troubleshooting/testing/ + */ + NEXT_PUBLIC_TURNSTILE_SITE_KEY: v.optional(v.string(), "XXXX.DUMMY.TOKEN.XXXX"), + + /** + * Optional. The base URL of a self-hosted Umami instance (including https://) to proxy requests to. If the website + * ID is set but this isn't, the managed Umami Cloud endpoint at https://cloud.umami.is is used. + * + * @see https://umami.is/docs/bypass-ad-blockers + */ + NEXT_PUBLIC_UMAMI_URL: v.optional(v.pipe(v.string(), v.startsWith("https://"), v.url()), "https://cloud.umami.is"), + /** + * Optional. Enables privacy-friendly tracking via Umami, either managed or self-hosted. This ID can be found in the + * dashboard under Settings > Websites > Edit > Details. + * + * @see https://umami.is/docs/collect-data + */ NEXT_PUBLIC_UMAMI_WEBSITE_ID: v.optional(v.string()), }, experimental__runtimeEnv: { diff --git a/lib/helpers/metadata.ts b/lib/helpers/metadata.ts index ff2e5166..f818c15c 100644 --- a/lib/helpers/metadata.ts +++ b/lib/helpers/metadata.ts @@ -1,6 +1,49 @@ -import defaultMetadata from "../config/seo"; +import * as config from "../config"; +import { BASE_URL } from "../config/constants"; import type { Metadata } from "next"; +export const defaultMetadata: Metadata = { + metadataBase: new URL(BASE_URL), + title: { + template: `%s – ${config.siteName}`, + default: `${config.siteName} – ${config.shortDescription}`, + }, + description: config.longDescription, + openGraph: { + siteName: config.siteName, + title: { + template: "%s", + default: `${config.siteName} – ${config.shortDescription}`, + }, + url: "/", + locale: config.siteLocale?.replace("-", "_"), + type: "website", + }, + twitter: { + creator: `@${config.authorSocial?.twitter}`, + }, + alternates: { + canonical: "/", + types: { + "application/rss+xml": [ + { + title: `${config.siteName} (RSS)`, + url: "/feed.xml", + }, + ], + "application/atom+xml": [ + { + title: `${config.siteName} (Atom)`, + url: "/feed.atom", + }, + ], + }, + }, + other: { + humans: "/humans.txt", + }, +}; + /** * Helper function to deep merge a page's metadata into the default site metadata * @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata diff --git a/next.config.ts b/next.config.ts index 6cdbaaa9..c0fc1f9a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -39,7 +39,11 @@ const nextConfig: NextConfig = { outputFileTracingExcludes: { "*": ["./public/**/*", "**/*.mp4", "**/*.webm", "**/*.vtt"], }, - transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"], + transpilePackages: [ + // https://env.t3.gg/docs/nextjs#create-your-schema + "@t3-oss/env-core", + "@t3-oss/env-nextjs", + ], webpack: (config) => { config.module.rules.push({ test: /\.(mp4|webm|vtt)$/i, diff --git a/package.json b/package.json index c70df3a6..016fbc4a 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "@giscus/react": "^3.1.0", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", - "@next/bundle-analyzer": "15.3.0-canary.45", - "@next/mdx": "15.3.0-canary.45", + "@next/bundle-analyzer": "15.3.0-canary.46", + "@next/mdx": "15.3.0-canary.46", "@octokit/graphql": "^8.2.1", "@octokit/graphql-schema": "^15.26.0", "@t3-oss/env-nextjs": "^0.12.0", @@ -37,7 +37,7 @@ "geist": "^1.3.1", "html-entities": "^2.6.0", "lucide-react": "0.487.0", - "next": "15.3.0-canary.45", + "next": "15.3.0-canary.46", "polished": "^4.3.1", "prop-types": "^15.8.1", "react": "19.1.0", @@ -80,7 +80,7 @@ "babel-plugin-react-compiler": "19.0.0-beta-e993439-20250405", "cross-env": "^7.0.3", "eslint": "^9.24.0", - "eslint-config-next": "15.3.0-canary.45", + "eslint-config-next": "15.3.0-canary.46", "eslint-config-prettier": "^10.1.1", "eslint-plugin-css-modules": "^2.12.0", "eslint-plugin-import": "^2.31.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eeb04524..cc673166 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,11 +27,11 @@ importers: specifier: ^3.1.0 version: 3.1.0(@types/react@19.1.0)(react@19.1.0) '@next/bundle-analyzer': - specifier: 15.3.0-canary.45 - version: 15.3.0-canary.45 + specifier: 15.3.0-canary.46 + version: 15.3.0-canary.46 '@next/mdx': - specifier: 15.3.0-canary.45 - version: 15.3.0-canary.45(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0)) + specifier: 15.3.0-canary.46 + version: 15.3.0-canary.46(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0)) '@octokit/graphql': specifier: ^8.2.1 version: 8.2.1 @@ -61,7 +61,7 @@ importers: version: 4.2.2 geist: specifier: ^1.3.1 - version: 1.3.1(next@15.3.0-canary.45(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 1.3.1(next@15.3.0-canary.46(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) html-entities: specifier: ^2.6.0 version: 2.6.0 @@ -69,8 +69,8 @@ importers: specifier: 0.487.0 version: 0.487.0(react@19.1.0) next: - specifier: 15.3.0-canary.45 - version: 15.3.0-canary.45(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: 15.3.0-canary.46 + version: 15.3.0-canary.46(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) polished: specifier: ^4.3.1 version: 4.3.1 @@ -193,8 +193,8 @@ importers: specifier: ^9.24.0 version: 9.24.0 eslint-config-next: - specifier: 15.3.0-canary.45 - version: 15.3.0-canary.45(eslint@9.24.0)(typescript@5.8.3) + specifier: 15.3.0-canary.46 + version: 15.3.0-canary.46(eslint@9.24.0)(typescript@5.8.3) eslint-config-prettier: specifier: ^10.1.1 version: 10.1.1(eslint@9.24.0) @@ -648,17 +648,17 @@ packages: '@napi-rs/wasm-runtime@0.2.8': resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==} - '@next/bundle-analyzer@15.3.0-canary.45': - resolution: {integrity: sha512-BsGWUzaSWtTMQujKXO91guSqbmiV1kr8Kwj0HPbREk7FbHuJqPUMi/mDnKERjQW06EKtT+zEdw72YOw2ajknXw==} + '@next/bundle-analyzer@15.3.0-canary.46': + resolution: {integrity: sha512-smlcbDYovPsUlGq/UkvS/5JHpENYpk1Kh7S2Bl3NN2ekxlcMqpfxG9Pbm6gaR9K/ukdzhD3F5i3asOsa6KMtqg==} - '@next/env@15.3.0-canary.45': - resolution: {integrity: sha512-AldaMWqETxXCzCkr0eMCeC5Jz+nN5yai6xiKFFCkwzZ1dSLhRngdl83RluzAKqF/6H5/E25dMkbVdgqa8ol6IA==} + '@next/env@15.3.0-canary.46': + resolution: {integrity: sha512-LGp2KJVajqocKZAcNknWhoKN+WLV7Ja3wjhHcgMVe1kboSESX6wYpyYl1tl240NRNsdYmKmbsYMua5K121ng3w==} - '@next/eslint-plugin-next@15.3.0-canary.45': - resolution: {integrity: sha512-DMxO+TwIATKWNWSzqVmYvH7xCIb8BJIuN38gBL3EnMyv1t2tml47AzQDAcZxEqfxwaEGiNKc0g02EE3bHVgfPg==} + '@next/eslint-plugin-next@15.3.0-canary.46': + resolution: {integrity: sha512-B97MOWegf9MV0OxpnEFh2P3ByxFjH4H/dMGGvcEbyVXJSyt7Xl0mQufC5Ku2SjDxaIoV7iB9WZmGQFKlXLOJJA==} - '@next/mdx@15.3.0-canary.45': - resolution: {integrity: sha512-JZfy25lkczYwD11uX5bCQxvMWu7UvKNZuEfsIbVobRzANQ9xCoxaAAk2TsBoB6l5ToUUeoJV4ISCeXthg5RuPA==} + '@next/mdx@15.3.0-canary.46': + resolution: {integrity: sha512-MxVBbvO0ocI+Q/uAy5O0tYFBM9p8XeUsFVXdpu5dKf5tUQ0buEbtLcZPBT2QL9qXn7CsLvoJU6Pv+yhNoXa+dw==} peerDependencies: '@mdx-js/loader': '>=0.15.0' '@mdx-js/react': '>=0.15.0' @@ -668,50 +668,50 @@ packages: '@mdx-js/react': optional: true - '@next/swc-darwin-arm64@15.3.0-canary.45': - resolution: {integrity: sha512-uGgfYxaRlWZC8tW5mU/NfsaEOVTWLWMLJeF1jcZcZ0wy6U+ouzMgDhCm1KbpEqy84yI/24TL5qtXGO4zckISTQ==} + '@next/swc-darwin-arm64@15.3.0-canary.46': + resolution: {integrity: sha512-Ug4gotScPU3/kHpsrG1N2A+7QD4CQKGz4hv9YwmXy05OUzQ7Ysc2qa/9ijSx02D88MXcCuPzChri+EKNgtmjOQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.3.0-canary.45': - resolution: {integrity: sha512-7cc2mOo15vNTu0nSjQNOSUXMuKCwkfTPjOUsWAZpl9N3tSg3oK7YEtvpPHq4IkWmI2nvGVUvjmD9ecBiBx3Z4w==} + '@next/swc-darwin-x64@15.3.0-canary.46': + resolution: {integrity: sha512-byMk0jqJVRHhFGGFBjKKXI4KhgkDeHQ715ReurOHrc+iCdULgP7q4cvuNTgrsv647+6q5utw4i6lh7ws6/k8Sg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.3.0-canary.45': - resolution: {integrity: sha512-UaXD2cNQeh4JBKZZCLAsNe7E4ahQgGiQuqYELb6InISEH6yMIKdofqFVGbhsntf4jiyyMx99plmeFNTIqtuLOw==} + '@next/swc-linux-arm64-gnu@15.3.0-canary.46': + resolution: {integrity: sha512-BzxhFv0kWyZLPmfNao6knlJD5k97yNJMbgvL/KdZReLqyxNFeKM4DOrMi4VwWfEzT1gdCYXTSppv9qaA+bp5Yg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.3.0-canary.45': - resolution: {integrity: sha512-Pyf426xLkc7IG6jEqFBhqHBaVEWcGtXQL6lBJzc5BFL/f9buwWL/RxX+eBl0KU+z3lSOYHzU2GKAEy+4xpJN1Q==} + '@next/swc-linux-arm64-musl@15.3.0-canary.46': + resolution: {integrity: sha512-MkNye3jjLpYAP4kADYxJ1mb9fKAUS9v3JVtWDJ1rmdbgoLF7ZXhyXKtrXW3PP/LKcs5/52N2rt833tZ1Hzw4yA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.3.0-canary.45': - resolution: {integrity: sha512-X8VrgcwtidmUGS1nd1XicvUI+UIK2ua2HLzZFsu1A/qfgoQzuoh6hPMe2hW9uPs+U4cTkTLDKaDEqOBQOggyng==} + '@next/swc-linux-x64-gnu@15.3.0-canary.46': + resolution: {integrity: sha512-yrTrDLswtfWAMkspvaWaenCIi6+K+8kx7WNK6RFAP2fnpbu9qG2AbnByzDeMcMT7Sew/YxFH46Nwri+N0U3Cyg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.3.0-canary.45': - resolution: {integrity: sha512-G1bf+/J6v4KWPDHlwJQMwo3Iud1eosWw1C0fz+7+Ag9QcZ3M0ZyJwxEusA4h4f+dFXMI7nJHseujObDjhhfepA==} + '@next/swc-linux-x64-musl@15.3.0-canary.46': + resolution: {integrity: sha512-xQrtPYWvXJ/9mz9TGwnVy2gRjjV+iRlt5+ErgDb+bOLycgg3fjZ3pZCx/LD4o4LhnHv/q8TisRkV+XGDit2zhw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.3.0-canary.45': - resolution: {integrity: sha512-4G2yvJOhstx5f0aF+g8fNWpV9qsl2RiC3l+v0AwdhDvqTCft/WlDIGtDTuhnuJ0l6LwcdoxexwynOSU46Ype+w==} + '@next/swc-win32-arm64-msvc@15.3.0-canary.46': + resolution: {integrity: sha512-20tz3ZyqbKKFu9ZmzoEMKVcsMUpK30S1s2gHce9GmunIwxv3xeXDqsawKZzxNoNjfFdhOeMu5Yqgpk5gi/OMzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.3.0-canary.45': - resolution: {integrity: sha512-IiYSgwKsj5qEXKXspObHiyzMP0Iv0bDmlwl3yOIXvWmS2K7MOOUW6pqafGXjtgVzBmPFDA+89UwY6+d4Nv/kOg==} + '@next/swc-win32-x64-msvc@15.3.0-canary.46': + resolution: {integrity: sha512-JoqfG99pgJtIrjqLvAMoLxbeGZtMFR0IWcwF0aiW4NcG4JDrGL8Cz7BD6pkyc21ZQshqdpTX0eSYjbHyXayySw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1552,8 +1552,8 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-config-next@15.3.0-canary.45: - resolution: {integrity: sha512-8Bf/O6qY80cEttW1Usz5zOu27TTdnZDQGhe8VbFvP7MU16UsPKCN0CyEun/d6k8ahYe6kw607ELRRJAbtH6XSA==} + eslint-config-next@15.3.0-canary.46: + resolution: {integrity: sha512-WjQkuA7J0hipwV+7E6AVrAUatPbytE3Jac97APOyRWIMo0X3BhLkFcPCoK4r/bismUO9QTFJQTixywQzWvUw8g==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -2643,8 +2643,8 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@15.3.0-canary.45: - resolution: {integrity: sha512-R9Pg4qlImyZLJpKv1CUvr9dIV2EN9NVLBq/x9cOAbHoS/bmv8xLEc+2OPCOmIzXxIIK6VByPWYQ7EcatsDxK8A==} + next@15.3.0-canary.46: + resolution: {integrity: sha512-U4iz2UOvmZb2UtsPREWaM/W20L1hBUJlfWkZBv+nFlX6d6JYYhNVGJWXgOEHQDE71JxOGFqcXJKjjws+IOD1lw==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -4174,48 +4174,48 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true - '@next/bundle-analyzer@15.3.0-canary.45': + '@next/bundle-analyzer@15.3.0-canary.46': dependencies: webpack-bundle-analyzer: 4.10.1 transitivePeerDependencies: - bufferutil - utf-8-validate - '@next/env@15.3.0-canary.45': {} + '@next/env@15.3.0-canary.46': {} - '@next/eslint-plugin-next@15.3.0-canary.45': + '@next/eslint-plugin-next@15.3.0-canary.46': dependencies: fast-glob: 3.3.1 - '@next/mdx@15.3.0-canary.45(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0))': + '@next/mdx@15.3.0-canary.46(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0))': dependencies: source-map: 0.7.4 optionalDependencies: '@mdx-js/loader': 3.1.0(acorn@8.14.1) '@mdx-js/react': 3.1.0(@types/react@19.1.0)(react@19.1.0) - '@next/swc-darwin-arm64@15.3.0-canary.45': + '@next/swc-darwin-arm64@15.3.0-canary.46': optional: true - '@next/swc-darwin-x64@15.3.0-canary.45': + '@next/swc-darwin-x64@15.3.0-canary.46': optional: true - '@next/swc-linux-arm64-gnu@15.3.0-canary.45': + '@next/swc-linux-arm64-gnu@15.3.0-canary.46': optional: true - '@next/swc-linux-arm64-musl@15.3.0-canary.45': + '@next/swc-linux-arm64-musl@15.3.0-canary.46': optional: true - '@next/swc-linux-x64-gnu@15.3.0-canary.45': + '@next/swc-linux-x64-gnu@15.3.0-canary.46': optional: true - '@next/swc-linux-x64-musl@15.3.0-canary.45': + '@next/swc-linux-x64-musl@15.3.0-canary.46': optional: true - '@next/swc-win32-arm64-msvc@15.3.0-canary.45': + '@next/swc-win32-arm64-msvc@15.3.0-canary.46': optional: true - '@next/swc-win32-x64-msvc@15.3.0-canary.45': + '@next/swc-win32-x64-msvc@15.3.0-canary.46': optional: true '@nodelib/fs.scandir@2.1.5': @@ -5140,9 +5140,9 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@15.3.0-canary.45(eslint@9.24.0)(typescript@5.8.3): + eslint-config-next@15.3.0-canary.46(eslint@9.24.0)(typescript@5.8.3): dependencies: - '@next/eslint-plugin-next': 15.3.0-canary.45 + '@next/eslint-plugin-next': 15.3.0-canary.46 '@rushstack/eslint-patch': 1.11.0 '@typescript-eslint/eslint-plugin': 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0)(typescript@5.8.3))(eslint@9.24.0)(typescript@5.8.3) '@typescript-eslint/parser': 8.29.1(eslint@9.24.0)(typescript@5.8.3) @@ -5561,9 +5561,9 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.3.1(next@15.3.0-canary.45(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): + geist@1.3.1(next@15.3.0-canary.46(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): dependencies: - next: 15.3.0-canary.45(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.0-canary.46(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) gensync@1.0.0-beta.2: {} @@ -6694,9 +6694,9 @@ snapshots: natural-compare@1.4.0: {} - next@15.3.0-canary.45(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@15.3.0-canary.46(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@next/env': 15.3.0-canary.45 + '@next/env': 15.3.0-canary.46 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 @@ -6706,14 +6706,14 @@ snapshots: react-dom: 19.1.0(react@19.1.0) styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.3.0-canary.45 - '@next/swc-darwin-x64': 15.3.0-canary.45 - '@next/swc-linux-arm64-gnu': 15.3.0-canary.45 - '@next/swc-linux-arm64-musl': 15.3.0-canary.45 - '@next/swc-linux-x64-gnu': 15.3.0-canary.45 - '@next/swc-linux-x64-musl': 15.3.0-canary.45 - '@next/swc-win32-arm64-msvc': 15.3.0-canary.45 - '@next/swc-win32-x64-msvc': 15.3.0-canary.45 + '@next/swc-darwin-arm64': 15.3.0-canary.46 + '@next/swc-darwin-x64': 15.3.0-canary.46 + '@next/swc-linux-arm64-gnu': 15.3.0-canary.46 + '@next/swc-linux-arm64-musl': 15.3.0-canary.46 + '@next/swc-linux-x64-gnu': 15.3.0-canary.46 + '@next/swc-linux-x64-musl': 15.3.0-canary.46 + '@next/swc-win32-arm64-msvc': 15.3.0-canary.46 + '@next/swc-win32-x64-msvc': 15.3.0-canary.46 babel-plugin-react-compiler: 19.0.0-beta-e993439-20250405 sharp: 0.34.1 transitivePeerDependencies: