feat(seo): site meta composable + Open Graph image generation
Adds `useSiteMeta()` composable that wraps useSeoMeta with site defaults (title template, canonical URL, og/twitter image, og:site_name) and absolute-URL handling via runtimeConfig.public.appUrl. Adds /og/events/[slug].png route that renders per-event OG images via satori + @resvg/resvg-js, cached on disk by slug + updatedAt. Bundles Brygada 1918 + Commit Mono fonts as server assets, ships a fallback default.png, and patches @shuding/opentype.js via patch-package. Converts ~25 pages from useHead to useSiteMeta and adds noindex on private/auth/admin pages.
This commit is contained in:
parent
877ef1a220
commit
31144617d7
36 changed files with 1182 additions and 53 deletions
58
app/composables/useSiteMeta.js
Normal file
58
app/composables/useSiteMeta.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* useSiteMeta — set page-level SEO + social meta with site defaults baked in.
|
||||
*
|
||||
* Builds absolute URLs from runtimeConfig.public.appUrl so og:image and og:url
|
||||
* resolve for crawlers. Defaults og:type=website, twitter:card=summary_large_image,
|
||||
* og:site_name=Ghost Guild. Set noindex:true to emit robots="noindex, nofollow".
|
||||
*
|
||||
* Pass a function (or refs in fields) to keep tags reactive when content loads
|
||||
* asynchronously via useFetch.
|
||||
*/
|
||||
export function useSiteMeta(input) {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const route = useRoute()
|
||||
const appUrl = (runtimeConfig.public.appUrl || '').replace(/\/$/, '')
|
||||
|
||||
const resolve = () => (typeof input === 'function' ? input() : input) || {}
|
||||
|
||||
const buildAbsolute = (path) => {
|
||||
if (!path) return undefined
|
||||
if (/^https?:\/\//i.test(path)) return path
|
||||
return `${appUrl}${path.startsWith('/') ? '' : '/'}${path}`
|
||||
}
|
||||
|
||||
const titleGetter = () => resolve().title || 'Ghost Guild'
|
||||
const descGetter = () => resolve().description || undefined
|
||||
const isBareTitle = () => Boolean(resolve().bareTitle)
|
||||
const imageGetter = () => buildAbsolute(resolve().image || '/og/default.png')
|
||||
const typeGetter = () => resolve().type || 'website'
|
||||
const robotsGetter = () =>
|
||||
resolve().noindex ? 'noindex, nofollow' : undefined
|
||||
const canonicalGetter = () => buildAbsolute(route.path)
|
||||
|
||||
useSeoMeta({
|
||||
title: titleGetter,
|
||||
description: descGetter,
|
||||
ogSiteName: 'Ghost Guild',
|
||||
ogTitle: titleGetter,
|
||||
ogDescription: descGetter,
|
||||
ogType: typeGetter,
|
||||
ogUrl: canonicalGetter,
|
||||
ogImage: imageGetter,
|
||||
ogImageWidth: 1200,
|
||||
ogImageHeight: 630,
|
||||
twitterCard: 'summary_large_image',
|
||||
twitterTitle: titleGetter,
|
||||
twitterDescription: descGetter,
|
||||
twitterImage: imageGetter,
|
||||
robots: robotsGetter,
|
||||
})
|
||||
|
||||
useHead({
|
||||
link: [{ rel: 'canonical', href: canonicalGetter }],
|
||||
})
|
||||
|
||||
if (isBareTitle()) {
|
||||
useHead({ titleTemplate: null })
|
||||
}
|
||||
}
|
||||
|
|
@ -217,6 +217,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useSiteMeta({ title: "Admin", noindex: true });
|
||||
|
||||
const route = useRoute();
|
||||
const isMobileMenuOpen = ref(false);
|
||||
const { logout } = useAuth();
|
||||
|
|
|
|||
|
|
@ -104,7 +104,13 @@
|
|||
</PageShell>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
<script setup>
|
||||
useSiteMeta({
|
||||
title: 'About',
|
||||
description:
|
||||
'A membership community for game developers exploring cooperative models. Three circles, pay what you can. A program of Baby Ghosts, a Canadian non-profit advancing cooperative practice in the game industry since 2023.',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ---- ABOUT HERO ---- */
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ import {
|
|||
} from "~/config/contributions";
|
||||
|
||||
definePageMeta({ layout: false });
|
||||
useSiteMeta({ title: "Accept Invitation", noindex: true });
|
||||
|
||||
const { checkMemberStatus } = useAuth();
|
||||
const { initializeHelcimPay, verifyPayment } = useHelcimPay();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ layout: false });
|
||||
useHead({ title: "Sign Out — Ghost Guild" });
|
||||
useSiteMeta({ title: "Sign Out", noindex: true });
|
||||
|
||||
// The xsrf token comes from a short-lived httpOnly cookie set by
|
||||
// oidc-provider's logoutSource callback (see server/utils/oidc-provider.ts).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ layout: false });
|
||||
useHead({ title: "Signed Out — Ghost Guild" });
|
||||
useSiteMeta({ title: "Signed Out", noindex: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ layout: false });
|
||||
useHead({ title: "Sign-In Error — Ghost Guild" });
|
||||
useSiteMeta({ title: "Sign-In Error", noindex: true });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
definePageMeta({
|
||||
layout: false,
|
||||
});
|
||||
useSiteMeta({ title: "Wiki Sign In", noindex: true });
|
||||
|
||||
const route = useRoute();
|
||||
const uid = route.query.uid as string;
|
||||
|
|
|
|||
|
|
@ -192,14 +192,10 @@ const loadTags = async () => {
|
|||
cooperativeTags.value = (data.tags || []).filter((t) => t.pool === 'cooperative')
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: 'Board - Ghost Guild',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Share what you are seeking and offering with the Ghost Guild community.',
|
||||
},
|
||||
],
|
||||
useSiteMeta({
|
||||
title: 'Bulletin Board',
|
||||
description:
|
||||
'The Ghost Guild bulletin board. Members post offers and requests around shared interests and cooperative topics.',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
|
|||
|
|
@ -273,8 +273,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: "Community Guidelines · Ghost Guild",
|
||||
useSiteMeta({
|
||||
title: "Community Guidelines",
|
||||
description:
|
||||
"What you're agreeing to when you join Ghost Guild — community values, member commitments, and the policies that govern participation.",
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,33 @@ definePageMeta({
|
|||
layout: "default",
|
||||
});
|
||||
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const siteUrl = (runtimeConfig.public.appUrl || "").replace(/\/$/, "");
|
||||
|
||||
useSiteMeta({
|
||||
title: "Ghost Guild",
|
||||
bareTitle: true,
|
||||
description:
|
||||
"Ghost Guild is where game developers explore cooperative models. Membership, events, and resources for people figuring it out together. Pay what you can.",
|
||||
});
|
||||
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: "application/ld+json",
|
||||
innerHTML: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
name: "Ghost Guild",
|
||||
url: siteUrl || "https://ghostguild.org",
|
||||
logo: `${siteUrl || "https://ghostguild.org"}/og/default.png`,
|
||||
description:
|
||||
"A membership community for game developers exploring cooperative models. A program of Baby Ghosts, a Canadian non-profit.",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { data: events } = await useFetch("/api/events", {
|
||||
query: { limit: 4, upcoming: true },
|
||||
default: () => [],
|
||||
|
|
|
|||
|
|
@ -391,6 +391,12 @@ import {
|
|||
getGuidanceLabel,
|
||||
} from "~/config/contributions";
|
||||
|
||||
useSiteMeta({
|
||||
title: "Join",
|
||||
description:
|
||||
"Join Ghost Guild — a membership community for game developers exploring cooperative models. Everyone gets everything. Pay what you can, $0 to $50 per month.",
|
||||
});
|
||||
|
||||
// Auth state
|
||||
const { isAuthenticated, memberData, checkMemberStatus } = useAuth();
|
||||
|
||||
|
|
|
|||
|
|
@ -317,6 +317,8 @@
|
|||
import { CONTRIBUTION_PRESETS, getGuidanceLabel, requiresPayment } from '~/config/contributions';
|
||||
import { STATUS_LABELS } from '~/config/memberStatus';
|
||||
|
||||
useSiteMeta({ title: 'Account', noindex: true });
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useSiteMeta({ title: 'Dashboard', noindex: true });
|
||||
|
||||
const { memberData, checkMemberStatus } = useAuth();
|
||||
const { isActive, statusConfig, isPendingPayment, canPeerSupport } =
|
||||
useMemberStatus();
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
<script setup>
|
||||
definePageMeta({ middleware: 'auth' });
|
||||
|
||||
useSiteMeta({ title: 'Payment Setup', noindex: true });
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
|
|
|||
|
|
@ -306,6 +306,8 @@ import { MEMBER_STATUSES } from "~/composables/useMemberStatus";
|
|||
import { TIMEZONE_OPTIONS } from "~/config/timezones";
|
||||
import { formatActivity } from "~/utils/activityText";
|
||||
|
||||
useSiteMeta({ title: "Profile", noindex: true });
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -276,14 +276,10 @@ onUnmounted(() => {
|
|||
pageBreadcrumbTitle.value = "";
|
||||
});
|
||||
|
||||
// Page head
|
||||
useHead({
|
||||
title: computed(() =>
|
||||
member.value
|
||||
? `${member.value.name} — Ghost Guild`
|
||||
: "Member Profile — Ghost Guild",
|
||||
),
|
||||
});
|
||||
useSiteMeta(() => ({
|
||||
title: member.value ? member.value.name : "Member Profile",
|
||||
noindex: true,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -277,16 +277,7 @@ onBeforeUnmount(() => {
|
|||
clearTimeout(searchTimeout)
|
||||
})
|
||||
|
||||
// ---- useHead ----
|
||||
useHead({
|
||||
title: 'Member Directory - Ghost Guild',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Connect with members of the Ghost Guild community - game developers, founders, and practitioners building solidarity economy studios.',
|
||||
},
|
||||
],
|
||||
})
|
||||
useSiteMeta({ title: 'Member Directory', noindex: true })
|
||||
|
||||
// ---- Init ----
|
||||
onMounted(async () => {
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ if (!policy) {
|
|||
throw createError({ statusCode: 404, statusMessage: 'Policy not found', fatal: true })
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `${policy.title} · Ghost Guild`,
|
||||
useSiteMeta({
|
||||
title: policy.title,
|
||||
description: policy.description,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -231,8 +231,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Privacy Policy · Ghost Guild',
|
||||
useSiteMeta({
|
||||
title: 'Privacy Policy',
|
||||
description:
|
||||
'How Ghost Guild handles your data: what we collect, why we collect it, and who has access. No Google Analytics, no advertising pixels, no third-party tracking.',
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Refund Policy · Ghost Guild',
|
||||
useSiteMeta({
|
||||
title: 'Refund Policy',
|
||||
description:
|
||||
'How Ghost Guild handles refund requests for membership dues and event tickets. Pay-what-you-can, case-by-case, run as a non-profit program of Baby Ghosts.',
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -250,8 +250,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Terms of Service · Ghost Guild',
|
||||
useSiteMeta({
|
||||
title: 'Terms of Service',
|
||||
description:
|
||||
'Terms of service for ghostguild.org and wiki.ghostguild.org, operated by Baby Ghosts. Covers accounts, membership, acceptable use, and what we expect from each other.',
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -117,9 +117,11 @@ const handlePurchaseSuccess = () => {
|
|||
refreshNuxtData()
|
||||
}
|
||||
|
||||
useHead(() => ({
|
||||
title: series.value ? `${series.value.title} - Event Series - Ghost Guild` : 'Event Series - Ghost Guild',
|
||||
meta: [{ name: 'description', content: series.value?.description || 'Multi-event series' }],
|
||||
useSiteMeta(() => ({
|
||||
title: series.value ? `${series.value.title} · Event Series` : 'Event Series',
|
||||
description:
|
||||
series.value?.description ||
|
||||
(series.value?.title ? `${series.value.title} — a Ghost Guild event series.` : undefined),
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -72,15 +72,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: "Event Series - Ghost Guild",
|
||||
meta: [
|
||||
{
|
||||
name: "description",
|
||||
content:
|
||||
"Multi-session events on cooperative topics for game developers.",
|
||||
},
|
||||
],
|
||||
useSiteMeta({
|
||||
title: "Event Series",
|
||||
description:
|
||||
"Multi-session event series on cooperative topics — from foundations courses to practitioner cohorts.",
|
||||
});
|
||||
|
||||
const { data: seriesData, pending } = await useFetch("/api/series", {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
<script setup>
|
||||
definePageMeta({ layout: false })
|
||||
useSiteMeta({ title: 'Verifying', noindex: true })
|
||||
|
||||
const state = ref('verifying')
|
||||
const errorMessage = ref('')
|
||||
|
|
|
|||
709
package-lock.json
generated
709
package-lock.json
generated
|
|
@ -15,6 +15,7 @@
|
|||
"@nuxt/eslint": "^1.9.0",
|
||||
"@nuxt/ui": "^4.0.0",
|
||||
"@nuxtjs/plausible": "^3.0.1",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@slack/web-api": "^7.10.0",
|
||||
"chrono-node": "^2.8.4",
|
||||
"cloudinary": "^2.7.0",
|
||||
|
|
@ -28,6 +29,7 @@
|
|||
"oidc-provider": "^9.6.1",
|
||||
"rate-limiter-flexible": "^9.1.1",
|
||||
"resend": "^6.0.1",
|
||||
"satori": "^0.26.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vue": "^3.5.20",
|
||||
"vue-cal": "^5.0.1-rc.28",
|
||||
|
|
@ -42,6 +44,7 @@
|
|||
"@types/oidc-provider": "^9.5.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^28.1.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
},
|
||||
|
|
@ -4648,6 +4651,221 @@
|
|||
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
|
||||
|
|
@ -5153,6 +5371,22 @@
|
|||
"win32"
|
||||
]
|
||||
},
|
||||
"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/base62": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz",
|
||||
|
|
@ -7599,6 +7833,13 @@
|
|||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz",
|
||||
|
|
@ -8373,6 +8614,25 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
|
||||
"integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"get-intrinsic": "^1.3.0",
|
||||
"set-function-length": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
|
|
@ -8386,6 +8646,23 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
|
|
@ -8395,6 +8672,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-api": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
|
||||
|
|
@ -8837,6 +9123,27 @@
|
|||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"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-declaration-sorter": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz",
|
||||
|
|
@ -8849,6 +9156,15 @@
|
|||
"postcss": "^8.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/css-gradient-parser": {
|
||||
"version": "0.0.17",
|
||||
"resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.17.tgz",
|
||||
"integrity": "sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||
|
|
@ -8865,6 +9181,17 @@
|
|||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"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-tree": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
|
||||
|
|
@ -9195,6 +9522,24 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/define-lazy-prop": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
|
||||
|
|
@ -9527,6 +9872,15 @@
|
|||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex-xs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-2.0.1.tgz",
|
||||
"integrity": "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
|
|
@ -10497,6 +10851,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
|
|
@ -10555,6 +10915,16 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/find-yarn-workspace-root": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
|
||||
|
|
@ -10720,6 +11090,21 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
|
|
@ -11007,6 +11392,19 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
|
|
@ -11046,6 +11444,18 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"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/hey-listen": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
|
||||
|
|
@ -11817,12 +12227,39 @@
|
|||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-stable-stringify": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
|
||||
"integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"isarray": "^2.0.5",
|
||||
"jsonify": "^0.0.1",
|
||||
"object-keys": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/json-stable-stringify-without-jsonify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-stable-stringify/node_modules/isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
|
|
@ -11835,6 +12272,29 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
|
||||
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonify": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
|
||||
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
|
||||
"dev": true,
|
||||
"license": "Public Domain",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
|
||||
|
|
@ -11908,6 +12368,16 @@
|
|||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/klaw-sync": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.11"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||
|
|
@ -12349,6 +12819,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/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
|
|
@ -12794,6 +13283,16 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
||||
|
|
@ -13597,6 +14096,16 @@
|
|||
"integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-keys": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
|
||||
|
|
@ -13966,6 +14475,12 @@
|
|||
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
|
@ -13978,6 +14493,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-imports-exports": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz",
|
||||
|
|
@ -14026,6 +14551,108 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
|
||||
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"chalk": "^4.1.2",
|
||||
"ci-info": "^3.7.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"json-stable-stringify": "^1.0.2",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"minimist": "^1.2.6",
|
||||
"open": "^7.4.2",
|
||||
"semver": "^7.5.3",
|
||||
"slash": "^2.0.0",
|
||||
"tmp": "^0.2.4",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"patch-package": "index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">5"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/ci-info": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/sibiraj-s"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/open": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0",
|
||||
"is-wsl": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
|
|
@ -15528,6 +16155,28 @@
|
|||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/satori": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/satori/-/satori-0.26.0.tgz",
|
||||
"integrity": "sha512-tkMFrfIs3l2mQ2JEcyW0ADTy3zGggFRFzi6Ef8YozQSFsFKEqaSO1Y8F9wJg4//PJGQauMalHGTUEkPrFwhVPA==",
|
||||
"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.17",
|
||||
"css-to-react-native": "^3.0.0",
|
||||
"emoji-regex-xs": "^2.0.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"linebreak": "^1.1.0",
|
||||
"parse-css-color": "^0.2.1",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"yoga-layout": "^3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
||||
|
|
@ -15678,6 +16327,24 @@
|
|||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
|
|
@ -15983,6 +16650,12 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"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/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
|
|
@ -16396,6 +17069,16 @@
|
|||
"integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
|
@ -16680,6 +17363,16 @@
|
|||
"integrity": "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==",
|
||||
"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/unicorn-magic": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
||||
|
|
@ -16780,6 +17473,16 @@
|
|||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
|
@ -18453,6 +19156,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yoga-layout": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
|
||||
"integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/youch": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"dev": " nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"postinstall": "patch-package && nuxt prepare",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"test:e2e": "npx playwright test",
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
"@nuxt/eslint": "^1.9.0",
|
||||
"@nuxt/ui": "^4.0.0",
|
||||
"@nuxtjs/plausible": "^3.0.1",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@slack/web-api": "^7.10.0",
|
||||
"chrono-node": "^2.8.4",
|
||||
"cloudinary": "^2.7.0",
|
||||
|
|
@ -41,6 +42,7 @@
|
|||
"oidc-provider": "^9.6.1",
|
||||
"rate-limiter-flexible": "^9.1.1",
|
||||
"resend": "^6.0.1",
|
||||
"satori": "^0.26.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vue": "^3.5.20",
|
||||
"vue-cal": "^5.0.1-rc.28",
|
||||
|
|
@ -58,6 +60,7 @@
|
|||
"@types/oidc-provider": "^9.5.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^28.1.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
patches/@shuding+opentype.js+1.4.0-beta.0.patch
Normal file
13
patches/@shuding+opentype.js+1.4.0-beta.0.patch
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
diff --git a/node_modules/@shuding/opentype.js/dist/opentype.js b/node_modules/@shuding/opentype.js/dist/opentype.js
|
||||
index d72d1b7..28f572e 100644
|
||||
--- a/node_modules/@shuding/opentype.js/dist/opentype.js
|
||||
+++ b/node_modules/@shuding/opentype.js/dist/opentype.js
|
||||
@@ -11502,7 +11502,7 @@
|
||||
break;
|
||||
case 'ltag':
|
||||
table = uncompressTable(data, tableEntry);
|
||||
- ltagTable = ltag.parse(table.data, table.offset);
|
||||
+ var ltagTable = ltag.parse(table.data, table.offset);
|
||||
break;
|
||||
case 'maxp':
|
||||
table = uncompressTable(data, tableEntry);
|
||||
BIN
public/og/default.png
Normal file
BIN
public/og/default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
server/assets/fonts/Brygada1918-Regular.ttf
Normal file
BIN
server/assets/fonts/Brygada1918-Regular.ttf
Normal file
Binary file not shown.
BIN
server/assets/fonts/Brygada1918-SemiBold.ttf
Normal file
BIN
server/assets/fonts/Brygada1918-SemiBold.ttf
Normal file
Binary file not shown.
BIN
server/assets/fonts/commit-mono-400.woff
Normal file
BIN
server/assets/fonts/commit-mono-400.woff
Normal file
Binary file not shown.
BIN
server/assets/fonts/commit-mono-600.woff
Normal file
BIN
server/assets/fonts/commit-mono-600.woff
Normal file
Binary file not shown.
29
server/routes/og/events/[slug].js
Normal file
29
server/routes/og/events/[slug].js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// GET /og/events/[slug].png — generated Open Graph image for an event.
|
||||
// Cached on disk by slug + event.updatedAt so any admin edit busts the cache.
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
if (!slug) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Missing slug' })
|
||||
}
|
||||
|
||||
// .png suffix is part of the route filename, but slugs in DB don't include it.
|
||||
const cleanSlug = slug.replace(/\.png$/, '')
|
||||
|
||||
const eventDoc = await loadPublicEvent(event, cleanSlug, { lean: true })
|
||||
const key = eventCacheKey(eventDoc)
|
||||
|
||||
let png = await getCachedOG(key)
|
||||
if (!png) {
|
||||
png = await renderEventOG(eventDoc)
|
||||
await setCachedOG(key, png)
|
||||
}
|
||||
|
||||
setHeader(event, 'Content-Type', 'image/png')
|
||||
setHeader(
|
||||
event,
|
||||
'Cache-Control',
|
||||
'public, max-age=3600, s-maxage=86400, stale-while-revalidate=86400'
|
||||
)
|
||||
return png
|
||||
})
|
||||
37
server/utils/og-cache.js
Normal file
37
server/utils/og-cache.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const CACHE_DIR = resolve(__dirname, '../../.cache/og')
|
||||
|
||||
let ensured = false
|
||||
|
||||
async function ensureCacheDir() {
|
||||
if (ensured) return
|
||||
await mkdir(CACHE_DIR, { recursive: true })
|
||||
ensured = true
|
||||
}
|
||||
|
||||
// Returns cached PNG buffer or null. Key must be safe for a filename.
|
||||
export async function getCachedOG(key) {
|
||||
await ensureCacheDir()
|
||||
try {
|
||||
return await readFile(resolve(CACHE_DIR, `${key}.png`))
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function setCachedOG(key, buffer) {
|
||||
await ensureCacheDir()
|
||||
await writeFile(resolve(CACHE_DIR, `${key}.png`), buffer)
|
||||
}
|
||||
|
||||
// Build a safe, deterministic cache key from event slug + updatedAt timestamp.
|
||||
// Slug is already URL-safe; updatedAt busts the cache on any edit.
|
||||
export function eventCacheKey(event) {
|
||||
const slug = String(event.slug || event._id).replace(/[^a-zA-Z0-9_-]/g, '_')
|
||||
const stamp = event.updatedAt ? new Date(event.updatedAt).getTime() : 0
|
||||
return `event-${slug}-${stamp}`
|
||||
}
|
||||
239
server/utils/og-render.js
Normal file
239
server/utils/og-render.js
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import satori from 'satori'
|
||||
import { Resvg } from '@resvg/resvg-js'
|
||||
|
||||
let fontCache
|
||||
|
||||
async function loadFonts() {
|
||||
if (fontCache) return fontCache
|
||||
const storage = useStorage('assets:server')
|
||||
const [brygadaRegular, brygadaSemiBold, commitRegular, commitBold] =
|
||||
await Promise.all([
|
||||
storage.getItemRaw('fonts/Brygada1918-Regular.ttf'),
|
||||
storage.getItemRaw('fonts/Brygada1918-SemiBold.ttf'),
|
||||
storage.getItemRaw('fonts/commit-mono-400.woff'),
|
||||
storage.getItemRaw('fonts/commit-mono-600.woff')
|
||||
])
|
||||
fontCache = [
|
||||
{ name: 'Brygada 1918', data: brygadaRegular, weight: 400, style: 'normal' },
|
||||
{ name: 'Brygada 1918', data: brygadaSemiBold, weight: 600, style: 'normal' },
|
||||
{ name: 'Commit Mono', data: commitRegular, weight: 400, style: 'normal' },
|
||||
{ name: 'Commit Mono', data: commitBold, weight: 600, style: 'normal' }
|
||||
]
|
||||
return fontCache
|
||||
}
|
||||
|
||||
// Insert Cloudinary transform params (resize, quality) into the URL so we
|
||||
// embed a smaller image and keep the resulting PNG under a reasonable size.
|
||||
function withCloudinaryTransform(url) {
|
||||
if (typeof url !== 'string') return url
|
||||
return url.replace(
|
||||
/\/image\/upload\/(?!w_|h_|c_|q_|f_)/,
|
||||
'/image/upload/w_1200,h_340,c_fill,q_auto,f_jpg/'
|
||||
)
|
||||
}
|
||||
|
||||
async function fetchImageAsDataUrl(url) {
|
||||
if (!url) return null
|
||||
try {
|
||||
const transformed = withCloudinaryTransform(url)
|
||||
const res = await fetch(transformed)
|
||||
if (!res.ok) return null
|
||||
const buf = Buffer.from(await res.arrayBuffer())
|
||||
const contentType = res.headers.get('content-type') || 'image/jpeg'
|
||||
return `data:${contentType};base64,${buf.toString('base64')}`
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function formatEventDate(startDate, timeZone) {
|
||||
const date = new Date(startDate)
|
||||
const dateFmt = new Intl.DateTimeFormat('en-US', {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
timeZone
|
||||
}).format(date)
|
||||
const timeFmt = new Intl.DateTimeFormat('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
timeZoneName: 'short',
|
||||
timeZone
|
||||
}).format(date)
|
||||
return `${dateFmt} · ${timeFmt}`
|
||||
}
|
||||
|
||||
// Design tokens lifted from app/assets/css/main.css (light palette).
|
||||
// Hardcoded here because satori doesn't resolve CSS variables.
|
||||
const COLOR = {
|
||||
bg: '#f4efe4',
|
||||
textBright: '#1a1008',
|
||||
textDim: '#5a5040',
|
||||
candle: '#7a5a10',
|
||||
border: '#b8a880',
|
||||
parch: '#2a2015',
|
||||
parchText: '#ede4d0',
|
||||
parchAccent: '#c4a448'
|
||||
}
|
||||
|
||||
function buildTemplate({ title, dateLabel, imageDataUrl }) {
|
||||
const hasImage = Boolean(imageDataUrl)
|
||||
|
||||
const imageBlock = hasImage
|
||||
? {
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: 340,
|
||||
overflow: 'hidden',
|
||||
borderBottom: `2px dashed ${COLOR.border}`
|
||||
},
|
||||
children: {
|
||||
type: 'img',
|
||||
props: {
|
||||
src: imageDataUrl,
|
||||
width: 1200,
|
||||
height: 340,
|
||||
style: { width: '100%', height: '100%', objectFit: 'cover' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
: {
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: 340,
|
||||
background: COLOR.parch,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderBottom: `2px dashed ${COLOR.border}`
|
||||
},
|
||||
children: {
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
fontFamily: 'Brygada 1918',
|
||||
fontSize: 64,
|
||||
fontWeight: 600,
|
||||
color: COLOR.parchText,
|
||||
letterSpacing: '-0.01em'
|
||||
},
|
||||
children: 'GHOST GUILD'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
background: COLOR.bg,
|
||||
fontFamily: 'Commit Mono'
|
||||
},
|
||||
children: [
|
||||
imageBlock,
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '36px 60px 48px 60px',
|
||||
flex: 1,
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 18
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
fontFamily: 'Commit Mono',
|
||||
fontSize: 18,
|
||||
fontWeight: 600,
|
||||
color: COLOR.candle,
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
children: 'Ghost Guild · Event'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
fontFamily: 'Brygada 1918',
|
||||
fontSize: 56,
|
||||
fontWeight: 600,
|
||||
color: COLOR.textBright,
|
||||
lineHeight: 1.1,
|
||||
letterSpacing: '-0.01em',
|
||||
maxHeight: 140,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
children: title
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'div',
|
||||
props: {
|
||||
style: {
|
||||
fontFamily: 'Commit Mono',
|
||||
fontSize: 22,
|
||||
color: COLOR.textDim
|
||||
},
|
||||
children: dateLabel
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function renderEventOG(event) {
|
||||
const fonts = await loadFonts()
|
||||
const imageDataUrl = event.featureImage?.url
|
||||
? await fetchImageAsDataUrl(event.featureImage.url)
|
||||
: null
|
||||
const dateLabel = formatEventDate(
|
||||
event.startDate,
|
||||
event.displayTimezone || 'America/Toronto'
|
||||
)
|
||||
|
||||
const svg = await satori(
|
||||
buildTemplate({ title: event.title, dateLabel, imageDataUrl }),
|
||||
{ width: 1200, height: 630, fonts }
|
||||
)
|
||||
|
||||
const png = new Resvg(svg, {
|
||||
fitTo: { mode: 'width', value: 1200 }
|
||||
})
|
||||
.render()
|
||||
.asPng()
|
||||
|
||||
return png
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue