diff --git a/web/app/modules/article.tsx b/web/app/modules/article.tsx index ae56e6d4..b7078fc8 100644 --- a/web/app/modules/article.tsx +++ b/web/app/modules/article.tsx @@ -3,6 +3,7 @@ import { mergeMeta } from "./meta"; import { InfoPill } from "./info-pill"; import clsx from "clsx"; import { InfoCard } from "./info-card"; +import { BASE_URL } from "./app"; export type NewsArticleHandle = { slug: string; @@ -20,6 +21,10 @@ export const composeArticleMeta = mergeMeta(({ matches }) => { return [ { title: `${article?.title} | CRAN/E` }, { name: "description", content: article?.subline }, + { property: "og:title", content: `${article?.title} | CRAN/E` }, + { property: "og:url", content: `${BASE_URL}/press/news/${article?.slug}` }, + { property: "og:description", content: article?.subline }, + { property: "og:image", content: `${BASE_URL}/press/news/og` }, ]; }); diff --git a/web/app/modules/contact-pill.tsx b/web/app/modules/contact-pill.tsx index dcfd1b0e..b79f54b1 100644 --- a/web/app/modules/contact-pill.tsx +++ b/web/app/modules/contact-pill.tsx @@ -62,7 +62,7 @@ export function ContactPill(props: Props) { className="text-gold-1 dark:text-gold-2" /> } - className="border-transparent bg-gold-10 text-gold-1 dark:bg-gold-11 dark:text-gold-2" + className="border-transparent text-gold-1 dark:bg-gold-12" > Maintainer diff --git a/web/app/modules/footer.tsx b/web/app/modules/footer.tsx index 509edb98..d1acdd40 100644 --- a/web/app/modules/footer.tsx +++ b/web/app/modules/footer.tsx @@ -3,7 +3,6 @@ import { ExternalLink } from "./external-link"; import { cva, VariantProps } from "cva"; import { ReactNode } from "react"; import clsx from "clsx"; -import { RiGithubLine } from "@remixicon/react"; const BASE_ITEMS: Array<{ label: string; href: string }> = [ { label: "About", href: "/about" }, @@ -53,12 +52,11 @@ export function Footer(props: Props) { href="https://github.com/flaming-codes/crane-app" className="underline-offset-4 hover:brightness-75" > - - Github + Github {version && ( -
  • +
  • v{version} diff --git a/web/app/modules/meta-og-image.server.tsx b/web/app/modules/meta-og-image.server.tsx new file mode 100644 index 00000000..a41c5e28 --- /dev/null +++ b/web/app/modules/meta-og-image.server.tsx @@ -0,0 +1,139 @@ +// ./app/utils/createOGImage.server.tsx + +import { Resvg } from "@resvg/resvg-js"; +import type { SatoriOptions } from "satori"; +import satori from "satori"; + +const OG_IMAGE_HEIGHT = 630; +const OG_IMAGE_WIDTH = 1200; + +function getRegularSans(baseUrl: string) { + return fetch(new URL(`${baseUrl}/fonts/Inter-Regular.ttf`)).then( + async (res) => { + return res.arrayBuffer(); + }, + ); +} + +async function getBaseOptions( + requestUrl: string, + partial: Partial = {}, +): Promise { + const fontSansData = await getRegularSans(requestUrl); + + return { + width: OG_IMAGE_WIDTH, + height: OG_IMAGE_HEIGHT, + fonts: [ + { + name: "Inter", + data: fontSansData, + weight: 400, + style: "normal", + }, + ], + ...partial, + }; +} + +export async function composeAuthorOGImage(params: { + name: string; + requestUrl: string; +}) { + const { name, requestUrl } = params; + + // Design the image and generate an SVG with "satori" + const svg = await satori( +
    + {name} +
    , + await getBaseOptions(requestUrl), + ); + + return new Resvg(svg).render().asPng(); +} + +export async function composePackageOGImage(params: { + name: string; + requestUrl: string; +}) { + const { name, requestUrl } = params; + + // Design the image and generate an SVG with "satori" + const svg = await satori( +
    + {name} +
    , + await getBaseOptions(requestUrl), + ); + + return new Resvg(svg).render().asPng(); +} + +export async function composeNewsArticleOGImage(params: { + headline: string; + subline?: string; + requestUrl: string; +}) { + const { headline, subline, requestUrl } = params; + + // Design the image and generate an SVG with "satori" + const svg = await satori( +
    + {headline} + {subline ? ( + {subline} + ) : null} +
    , + await getBaseOptions(requestUrl), + ); + + return new Resvg(svg).render().asPng(); +} diff --git a/web/app/modules/meta.ts b/web/app/modules/meta.ts index d1821a2a..7aa34ca6 100644 --- a/web/app/modules/meta.ts +++ b/web/app/modules/meta.ts @@ -42,6 +42,8 @@ export const mergeMeta = ( ); if (index !== -1) { mergedMeta.splice(index, 1, override); + } else { + mergedMeta.push(override); } } diff --git a/web/app/modules/svg.tsx b/web/app/modules/svg.tsx index 32ebdba2..5a02fd6c 100644 --- a/web/app/modules/svg.tsx +++ b/web/app/modules/svg.tsx @@ -1,13 +1,17 @@ +import { CSSProperties } from "react"; + type Props = { className?: string; + style?: CSSProperties; }; -export function SineLogo({ className }: Props) { +export function SineLogo({ className, style }: Props) { return ( { + const { origin } = new URL(request.url); + const { authorId } = params; + + const parsedId = authorSlugSchema.safeParse(authorId); + if (parsedId.error) { + throw new Response(null, { + status: 400, + statusText: "Valid author ID is required", + }); + } + + const png = await composeAuthorOGImage({ + name: encodeURIComponent(parsedId.data), + requestUrl: origin, + }); + + // Respond with the PNG buffer + return new Response(png, { + status: 200, + headers: { + // Tell the browser the response is an image + "Content-Type": "image/png", + // Tip: You might want to heavily cache the response in production + "cache-control": + ENV.NODE_ENV === "production" + ? `public, immutable, no-transform, max-age=${getSeconds(addDays(new Date(), 365))}` + : "no-cache", + }, + }); +}; diff --git a/web/app/routes/_page.author.$authorId.tsx b/web/app/routes/_page.author.$authorId.tsx index f7c2977f..b15c7650 100644 --- a/web/app/routes/_page.author.$authorId.tsx +++ b/web/app/routes/_page.author.$authorId.tsx @@ -28,6 +28,7 @@ const anchors = ["Synopsis", "Packages", "Team"] as const; export const meta = mergeMeta( (params) => { const data = params.data as AuthorRes; + const url = BASE_URL + `/author/${encodeURIComponent(data.authorId)}`; return [ { title: `${data.authorId} | CRAN/E` }, @@ -35,19 +36,22 @@ export const meta = mergeMeta( name: "description", content: `All R packages created by ${data.authorId} for CRAN`, }, - ]; - }, - (params) => { - const data = params.data as AuthorRes; - const url = BASE_URL + `/author/${data.authorId}`; - - return [ + { + property: "og:image", + content: `${url}/og`, + }, { property: "og:title", content: `${data.authorId} | CRAN/E` }, { property: "og:description", content: `All R packages created by ${data.authorId} for CRAN`, }, { property: "og:url", content: url }, + ]; + }, + (params) => { + const data = params.data as AuthorRes; + + return [ { "script:ld+json": composeBreadcrumbsJsonLd([ { @@ -91,7 +95,7 @@ export const loader: LoaderFunction = async ({ params }) => { if (!authorId) { throw new Response(null, { status: 400, - statusText: "Author ID is required", + statusText: "Valid author ID is required", }); } diff --git a/web/app/routes/_page.package.$packageId.og.tsx b/web/app/routes/_page.package.$packageId.og.tsx new file mode 100644 index 00000000..35a06088 --- /dev/null +++ b/web/app/routes/_page.package.$packageId.og.tsx @@ -0,0 +1,37 @@ +import { LoaderFunctionArgs } from "@remix-run/node"; +import { composePackageOGImage } from "../modules/meta-og-image.server"; +import { ENV } from "../data/env"; +import { addDays, getSeconds } from "date-fns"; +import { packageSlugSchema } from "../data/package.shape"; + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + const { origin } = new URL(request.url); + const { packageId } = params; + + const parsedId = packageSlugSchema.safeParse(packageId); + if (parsedId.error) { + throw new Response(null, { + status: 400, + statusText: "Valid package ID is required", + }); + } + + const png = await composePackageOGImage({ + name: encodeURIComponent(parsedId.data), + requestUrl: origin, + }); + + // Respond with the PNG buffer + return new Response(png, { + status: 200, + headers: { + // Tell the browser the response is an image + "Content-Type": "image/png", + // Tip: You might want to heavily cache the response in production + "cache-control": + ENV.NODE_ENV === "production" + ? `public, immutable, no-transform, max-age=${getSeconds(addDays(new Date(), 365))}` + : "no-cache", + }, + }); +}; diff --git a/web/app/routes/_page.package.$packageId.tsx b/web/app/routes/_page.package.$packageId.tsx index d5582185..e8187464 100644 --- a/web/app/routes/_page.package.$packageId.tsx +++ b/web/app/routes/_page.package.$packageId.tsx @@ -54,20 +54,21 @@ const sections = [ export const meta = mergeMeta( ({ data }) => { const { item } = data as { item: Pkg }; + const url = BASE_URL + `/${encodeURIComponent(item.name)}`; return [ { title: `${item.name} | CRAN/E` }, { name: "description", content: item.title }, + { property: "og:title", content: `${item.name} | CRAN/E` }, + { property: "og:description", content: item.title }, + { property: "og:url", content: url }, + { property: "og:image", content: `${url}/og` }, ]; }, ({ data }) => { const { item } = data as { item: Pkg }; - const url = BASE_URL + `/${item.name}`; return [ - { property: "og:title", content: `${item.name} | CRAN/E` }, - { property: "og:description", content: item.title }, - { property: "og:url", content: url }, { "script:ld+json": composeBreadcrumbsJsonLd([ { diff --git a/web/app/routes/_page.press.news._article.tsx b/web/app/routes/_page.press.news._article.tsx index 019d66dd..a6587190 100644 --- a/web/app/routes/_page.press.news._article.tsx +++ b/web/app/routes/_page.press.news._article.tsx @@ -5,7 +5,7 @@ import { Tag } from "../modules/tag"; import { findArticleMatch } from "../modules/article"; import { AnchorLink, Anchors } from "../modules/anchors"; -export default function PrivacyPage() { +export default function NewsArticlePage() { const matches = useMatches(); const article = findArticleMatch(matches); diff --git a/web/app/routes/_page.press.news._index.tsx b/web/app/routes/_page.press.news._index.tsx index a421b53c..cc7975f9 100644 --- a/web/app/routes/_page.press.news._index.tsx +++ b/web/app/routes/_page.press.news._index.tsx @@ -4,6 +4,7 @@ import { Header } from "../modules/header"; import { mergeMeta } from "../modules/meta"; import { Link } from "@remix-run/react"; import { ArticlePreviewInfoCard } from "../modules/article"; +import { BASE_URL } from "../modules/app"; export const handle = { hasFooter: true, @@ -13,6 +14,13 @@ export const meta = mergeMeta(() => { return [ { title: "Newsroom | CRAN/E" }, { name: "description", content: "Latest news and updates of CRAN/E" }, + { property: "og:title", content: "Newsroom | CRAN/E" }, + { property: "og:url", content: `${BASE_URL}/press/news` }, + { + property: "og:description", + content: "Latest news and updates of CRAN/E", + }, + { property: "og:image", content: `${BASE_URL}/press/news/og` }, ]; }); diff --git a/web/app/routes/_page.press.news.og._index.tsx b/web/app/routes/_page.press.news.og._index.tsx new file mode 100644 index 00000000..419116b4 --- /dev/null +++ b/web/app/routes/_page.press.news.og._index.tsx @@ -0,0 +1,27 @@ +import { LoaderFunctionArgs } from "@remix-run/node"; +import { composeNewsArticleOGImage } from "../modules/meta-og-image.server"; +import { ENV } from "../data/env"; +import { addDays, getSeconds } from "date-fns"; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const { origin } = new URL(request.url); + + const png = await composeNewsArticleOGImage({ + headline: "CRAN/E Newsroom", + requestUrl: origin, + }); + + // Respond with the PNG buffer + return new Response(png, { + status: 200, + headers: { + // Tell the browser the response is an image + "Content-Type": "image/png", + // Tip: You might want to heavily cache the response in production + "cache-control": + ENV.NODE_ENV === "production" + ? `public, immutable, no-transform, max-age=${getSeconds(addDays(new Date(), 365))}` + : "no-cache", + }, + }); +}; diff --git a/web/package-lock.json b/web/package-lock.json index 7200aaa6..2e08b298 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -15,7 +15,8 @@ "@remix-run/node": "^2.13.1", "@remix-run/react": "^2.13.1", "@remix-run/serve": "^2.13.1", - "@remixicon/react": "^4.3.0", + "@remixicon/react": "^4.5.0", + "@resvg/resvg-js": "^2.6.2", "@splinetool/react-spline": "^4.0.0", "@uidotdev/usehooks": "^2.4.1", "clsx": "^2.1.1", @@ -28,6 +29,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "remix-utils": "^7.7.0", + "satori": "^0.11.2", "schema-dts": "^1.1.2", "sonner": "^1.5.0", "tailwindcss-animated": "^1.1.2", @@ -38,8 +40,8 @@ "@remix-run/dev": "^2.13.1", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.1", - "@typescript-eslint/eslint-plugin": "^8.11.0", - "@typescript-eslint/parser": "^8.11.0", + "@typescript-eslint/eslint-plugin": "^8.12.2", + "@typescript-eslint/parser": "^8.12.2", "autoprefixer": "^10.4.19", "dotenv-cli": "^7.4.2", "eslint": "^8.38.0", @@ -48,7 +50,7 @@ "eslint-plugin-jsx-a11y": "^6.10.1", "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-hooks": "^5.0.0", - "msw": "^2.5.1", + "msw": "^2.6.0", "npm-check": "^6.0.1", "postcss": "^8.4.38", "prettier": "^3.3.3", @@ -2475,14 +2477,229 @@ } }, "node_modules/@remixicon/react": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@remixicon/react/-/react-4.4.0.tgz", - "integrity": "sha512-f1N61WiAZZZckaC8/dzQTIQBfpPbDIpAlUR8x2Q/g9AQT0/sMxeqqGrxh4VrTrimCD6pfret0++2CRI+pahldA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@remixicon/react/-/react-4.5.0.tgz", + "integrity": "sha512-Xr20SxMpRNlgXZnoF5BCMyZuQEhXY3yJCyms8kxB/vJCCiV1nWdiO48XqRG5LBd1192iSHC4m658AIWi6rmBFg==", "license": "Apache-2.0", "peerDependencies": { "react": ">=18.2.0" } }, + "node_modules/@resvg/resvg-js": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", + "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==", + "license": "MPL-2.0", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@resvg/resvg-js-android-arm-eabi": "2.6.2", + "@resvg/resvg-js-android-arm64": "2.6.2", + "@resvg/resvg-js-darwin-arm64": "2.6.2", + "@resvg/resvg-js-darwin-x64": "2.6.2", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", + "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", + "@resvg/resvg-js-linux-arm64-musl": "2.6.2", + "@resvg/resvg-js-linux-x64-gnu": "2.6.2", + "@resvg/resvg-js-linux-x64-musl": "2.6.2", + "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", + "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", + "@resvg/resvg-js-win32-x64-msvc": "2.6.2" + } + }, + "node_modules/@resvg/resvg-js-android-arm-eabi": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz", + "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-android-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz", + "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz", + "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-x64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz", + "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz", + "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz", + "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-musl": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz", + "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-gnu": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz", + "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-musl": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz", + "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz", + "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz", + "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==", + "cpu": [ + "ia32" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-x64-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz", + "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@rollup/plugin-commonjs": { "version": "26.0.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.3.tgz", @@ -2822,6 +3039,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@shuding/opentype.js": { + "version": "1.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", + "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==", + "license": "MIT", + "dependencies": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -3053,17 +3286,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", - "integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", + "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", "devOptional": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/type-utils": "8.11.0", - "@typescript-eslint/utils": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/type-utils": "8.12.2", + "@typescript-eslint/utils": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3087,16 +3320,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz", - "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", + "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", "devOptional": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4" }, "engines": { @@ -3116,14 +3349,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", - "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", + "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0" + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3134,14 +3367,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz", - "integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", + "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/utils": "8.11.0", + "@typescript-eslint/typescript-estree": "8.12.2", + "@typescript-eslint/utils": "8.12.2", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -3159,9 +3392,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", - "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", + "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", "devOptional": true, "license": "MIT", "engines": { @@ -3173,14 +3406,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", - "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", + "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", "devOptional": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3202,16 +3435,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz", - "integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", + "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", "devOptional": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0" + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3225,13 +3458,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", - "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", + "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/types": "8.12.2", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -4492,6 +4725,15 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001672", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001672.tgz", @@ -5024,6 +5266,47 @@ "node": ">=8" } }, + "node_modules/css-background-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz", + "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==", + "license": "MIT" + }, + "node_modules/css-box-shadow": { + "version": "1.0.0-3", + "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", + "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==", + "license": "MIT" + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-gradient-parser": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.16.tgz", + "integrity": "sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -6996,6 +7279,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "license": "MIT" + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -7887,6 +8176,18 @@ "dev": true, "license": "MIT" }, + "node_modules/hex-rgb": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz", + "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/highlight-es": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/highlight-es/-/highlight-es-1.0.3.tgz", @@ -9176,6 +9477,25 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10722,9 +11042,9 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.5.1.tgz", - "integrity": "sha512-V0BmHvFkbWGXqbyrc+XiuQ8DU3qzcb6lb8gB9Vzltp3cgHLHLCDF/KmmFo0xw58StNaRMTebw3/xpWVvU9xq9g==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.6.0.tgz", + "integrity": "sha512-n3tx2w0MZ3H4pxY0ozrQ4sNPzK/dGtlr2cIIyuEsgq2Bhy4wvcW6ZH2w/gXM9+MEUY6HC1fWhqtcXDxVZr5Jxw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -10734,6 +11054,7 @@ "@bundled-es-modules/tough-cookie": "^0.1.6", "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.36.5", + "@open-draft/deferred-promise": "^2.2.0", "@open-draft/until": "^2.1.0", "@types/cookie": "^0.6.0", "@types/statuses": "^2.0.4", @@ -11431,6 +11752,16 @@ "node": ">=6" } }, + "node_modules/parse-css-color": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz", + "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.1.4", + "hex-rgb": "^4.1.0" + } + }, "node_modules/parse-entities": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", @@ -13228,6 +13559,34 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/satori": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.11.2.tgz", + "integrity": "sha512-uEPLbx89BfwzJroECvnTg8IQ+XxqkMl0apvB41mm8fmc6brzHA8bu9Etu43UoUF4ECnACPiDDFz6PfYDG0S46Q==", + "license": "MPL-2.0", + "dependencies": { + "@shuding/opentype.js": "1.4.0-beta.0", + "css-background-parser": "^0.1.0", + "css-box-shadow": "1.0.0-3", + "css-gradient-parser": "^0.0.16", + "css-to-react-native": "^3.0.0", + "emoji-regex": "^10.2.1", + "escape-html": "^1.0.3", + "linebreak": "^1.1.0", + "parse-css-color": "^0.2.1", + "postcss-value-parser": "^4.2.0", + "yoga-wasm-web": "^0.3.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/satori/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -13693,6 +14052,12 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", + "license": "MIT" + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -14222,6 +14587,12 @@ "integrity": "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg==", "license": "MIT" }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -14575,6 +14946,16 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -15903,6 +16284,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", + "license": "MIT" + }, "node_modules/zod": { "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", diff --git a/web/package.json b/web/package.json index 0b7e656d..eada467c 100644 --- a/web/package.json +++ b/web/package.json @@ -22,7 +22,8 @@ "@remix-run/node": "^2.13.1", "@remix-run/react": "^2.13.1", "@remix-run/serve": "^2.13.1", - "@remixicon/react": "^4.3.0", + "@remixicon/react": "^4.5.0", + "@resvg/resvg-js": "^2.6.2", "@splinetool/react-spline": "^4.0.0", "@uidotdev/usehooks": "^2.4.1", "clsx": "^2.1.1", @@ -35,6 +36,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "remix-utils": "^7.7.0", + "satori": "^0.11.2", "schema-dts": "^1.1.2", "sonner": "^1.5.0", "tailwindcss-animated": "^1.1.2", @@ -45,8 +47,8 @@ "@remix-run/dev": "^2.13.1", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.1", - "@typescript-eslint/eslint-plugin": "^8.11.0", - "@typescript-eslint/parser": "^8.11.0", + "@typescript-eslint/eslint-plugin": "^8.12.2", + "@typescript-eslint/parser": "^8.12.2", "autoprefixer": "^10.4.19", "dotenv-cli": "^7.4.2", "eslint": "^8.38.0", @@ -55,7 +57,7 @@ "eslint-plugin-jsx-a11y": "^6.10.1", "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-hooks": "^5.0.0", - "msw": "^2.5.1", + "msw": "^2.6.0", "npm-check": "^6.0.1", "postcss": "^8.4.38", "prettier": "^3.3.3", diff --git a/web/public/entry.worker.js b/web/public/entry.worker.js index 61088898..f4122112 100644 --- a/web/public/entry.worker.js +++ b/web/public/entry.worker.js @@ -121,6 +121,20 @@ self.addEventListener("activate", (event) => { const entryWorker = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null }, Symbol.toStringTag, { value: "Module" })); +var __getOwnPropNames$h = Object.getOwnPropertyNames; +var __commonJS$h = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames$h(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var require_worker_runtime$h = __commonJS$h({ + "@remix-pwa/worker-runtime"(exports, module) { + module.exports = {}; + } +}); +var worker_runtime_default$h = require_worker_runtime$h(); +const route0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + default: worker_runtime_default$h +}, Symbol.toStringTag, { value: "Module" })); var __getOwnPropNames$g = Object.getOwnPropertyNames; var __commonJS$g = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames$g(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; @@ -131,7 +145,7 @@ var require_worker_runtime$g = __commonJS$g({ } }); var worker_runtime_default$g = require_worker_runtime$g(); -const route0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$g }, Symbol.toStringTag, { value: "Module" })); @@ -145,7 +159,7 @@ var require_worker_runtime$f = __commonJS$f({ } }); var worker_runtime_default$f = require_worker_runtime$f(); -const route1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$f }, Symbol.toStringTag, { value: "Module" })); @@ -159,7 +173,7 @@ var require_worker_runtime$e = __commonJS$e({ } }); var worker_runtime_default$e = require_worker_runtime$e(); -const route2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$e }, Symbol.toStringTag, { value: "Module" })); @@ -173,7 +187,7 @@ var require_worker_runtime$d = __commonJS$d({ } }); var worker_runtime_default$d = require_worker_runtime$d(); -const route3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$d }, Symbol.toStringTag, { value: "Module" })); @@ -187,7 +201,7 @@ var require_worker_runtime$c = __commonJS$c({ } }); var worker_runtime_default$c = require_worker_runtime$c(); -const route4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$c }, Symbol.toStringTag, { value: "Module" })); @@ -201,7 +215,7 @@ var require_worker_runtime$b = __commonJS$b({ } }); var worker_runtime_default$b = require_worker_runtime$b(); -const route5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$b }, Symbol.toStringTag, { value: "Module" })); @@ -215,7 +229,7 @@ var require_worker_runtime$a = __commonJS$a({ } }); var worker_runtime_default$a = require_worker_runtime$a(); -const route6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$a }, Symbol.toStringTag, { value: "Module" })); @@ -229,7 +243,7 @@ var require_worker_runtime$9 = __commonJS$9({ } }); var worker_runtime_default$9 = require_worker_runtime$9(); -const route7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$9 }, Symbol.toStringTag, { value: "Module" })); @@ -243,7 +257,7 @@ var require_worker_runtime$8 = __commonJS$8({ } }); var worker_runtime_default$8 = require_worker_runtime$8(); -const route8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$8 }, Symbol.toStringTag, { value: "Module" })); @@ -257,7 +271,7 @@ var require_worker_runtime$7 = __commonJS$7({ } }); var worker_runtime_default$7 = require_worker_runtime$7(); -const route9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route10 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$7 }, Symbol.toStringTag, { value: "Module" })); @@ -271,7 +285,7 @@ var require_worker_runtime$6 = __commonJS$6({ } }); var worker_runtime_default$6 = require_worker_runtime$6(); -const route10 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route11 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$6 }, Symbol.toStringTag, { value: "Module" })); @@ -285,7 +299,7 @@ var require_worker_runtime$5 = __commonJS$5({ } }); var worker_runtime_default$5 = require_worker_runtime$5(); -const route11 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route12 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$5 }, Symbol.toStringTag, { value: "Module" })); @@ -299,7 +313,7 @@ var require_worker_runtime$4 = __commonJS$4({ } }); var worker_runtime_default$4 = require_worker_runtime$4(); -const route12 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route13 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$4 }, Symbol.toStringTag, { value: "Module" })); @@ -313,7 +327,7 @@ var require_worker_runtime$3 = __commonJS$3({ } }); var worker_runtime_default$3 = require_worker_runtime$3(); -const route13 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route14 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$3 }, Symbol.toStringTag, { value: "Module" })); @@ -327,7 +341,7 @@ var require_worker_runtime$2 = __commonJS$2({ } }); var worker_runtime_default$2 = require_worker_runtime$2(); -const route14 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route15 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$2 }, Symbol.toStringTag, { value: "Module" })); @@ -341,7 +355,7 @@ var require_worker_runtime$1 = __commonJS$1({ } }); var worker_runtime_default$1 = require_worker_runtime$1(); -const route15 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route16 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default$1 }, Symbol.toStringTag, { value: "Module" })); @@ -355,7 +369,7 @@ var require_worker_runtime = __commonJS({ } }); var worker_runtime_default = require_worker_runtime(); -const route16 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ +const route17 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: worker_runtime_default }, Symbol.toStringTag, { value: "Module" })); @@ -364,11 +378,7 @@ const assets = [ "/entry.worker.js", "/manifest.webmanifest", "/robots.txt", - "/images/logo.jpeg", - "/news/crane-v2-0.webp", - "/news/crane-v2-1.webp", - "/news/crane-v2-2.webp", - "/news/crane-v2-3.webp", + "/fonts/Inter-Regular.ttf", "/icons/apple-icon-180.png", "/icons/apple-splash-1125-2436.jpg", "/icons/apple-splash-1136-640.jpg", @@ -406,10 +416,15 @@ const assets = [ "/icons/manifest-icon-192.maskable.png", "/icons/manifest-icon-512.maskable.png", "/icons/safari-pinned-tab.svg", - "/images/og/cover-01.jpg", - "/images/og/cover-02.jpg", + "/images/logo.jpeg", + "/news/crane-v2-0.webp", + "/news/crane-v2-1.webp", + "/news/crane-v2-2.webp", + "/news/crane-v2-3.webp", "/images/we/lukas.webp", "/images/we/tom.webp", + "/images/og/cover-01.jpg", + "/images/og/cover-02.jpg", "/images/screenshots/screenshot-portrait-0.jpg", "/images/screenshots/screenshot-portrait-1.jpg", "/images/screenshots/screenshot-portrait-2.jpg", @@ -455,6 +470,18 @@ const routes = { hasWorkerAction: false, module: route2 }, + "routes/_page.author.$authorId.og": { + id: "routes/_page.author.$authorId.og", + parentId: "routes/_page.author.$authorId", + path: "og", + index: void 0, + caseSensitive: void 0, + hasLoader: true, + hasAction: false, + hasWorkerLoader: false, + hasWorkerAction: false, + module: route3 + }, "routes/_page.press.news._article": { id: "routes/_page.press.news._article", parentId: "routes/_page", @@ -465,7 +492,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route3 + module: route4 }, "routes/_page.package.$packageId": { id: "routes/_page.package.$packageId", @@ -477,7 +504,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route4 + module: route5 }, "routes/_page.press.news._index": { id: "routes/_page.press.news._index", @@ -489,7 +516,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route5 + module: route6 }, "routes/_page.author.$authorId": { id: "routes/_page.author.$authorId", @@ -501,7 +528,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route6 + module: route7 }, "routes/_page.statistic._index": { id: "routes/_page.statistic._index", @@ -513,7 +540,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route7 + module: route8 }, "routes/_page.package._index": { id: "routes/_page.package._index", @@ -525,7 +552,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route8 + module: route9 }, "routes/_page.privacy._index": { id: "routes/_page.privacy._index", @@ -537,7 +564,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route9 + module: route10 }, "routes/sitemap[.xml]._index": { id: "routes/sitemap[.xml]._index", @@ -549,7 +576,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route10 + module: route11 }, "routes/_page.author._index": { id: "routes/_page.author._index", @@ -561,7 +588,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route11 + module: route12 }, "routes/$slug[.xml]._index": { id: "routes/$slug[.xml]._index", @@ -573,7 +600,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route12 + module: route13 }, "routes/_page.about._index": { id: "routes/_page.about._index", @@ -585,7 +612,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route13 + module: route14 }, "routes/api.search._index": { id: "routes/api.search._index", @@ -597,7 +624,7 @@ const routes = { hasAction: true, hasWorkerLoader: false, hasWorkerAction: false, - module: route14 + module: route15 }, "routes/_index": { id: "routes/_index", @@ -609,7 +636,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route15 + module: route16 }, "routes/_page": { id: "routes/_page", @@ -621,7 +648,7 @@ const routes = { hasAction: false, hasWorkerLoader: false, hasWorkerAction: false, - module: route16 + module: route17 } }; const entry = { module: entryWorker }; diff --git a/web/public/fonts/Inter-Regular.ttf b/web/public/fonts/Inter-Regular.ttf new file mode 100644 index 00000000..c544be47 Binary files /dev/null and b/web/public/fonts/Inter-Regular.ttf differ