diff --git a/biome.json b/biome.json index f913aa2..f8f8494 100644 --- a/biome.json +++ b/biome.json @@ -34,7 +34,6 @@ { "includes": [ "data/src/branches/**/Logo.tsx", - "src/components/GameCard/GameCard.tsx", "src/components/NavBar/**/*.tsx", "src/components/LeagueTable/LeagueTable.tsx", "src/components/FixtureCard/FixtureCard.tsx" diff --git a/package.json b/package.json index 045c51a..c6e18f0 100644 --- a/package.json +++ b/package.json @@ -45,11 +45,11 @@ "@vercel/speed-insights": "2.0.0", "clsx": "2.1.1", "next": "16.2.4", + "opentype.js": "1.3.4", "react": "19.2.5", "react-dom": "19.2.5", "react-error-boundary": "6.1.1", - "react-ios-pwa-prompt": "2.0.6", - "react-textfit": "1.1.1" + "react-ios-pwa-prompt": "2.0.6" }, "devDependencies": { "@axe-core/playwright": "4.11.2", @@ -59,9 +59,9 @@ "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "16.3.2", "@types/node": "25.6.0", + "@types/opentype.js": "^1", "@types/react": "19.2.14", "@types/react-dom": "19.2.3", - "@types/react-textfit": "1.1.4", "@vitejs/plugin-react": "5.1.4", "@vitest/coverage-v8": "4.0.18", "happy-dom": "20.9.0", diff --git a/src/app/[domain]/game-card/_backgrounds.tsx b/src/app/[domain]/game-card/_backgrounds.tsx new file mode 100644 index 0000000..e50f5f6 --- /dev/null +++ b/src/app/[domain]/game-card/_backgrounds.tsx @@ -0,0 +1,91 @@ +export function HomeBg() { + return ( + // biome-ignore lint/a11y/noSvgWithoutTitle: rendered to PNG via Satori + + + + + + + + + + + + ); +} + +export function AwayBg() { + return ( + // biome-ignore lint/a11y/noSvgWithoutTitle: rendered to PNG via Satori + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/[domain]/game-card/loading.tsx b/src/app/[domain]/game-card/loading.tsx deleted file mode 100644 index 00bb5a1..0000000 --- a/src/app/[domain]/game-card/loading.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default async function GameCardPageLoading() { - return

Loading

; -} diff --git a/src/app/[domain]/game-card/page.tsx b/src/app/[domain]/game-card/page.tsx deleted file mode 100644 index 4bbe8f2..0000000 --- a/src/app/[domain]/game-card/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { Metadata } from 'next'; - -import { GameCard } from '@/components'; -import { branchData } from '@/data'; -import { getNextFixture } from '@/lib/data/fixtures'; - -export const metadata: Metadata = { - robots: { - index: false, - follow: false, - }, -}; - -export default async function GameCardPage(props: { - params: Promise<{ domain: string }>; -}) { - const params = await props.params; - const branch = branchData[params.domain]; - - const [nextFixture] = await getNextFixture(); - - return ; -} diff --git a/src/app/[domain]/game-card/route.tsx b/src/app/[domain]/game-card/route.tsx new file mode 100644 index 0000000..5de5f5e --- /dev/null +++ b/src/app/[domain]/game-card/route.tsx @@ -0,0 +1,354 @@ +import { ImageResponse } from 'next/og'; +import teams from '@/components/TeamLogo/teams'; +import { branchData, branchLogo, branchLogoSrc } from '@/data'; +import { getNextFixture } from '@/lib/data/fixtures'; +import { fitText } from '@/lib/og/fitText'; +import { getImageResponseFonts, getParsedFonts } from '@/lib/og/fonts'; +import { ARSENAL_TEAM_ID } from '@/lib/sportmonks/sportmonks'; +import { dateFromEpoch, epochToTime } from '@/lib/utils'; + +import { AwayBg, HomeBg } from './_backgrounds'; + +const SIZE = 1080; +// Center X for logo, billing, and time: right:33% + translateX(50%) = 67% from left +const CENTER_X = Math.round(SIZE * 0.6667); + +export async function GET( + req: Request, + { params }: { params: Promise<{ domain: string }> }, +) { + const { domain } = await params; + + const branch = branchData[domain]; + const Logo = branchLogo[domain]; + const rasterSrc = branchLogoSrc[domain]; + + const fonts = getImageResponseFonts(); + const { boldFont, regFont } = getParsedFonts(); + + const [fixture] = await getNextFixture(); + const { participants, starting_at_timestamp } = fixture; + + const url = new URL(req.url); + const simulateAway = url.searchParams.get('simulate') === 'away'; + + let localTeam = participants.find((p) => p.meta.location === 'home'); + let visitorTeam = participants.find((p) => p.meta.location === 'away'); + let isHomeGame = localTeam?.id === ARSENAL_TEAM_ID; + + if (simulateAway && isHomeGame) { + [localTeam, visitorTeam] = [visitorTeam, localTeam]; + isHomeGame = false; + } + + const fixtureDate = dateFromEpoch(starting_at_timestamp, branch.timezone); + const fixtureTime = new Date( + epochToTime(starting_at_timestamp), + ).toLocaleTimeString('en-US', { + timeZone: branch.timezone, + timeStyle: 'short', + }); + + // Billing text sizing: max width 510px (leaving 30px breathing room from 540px container) + const localLabel = localTeam + ? localTeam.name + (localTeam.id !== ARSENAL_TEAM_ID ? ' vs' : '') + : ''; + const visitorLabel = visitorTeam + ? (visitorTeam.id !== ARSENAL_TEAM_ID ? 'vs ' : '') + visitorTeam.name + : ''; + + // Arsenal's row (no "vs") scales up to 200px. The "vs + opponent" row is then + // sized to match Arsenal's actual rendered pixel width so both lines are equal. + let localSize: number; + let visitorSize: number; + if (isHomeGame && localTeam && visitorTeam) { + localSize = fitText(boldFont, localTeam.name, 510, 200, 32); + const arsenalWidth = boldFont.getAdvanceWidth(localTeam.name, localSize); + const visitorRowAt100 = + regFont.getAdvanceWidth('vs', 75) + + 15 + + boldFont.getAdvanceWidth(visitorTeam.name, 100); + visitorSize = Math.max( + 32, + Math.ceil((100 * arsenalWidth) / visitorRowAt100), + ); + } else if (!isHomeGame && localTeam && visitorTeam) { + visitorSize = fitText(boldFont, visitorTeam.name, 510, 200, 32); + const arsenalWidth = boldFont.getAdvanceWidth( + visitorTeam.name, + visitorSize, + ); + const localRowAt100 = + boldFont.getAdvanceWidth(localTeam.name, 100) + + 15 + + regFont.getAdvanceWidth('vs', 75); + localSize = Math.max(32, Math.ceil((100 * arsenalWidth) / localRowAt100)); + } else { + localSize = fitText(boldFont, localLabel, 510, 200, 32); + visitorSize = fitText(boldFont, visitorLabel, 510, 200, 32); + } + + // Time text sizing: max width 420px + const dateSize = fitText(regFont, fixtureDate, 420, 72, 24); + const timeSize = fitText(boldFont, `${fixtureTime} @ Doyle's`, 420, 72, 24); + + const textShadow = '0 0 16px rgba(0,0,0,0.3)'; + const badgeSize = 270; + + return new ImageResponse( +
+ {/* Background pattern */} +
+ {isHomeGame ? : } +
+ + {/* Branch logo */} +
+ {rasterSrc ? ( + // biome-ignore lint/performance/noImgElement: required inside ImageResponse + // biome-ignore lint/a11y/useAltText: rendered to PNG via Satori + + ) : ( + Logo && + )} +
+ + {/* Team badges — left column, vertically centered */} + {localTeam && visitorTeam && ( + <> +
+ {teams.has(localTeam.id) ? ( + // biome-ignore lint/a11y/noSvgWithoutTitle: rendered to PNG via Satori + + + + ) : ( + // biome-ignore lint/performance/noImgElement: required inside ImageResponse — Next/Image is not compatible with next/og + // biome-ignore lint/a11y/useAltText: rendered to PNG via Satori, not served to browsers + + )} + {teams.has(visitorTeam.id) ? ( + // biome-ignore lint/a11y/noSvgWithoutTitle: rendered to PNG via Satori + + + + ) : ( + // biome-ignore lint/performance/noImgElement: required inside ImageResponse — Next/Image is not compatible with next/og + // biome-ignore lint/a11y/useAltText: rendered to PNG via Satori, not served to browsers + + )} +
+ + {/* Team name billing */} +
+
+ + {localTeam.name} + + {localTeam.id !== ARSENAL_TEAM_ID && ( + + {'vs'} + + )} +
+
+ {visitorTeam.id !== ARSENAL_TEAM_ID && ( + + {'vs'} + + )} + + {visitorTeam.name} + +
+
+ + {/* Date and kickoff time */} +
+
+ {fixtureDate} +
+
+ {`${fixtureTime} @ Doyle's`} +
+
+ + )} +
, + { + width: SIZE, + height: SIZE, + fonts, + headers: { + 'Cache-Control': + 'public, max-age=45, s-maxage=45, stale-while-revalidate=120', + }, + }, + ); +} diff --git a/src/components/GameCard/GameCard.module.scss b/src/components/GameCard/GameCard.module.scss deleted file mode 100644 index 2c37d85..0000000 --- a/src/components/GameCard/GameCard.module.scss +++ /dev/null @@ -1,97 +0,0 @@ -$margin: 8vw; - -._ { - position: relative; - z-index: 50; - margin: 0 -1em; - aspect-ratio: 1/1; - // background-color: white; - - h2 { - position: initial; - padding: initial; - margin: 0; - background-color: transparent; - box-shadow: none; - font-size: inherit; - letter-spacing: -0.03em; - - &::after { - display: none; - } - - span { - font-family: ars-reg, sans-serif; - font-size: 0.75em; - font-weight: 400; - } - } - - .Logo, - .GameTime, - .MainBilling { - position: absolute; - z-index: 10; - right: 33%; - transform: translateX(50%); - } - - .MainBilling, - .GameTime { - text-align: center; - text-shadow: 0 0 1rem rgb(0 0 0 / 30%); - } - - .Logo { - top: $margin; - width: 25%; - filter: drop-shadow(0 1.5vmin 3vmin rgb(0 0 0 / 30%)); - } - - .Badges { - position: absolute; - top: 50%; - left: 8%; - display: flex; - flex-direction: column; - gap: 5vw; - transform: translateY(-50%); - - svg, - img { - width: 25vw; - height: auto; - fill: #fff; - } - } - - .MainBilling { - top: 50%; - width: 50%; - transform: translate(50%, -40%); - - h2 { - line-height: 0.9; - } - } - - .GameTime { - bottom: $margin; - width: 40%; - - h2 { - line-height: 1; - } - - > *:first-child { - h2 { - font-family: ars-reg, sans-serif; - font-weight: 400; - } - } - } - - .Background { - display: block; - } -} diff --git a/src/components/GameCard/GameCard.tsx b/src/components/GameCard/GameCard.tsx deleted file mode 100644 index 79ac52a..0000000 --- a/src/components/GameCard/GameCard.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { branchLogo } from '@/data'; -import type { FixtureEntity } from '@/lib/sportmonks'; -import { TeamLogo } from '../TeamLogo/TeamLogo'; -import styles from './GameCard.module.scss'; - -import { GameCardBilling } from './GameCardBilling'; -import { GameCardTime } from './GameCardTime'; - -export function GameCard({ - branch, - participants, - starting_at_timestamp, -}: { branch: { domain: string; timezone: string } } & FixtureEntity) { - const Logo = branchLogo[branch.domain]; - - const localTeam = participants.find((team) => team.meta.location === 'home'); - const visitorTeam = participants.find( - (team) => team.meta.location === 'away', - ); - - const isHomeGame = localTeam?.id === 19; - - return ( -
- {Logo && } - {localTeam && visitorTeam && ( - <> -
- - -
- - - - )} - {isHomeGame ? ( - - - - - - - - - - - - ) : ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - )} -
- ); -} diff --git a/src/components/GameCard/GameCardBilling.tsx b/src/components/GameCard/GameCardBilling.tsx deleted file mode 100644 index 0bf37b2..0000000 --- a/src/components/GameCard/GameCardBilling.tsx +++ /dev/null @@ -1,30 +0,0 @@ -'use client'; - -import { Textfit } from 'react-textfit'; -import type { EntityBase } from '@/lib/sportmonks'; -import styles from './GameCard.module.scss'; - -export function GameCardBilling({ - localTeam, - visitorTeam, -}: { - localTeam: EntityBase; - visitorTeam: EntityBase; -}) { - return ( -
- -

- {localTeam.name} - {localTeam.id !== 19 && ' vs'} -

-
- -

- {visitorTeam.id !== 19 && 'vs '} - {visitorTeam.name} -

-
-
- ); -} diff --git a/src/components/GameCard/GameCardTime.tsx b/src/components/GameCard/GameCardTime.tsx deleted file mode 100644 index d69fadc..0000000 --- a/src/components/GameCard/GameCardTime.tsx +++ /dev/null @@ -1,32 +0,0 @@ -'use client'; - -import { Textfit } from 'react-textfit'; -import { dateFromEpoch, epochToTime } from '@/lib/utils'; -import styles from './GameCard.module.scss'; - -export function GameCardTime({ - starting_at_timestamp, - timeZone, -}: { - starting_at_timestamp: number; - timeZone: string; -}) { - const fixtureDate = dateFromEpoch(starting_at_timestamp, timeZone); - const fixtureTime = new Date( - epochToTime(starting_at_timestamp), - ).toLocaleTimeString('en-US', { - timeZone, - timeStyle: 'short', - }); - - return ( -
- -

{fixtureDate}

-
- -

{fixtureTime} @ Doyle's

-
-
- ); -} diff --git a/src/components/GameCard/bg.svg b/src/components/GameCard/bg.svg deleted file mode 100644 index b0d7805..0000000 --- a/src/components/GameCard/bg.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/GameCard/bg_2023.svg b/src/components/GameCard/bg_2023.svg deleted file mode 100644 index 8fb7c38..0000000 --- a/src/components/GameCard/bg_2023.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - diff --git a/src/components/GameCard/bg_2024.svg b/src/components/GameCard/bg_2024.svg deleted file mode 100644 index ff0a32e..0000000 --- a/src/components/GameCard/bg_2024.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/components/GameCard/bg_2025_away.svg b/src/components/GameCard/bg_2025_away.svg deleted file mode 100644 index cb4215b..0000000 --- a/src/components/GameCard/bg_2025_away.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/components/index.ts b/src/components/index.ts index e732624..1a013a7 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -7,7 +7,6 @@ export * from './FixtureCard/FixtureCardBoundary'; export * from './FixtureCard/FixtureCardError'; export * from './FixtureCard/SettledFixtureCard'; export * from './FixtureCard/UnsettledFixtureCard'; -export * from './GameCard/GameCard'; export * from './LeagueLogo/LeagueLogo'; export * from './LeagueTable/LeagueTable'; export * from './LocalDateTime/LocalDateTime'; diff --git a/src/lib/og/fitText.ts b/src/lib/og/fitText.ts new file mode 100644 index 0000000..9c3d484 --- /dev/null +++ b/src/lib/og/fitText.ts @@ -0,0 +1,14 @@ +import type { Font } from 'opentype.js'; + +export function fitText( + font: Font, + text: string, + maxWidth: number, + maxFontSize: number, + minFontSize = 24, +): number { + if (!text) return maxFontSize; + const w = font.getAdvanceWidth(text, maxFontSize); + if (w <= maxWidth) return maxFontSize; + return Math.max(Math.floor((maxFontSize * maxWidth) / w), minFontSize); +} diff --git a/src/lib/og/fonts.ts b/src/lib/og/fonts.ts new file mode 100644 index 0000000..b44ffb8 --- /dev/null +++ b/src/lib/og/fonts.ts @@ -0,0 +1,61 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { type Font, parse } from 'opentype.js'; + +type FontCache = { + boldBuf: ArrayBuffer; + regBuf: ArrayBuffer; + boldFont: Font; + regFont: Font; +}; + +let _cache: FontCache | null = null; + +function toArrayBuffer(buf: Buffer): ArrayBuffer { + return buf.buffer.slice( + buf.byteOffset, + buf.byteOffset + buf.byteLength, + ) as ArrayBuffer; +} + +function getCache(): FontCache { + if (_cache) return _cache; + const boldRaw = fs.readFileSync( + path.join(process.cwd(), 'src/fonts/ars-bold.woff'), + ); + const regRaw = fs.readFileSync( + path.join(process.cwd(), 'src/fonts/ars-reg.woff'), + ); + const boldBuf = toArrayBuffer(boldRaw); + const regBuf = toArrayBuffer(regRaw); + _cache = { + boldBuf, + regBuf, + boldFont: parse(boldBuf), + regFont: parse(regBuf), + }; + return _cache; +} + +export function getImageResponseFonts() { + const { boldBuf, regBuf } = getCache(); + return [ + { + name: 'ars-bold', + data: boldBuf, + weight: 700 as const, + style: 'normal' as const, + }, + { + name: 'ars-reg', + data: regBuf, + weight: 400 as const, + style: 'normal' as const, + }, + ]; +} + +export function getParsedFonts() { + const { boldFont, regFont } = getCache(); + return { boldFont, regFont }; +} diff --git a/yarn.lock b/yarn.lock index 42184e3..bd3c5cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2381,6 +2381,13 @@ __metadata: languageName: node linkType: hard +"@types/opentype.js@npm:^1": + version: 1.3.9 + resolution: "@types/opentype.js@npm:1.3.9" + checksum: 10c0/5cfc2436d5bfc9d9688a90bd517d4789dcb3ff886c94420635002ca342c3c284392bd9f49e93d330769fc2e6187fa4e3fe1344da72f148b71c2b983dd5b68240 + languageName: node + linkType: hard + "@types/react-dom@npm:19.2.3": version: 19.2.3 resolution: "@types/react-dom@npm:19.2.3" @@ -2390,16 +2397,7 @@ __metadata: languageName: node linkType: hard -"@types/react-textfit@npm:1.1.4": - version: 1.1.4 - resolution: "@types/react-textfit@npm:1.1.4" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/7e70cfcc0a4411b28ac700a7a07a3dfbf848e51d3b3e01c1d2ba9a7d6021e3c6e7c522e2bc39b0946061c52c8c8c92e29820367a81c7c1cfdca3e1e26f280cd9 - languageName: node - linkType: hard - -"@types/react@npm:*, @types/react@npm:19.2.14": +"@types/react@npm:19.2.14": version: 19.2.14 resolution: "@types/react@npm:19.2.14" dependencies: @@ -3103,9 +3101,9 @@ __metadata: "@testing-library/jest-dom": "npm:6.9.1" "@testing-library/react": "npm:16.3.2" "@types/node": "npm:25.6.0" + "@types/opentype.js": "npm:^1" "@types/react": "npm:19.2.14" "@types/react-dom": "npm:19.2.3" - "@types/react-textfit": "npm:1.1.4" "@vercel/analytics": "npm:2.0.1" "@vercel/speed-insights": "npm:2.0.0" "@vitejs/plugin-react": "npm:5.1.4" @@ -3115,11 +3113,11 @@ __metadata: knip: "npm:6.5.0" lefthook: "npm:2.1.6" next: "npm:16.2.4" + opentype.js: "npm:1.3.4" react: "npm:19.2.5" react-dom: "npm:19.2.5" react-error-boundary: "npm:6.1.1" react-ios-pwa-prompt: "npm:2.0.6" - react-textfit: "npm:1.1.1" sass: "npm:1.99.0" typescript: "npm:5.9.3" vercel: "npm:51.8.0" @@ -4642,7 +4640,7 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": +"js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed @@ -4864,17 +4862,6 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.4.0": - version: 1.4.0 - resolution: "loose-envify@npm:1.4.0" - dependencies: - js-tokens: "npm:^3.0.0 || ^4.0.0" - bin: - loose-envify: cli.js - checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e - languageName: node - linkType: hard - "lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": version: 11.3.5 resolution: "lru-cache@npm:11.3.5" @@ -5387,13 +5374,6 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.1.1": - version: 4.1.1 - resolution: "object-assign@npm:4.1.1" - checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 - languageName: node - linkType: hard - "obug@npm:^2.1.1": version: 2.1.1 resolution: "obug@npm:2.1.1" @@ -5428,6 +5408,18 @@ __metadata: languageName: node linkType: hard +"opentype.js@npm:1.3.4": + version: 1.3.4 + resolution: "opentype.js@npm:1.3.4" + dependencies: + string.prototype.codepointat: "npm:^0.2.1" + tiny-inflate: "npm:^1.0.3" + bin: + ot: bin/ot + checksum: 10c0/7de1c175c439a648f32f5113c619cdcd7ae09096cb791b5b1b8b24afdaf76ccdf8938297dab61ded48e032e8ebb54bcf444eedef562498f7e241dbebcf3da2f6 + languageName: node + linkType: hard + "os-paths@npm:^4.0.1": version: 4.4.0 resolution: "os-paths@npm:4.4.0" @@ -5857,13 +5849,6 @@ __metadata: languageName: node linkType: hard -"process@npm:^0.11.10": - version: 0.11.10 - resolution: "process@npm:0.11.10" - checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 - languageName: node - linkType: hard - "promisepipe@npm:3.0.0": version: 3.0.0 resolution: "promisepipe@npm:3.0.0" @@ -5871,17 +5856,6 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.7.2": - version: 15.8.1 - resolution: "prop-types@npm:15.8.1" - dependencies: - loose-envify: "npm:^1.4.0" - object-assign: "npm:^4.1.1" - react-is: "npm:^16.13.1" - checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 - languageName: node - linkType: hard - "proxy-agent@npm:6.4.0": version: 6.4.0 resolution: "proxy-agent@npm:6.4.0" @@ -5971,13 +5945,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1": - version: 16.13.1 - resolution: "react-is@npm:16.13.1" - checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 - languageName: node - linkType: hard - "react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" @@ -5992,19 +5959,6 @@ __metadata: languageName: node linkType: hard -"react-textfit@npm:1.1.1": - version: 1.1.1 - resolution: "react-textfit@npm:1.1.1" - dependencies: - process: "npm:^0.11.10" - prop-types: "npm:^15.7.2" - peerDependencies: - react: ^15.0.0 || ^16.0.0 - react-dom: ^15.0.0 || ^16.0.0 - checksum: 10c0/6dc8eb8d27a1c6e6c7bffd5c8c3bbe5394eaa733b7b7bc52334c59709e38577b6877006f56762a783a2bab80b073752771a045fb570b901aebd42b9e58930e2a - languageName: node - linkType: hard - "react@npm:19.2.5": version: 19.2.5 resolution: "react@npm:19.2.5" @@ -6564,6 +6518,13 @@ __metadata: languageName: node linkType: hard +"string.prototype.codepointat@npm:^0.2.1": + version: 0.2.1 + resolution: "string.prototype.codepointat@npm:0.2.1" + checksum: 10c0/83c4d2f83b6f3f8f377e0b36628b74a9efcaf5a725e6fb6354f15f30f0399c8f4b08956df883877b2b0f50dd2e644ed7e8b1f6d45bdee2dc5b3f4248796607fa + languageName: node + linkType: hard + "strip-final-newline@npm:^2.0.0": version: 2.0.0 resolution: "strip-final-newline@npm:2.0.0" @@ -6661,6 +6622,13 @@ __metadata: languageName: node linkType: hard +"tiny-inflate@npm:^1.0.3": + version: 1.0.3 + resolution: "tiny-inflate@npm:1.0.3" + checksum: 10c0/fab687537254f6ec44c9a2e880048fe70da3542aba28f73cda3e74c95cabf342a339372f2a6c032e322324f01accc03ca26c04ba2bad9b3eb8cf3ee99bba7f9b + languageName: node + linkType: hard + "tinybench@npm:^2.9.0": version: 2.9.0 resolution: "tinybench@npm:2.9.0"