#4252 Use vercel og to generate og images for meetings and apps. (#4943)

* #4252 Use vercel og to generate og images for meetings and apps.

* Removed duplication.

* Added new design for og images (wip).

* Implemented og image design for meeting image, including support for all variants.

* Implemented design for app og images.

* clenaup

* Fixed types

* Added the option to not render headseo in main shell in order to render your own.

* Added comments.

* fix

* Small tweaks.

* Fixed lock file.

* Fixed types

* Optimized svg's so vercel og supports them.

* Fixed og image on user page.

* Added truncate utils.

* Small style tweaks

* App og image alignment.

* Added og image to team/slug pages.

* Added correct variable to og image path constant.
This commit is contained in:
Jeroen Reumkens 2022-10-18 19:46:22 +02:00 committed by GitHub
parent 3cf47c2ee9
commit 5c01467caa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 531 additions and 164 deletions

View File

@ -371,7 +371,6 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
const slug = rawSlug.join("/");
// Define conditional gates here
const gates = [
@ -386,8 +385,16 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
<HeadSeo
title={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title} | ${profile.name}`}
description={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title}`}
name={profile.name || undefined}
username={slug || undefined}
meeting={{
title: eventType.title,
profile: { name: `${profile.name}`, image: profile.image },
users: [
...(eventType.users || []).map((user) => ({
name: `${user.name}`,
username: `${user.username}`,
})),
],
}}
nextSeoProps={{
nofollow: eventType.hidden,
noindex: eventType.hidden,

View File

@ -1,6 +1,13 @@
import merge from "lodash/merge";
import { NextSeo, NextSeoProps } from "next-seo";
import React from "react";
import {
AppImageProps,
constructAppImage,
constructMeetingImage,
MeetingImageProps,
} from "@calcom/lib/OgImages";
import { truncate, truncateOnWord } from "@calcom/lib/text";
import { getSeoImage, seoConfig } from "@lib/config/next-seo.config";
import { getBrowserInfo } from "@lib/core/browser/browser.utils";
@ -9,11 +16,11 @@ export type HeadSeoProps = {
title: string;
description: string;
siteName?: string;
name?: string;
url?: string;
username?: string;
canonical?: string;
nextSeoProps?: NextSeoProps;
app?: AppImageProps;
meeting?: MeetingImageProps;
};
/**
@ -63,32 +70,15 @@ const buildSeoMeta = (pageProps: {
};
};
const constructImage = (name: string, description: string, username: string): string => {
return (
encodeURIComponent("Meet **" + name + "** <br>" + description).replace(/'/g, "%27") +
".png?md=1&images=https%3A%2F%2Fcal.com%2Flogo-white.svg&images=" +
(process.env.NEXT_PUBLIC_WEBSITE_URL || process.env.NEXT_PUBLIC_WEBAPP_URL) +
"/" +
username +
"/avatar.png"
);
};
export const HeadSeo = (props: HeadSeoProps): JSX.Element => {
const defaultUrl = getBrowserInfo()?.url;
const image = getSeoImage("default");
const {
title,
description,
name = null,
username = null,
siteName,
canonical = defaultUrl,
nextSeoProps = {},
} = props;
const { title, description, siteName, canonical = defaultUrl, nextSeoProps = {}, app, meeting } = props;
const truncatedDescription = truncate(description, 24);
const longerTruncatedDescriptionOnWords = truncateOnWord(description, 148);
const truncatedDescription = description.length > 24 ? description.substring(0, 23) + "..." : description;
const pageTitle = title + " | Cal.com";
let seoObject = buildSeoMeta({
title: pageTitle,
@ -98,8 +88,20 @@ export const HeadSeo = (props: HeadSeoProps): JSX.Element => {
siteName,
});
if (name && username) {
const pageImage = getSeoImage("ogImage") + constructImage(name, truncatedDescription, username);
if (meeting) {
const pageImage = getSeoImage("ogImage") + constructMeetingImage(meeting);
seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,
image: pageImage,
canonical,
siteName,
});
}
if (app) {
const pageImage =
getSeoImage("ogImage") + constructAppImage({ ...app, description: longerTruncatedDescriptionOnWords });
seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,

View File

@ -14,6 +14,8 @@ import { showToast, SkeletonText } from "@calcom/ui/v2";
import { Button, SkeletonButton, Shell } from "@calcom/ui/v2";
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
import HeadSeo from "@components/seo/head-seo";
const Component = ({
name,
type,
@ -276,6 +278,7 @@ const Component = ({
export default function App(props: {
name: string;
description: AppType["description"];
type: AppType["type"];
isGlobal?: AppType["isGlobal"];
logo: string;
@ -300,7 +303,16 @@ export default function App(props: {
const { t } = useLocale();
return (
<Shell large isPublic heading={t("app_store")} backPath="/apps">
<Shell large isPublic heading={t("app_store")} backPath="/apps" withoutSeo>
<HeadSeo
title={props.name}
description={props.description}
app={{ slug: props.logo, name: props.name, description: props.description }}
nextSeoProps={{
nofollow: true,
noindex: true,
}}
/>
{props.licenseRequired ? (
<LicenseRequired>
<Component {...props} />

View File

@ -60,6 +60,7 @@
"@stripe/stripe-js": "^1.35.0",
"@tanstack/react-query": "^4.3.9",
"@vercel/edge-functions-ui": "^0.2.1",
"@vercel/og": "^0.0.19",
"@wojtekmaj/react-daterange-picker": "^3.3.1",
"accept-language-parser": "^1.5.0",
"async": "^3.2.4",

View File

@ -110,9 +110,13 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
description={
isDynamicGroup ? `Book events with ${dynamicUsernames.join(", ")}` : (user.bio as string) || ""
}
name={isDynamicGroup ? dynamicNames.join(", ") : nameOrUsername}
username={isDynamicGroup ? dynamicUsernames.join(", ") : (user.username as string) || ""}
// avatar={user.avatar || undefined}
meeting={{
title: isDynamicGroup ? "" : `${user.bio}`,
profile: { name: `${profile.name}`, image: null },
users: isDynamicGroup
? dynamicUsernames.map((username, index) => ({ username, name: dynamicNames[index] }))
: [{ username: `${user.username}`, name: `${user.name}` }],
}}
/>
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />

View File

@ -0,0 +1,90 @@
import { ImageResponse } from "@vercel/og";
import { NextApiRequest } from "next";
import type { SatoriOptions } from "satori";
import { z } from "zod";
import { Meeting, App } from "@calcom/lib/OgImages";
const calFont = fetch(new URL("../../../../public/fonts/cal.ttf", import.meta.url)).then((res) =>
res.arrayBuffer()
);
const interFont = fetch(new URL("../../../../public/fonts/Inter-Regular.ttf", import.meta.url)).then((res) =>
res.arrayBuffer()
);
const interFontMedium = fetch(new URL("../../../../public/fonts/Inter-Medium.ttf", import.meta.url)).then(
(res) => res.arrayBuffer()
);
export const config = {
runtime: "experimental-edge",
};
const meetingSchema = z.object({
imageType: z.literal("meeting"),
title: z.string(),
names: z.string().array(),
usernames: z.string().array(),
meetingProfileName: z.string(),
meetingImage: z.string().nullable().optional(),
});
const appSchema = z.object({
imageType: z.literal("app"),
name: z.string(),
description: z.string(),
slug: z.string(),
});
export default async function handler(req: NextApiRequest) {
const { searchParams } = new URL(`${req.url}`);
const imageType = searchParams.get("type");
const [calFontData, interFontData, interFontMediumData] = await Promise.all([
calFont,
interFont,
interFontMedium,
]);
const ogConfig = {
fonts: [
{ name: "inter", data: interFontData, weight: 400 },
{ name: "inter", data: interFontMediumData, weight: 500 },
{ name: "cal", data: calFontData, weight: 400 },
] as SatoriOptions["fonts"],
};
switch (imageType) {
case "meeting": {
const { names, usernames, title, meetingProfileName, meetingImage } = meetingSchema.parse({
names: searchParams.getAll("names"),
usernames: searchParams.getAll("usernames"),
title: searchParams.get("title"),
meetingProfileName: searchParams.get("meetingProfileName"),
meetingImage: searchParams.get("meetingImage"),
imageType,
});
return new ImageResponse(
(
<Meeting
title={title}
profile={{ name: meetingProfileName, image: meetingImage }}
users={names.map((name, index) => ({ name, username: usernames[index] }))}
/>
),
ogConfig
);
}
case "app": {
const { name, description, slug } = appSchema.parse({
name: searchParams.get("name"),
description: searchParams.get("description"),
slug: searchParams.get("slug"),
imageType,
});
return new ImageResponse(<App name={name} description={description} slug={slug} />, ogConfig);
}
default:
return new Response("What you're looking for is not here..", { status: 404 });
}
}

View File

@ -34,6 +34,7 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return (
<App
name={data.name}
description={data.description}
isGlobal={data.isGlobal}
slug={data.slug}
variant={data.variant}

View File

@ -84,6 +84,14 @@ function TeamPage({ team }: TeamPageProps) {
return (
<div>
<HeadSeo title={teamName} description={teamName} />
<HeadSeo
title={teamName}
description={teamName}
meeting={{
title: team?.bio || "",
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
}}
/>
<div className="dark:bg-darkgray-50 h-screen rounded-md bg-gray-100 px-4 pt-12 pb-12">
<div className="max-w-96 mx-auto mb-8 text-center">
<Avatar alt={teamName} imageSrc={getPlaceholderAvatar(team.logo, team.name)} size="lg" />

View File

@ -0,0 +1,9 @@
<svg width="101" height="22" viewBox="0 0 101 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0582 20.817C4.32115 20.817 0 16.2763 0 10.6704C0 5.04589 4.1005 0.467773 10.0582 0.467773C13.2209 0.467773 15.409 1.43945 17.1191 3.66311L14.3609 5.96151C13.2025 4.72822 11.805 4.11158 10.0582 4.11158C6.17833 4.11158 4.04533 7.08268 4.04533 10.6704C4.04533 14.2582 6.38059 17.1732 10.0582 17.1732C11.7866 17.1732 13.2577 16.5566 14.4161 15.3233L17.1375 17.7151C15.501 19.8453 13.2577 20.817 10.0582 20.817Z" fill="#000000"/>
<path d="M29.0161 5.88601H32.7304V20.4612H29.0161V18.331C28.2438 19.8446 26.9566 20.8536 24.4927 20.8536C20.5577 20.8536 17.4133 17.4341 17.4133 13.2297C17.4133 9.02528 20.5577 5.60571 24.4927 5.60571C26.9383 5.60571 28.2438 6.61477 29.0161 8.12835V5.88601ZM29.1264 13.2297C29.1264 10.95 27.5634 9.06266 25.0995 9.06266C22.7274 9.06266 21.1828 10.9686 21.1828 13.2297C21.1828 15.4346 22.7274 17.3967 25.0995 17.3967C27.5451 17.3967 29.1264 15.4907 29.1264 13.2297Z" fill="#000000"/>
<path d="M35.3599 0H39.0742V20.4427H35.3599V0Z" fill="#000000"/>
<path d="M40.7291 18.5182C40.7291 17.3223 41.6853 16.3132 42.9908 16.3132C44.2964 16.3132 45.2158 17.3223 45.2158 18.5182C45.2158 19.7515 44.278 20.7605 42.9908 20.7605C41.7037 20.7605 40.7291 19.7515 40.7291 18.5182Z" fill="#000000"/>
<path d="M59.4296 18.1068C58.0505 19.7885 55.9543 20.8536 53.4719 20.8536C49.0404 20.8536 45.7858 17.4341 45.7858 13.2297C45.7858 9.02528 49.0404 5.60571 53.4719 5.60571C55.8623 5.60571 57.9402 6.61477 59.3193 8.20309L56.4508 10.6136C55.7336 9.71667 54.7958 9.04397 53.4719 9.04397C51.0999 9.04397 49.5553 10.95 49.5553 13.211C49.5553 15.472 51.0999 17.378 53.4719 17.378C54.9062 17.378 55.8991 16.6306 56.6346 15.6215L59.4296 18.1068Z" fill="#000000"/>
<path d="M59.7422 13.2297C59.7422 9.02528 62.9968 5.60571 67.4283 5.60571C71.8598 5.60571 75.1144 9.02528 75.1144 13.2297C75.1144 17.4341 71.8598 20.8536 67.4283 20.8536C62.9968 20.8349 59.7422 17.4341 59.7422 13.2297ZM71.3449 13.2297C71.3449 10.95 69.8003 9.06266 67.4283 9.06266C65.0563 9.04397 63.5117 10.95 63.5117 13.2297C63.5117 15.4907 65.0563 17.3967 67.4283 17.3967C69.8003 17.3967 71.3449 15.4907 71.3449 13.2297Z" fill="#000000"/>
<path d="M100.232 11.5482V20.4428H96.518V12.4638C96.518 9.94119 95.3412 8.85739 93.576 8.85739C91.921 8.85739 90.7442 9.67958 90.7442 12.4638V20.4428H87.0299V12.4638C87.0299 9.94119 85.8346 8.85739 84.0878 8.85739C82.4329 8.85739 80.9802 9.67958 80.9802 12.4638V20.4428H77.2659V5.8676H80.9802V7.88571C81.7525 6.31607 83.15 5.53125 85.3014 5.53125C87.3425 5.53125 89.0525 6.5403 89.9903 8.24074C90.9281 6.50293 92.3072 5.53125 94.8079 5.53125C97.8603 5.54994 100.232 7.86702 100.232 11.5482Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

View File

@ -1,4 +1,4 @@
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" width="1.25em" height="1.25em" viewBox="0, 0, 400,400">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0,0,400,400">
<path id="path0"
d="M100.400 142.062 C 99.630 142.280,98.394 143.076,97.654 143.830 C 96.914 144.583,95.997 145.200,95.616 145.200 C 94.776 145.200,93.802 146.248,93.389 147.598 C 93.221 148.147,92.560 149.054,91.919 149.613 C 90.024 151.267,90.020 151.390,90.010 199.645 C 89.999 248.545,90.014 248.945,91.940 250.744 C 92.571 251.334,93.229 252.262,93.401 252.808 C 93.751 253.916,95.054 255.200,95.829 255.200 C 96.107 255.200,96.710 255.808,97.169 256.550 C 98.373 258.498,94.832 258.400,164.273 258.400 C 231.741 258.400,231.099 258.418,231.949 256.552 C 232.208 255.983,233.149 255.250,234.197 254.801 C 235.357 254.304,236.005 253.774,236.014 253.314 C 236.021 252.921,236.375 251.880,236.800 251.000 C 237.225 250.120,237.579 249.119,237.586 248.776 C 237.594 248.434,237.864 247.804,238.187 247.376 C 238.696 246.704,238.776 240.392,238.787 200.426 C 238.801 149.852,238.967 154.051,236.799 149.949 C 236.610 149.591,236.332 148.647,236.183 147.850 C 235.956 146.640,235.591 146.227,233.964 145.342 C 232.893 144.759,231.907 143.938,231.774 143.518 C 231.641 143.098,231.052 142.539,230.466 142.277 C 229.079 141.657,102.567 141.447,100.400 142.062 "
stroke="none" fill="#f9f9f9" fillRule="evenodd"></path>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4 8 55 48"><title>Exchange_64x</title><path d="M55.50977,8h-12.207A3.48835,3.48835,0,0,0,40.835,9.02246L12.02246,37.835A3.48835,3.48835,0,0,0,11,40.30273v12.207A3.49006,3.49006,0,0,0,14.49023,56h12.207A3.48835,3.48835,0,0,0,29.165,54.97754L57.978,26.165A3.48994,3.48994,0,0,0,59,23.69727v-12.207A3.49007,3.49007,0,0,0,55.50977,8Z" fill="#28a8ea"/><path d="M55.51,56H43.30275a3.49,3.49,0,0,1-2.4678-1.0222L35,49.14286V38.24A6.24,6.24,0,0,1,41.24,32H52.14286L57.9778,37.835A3.49,3.49,0,0,1,59,40.30275V52.51A3.49,3.49,0,0,1,55.51,56Z" fill="#0078d4"/><path d="M14.49,8H26.69725a3.49,3.49,0,0,1,2.4678,1.0222L35,14.85714V25.76A6.24,6.24,0,0,1,28.76,32H17.85714L12.0222,26.16505A3.49,3.49,0,0,1,11,23.69725V11.49A3.49,3.49,0,0,1,14.49,8Z" fill="#50d9ff"/><path d="M33,20.33008V46.66992a1.73444,1.73444,0,0,1-.04.3999A2.31378,2.31378,0,0,1,30.66992,49H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/><path d="M34,20.33008V44.66992A3.36171,3.36171,0,0,1,30.66992,48H11V17H30.66992A3.34177,3.34177,0,0,1,34,20.33008Z" opacity="0.1"/><path d="M33,20.33008V44.66992A2.326,2.326,0,0,1,30.66992,47H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/><path d="M32,20.33008V44.66992A2.326,2.326,0,0,1,29.66992,47H11V18H29.66992A2.326,2.326,0,0,1,32,20.33008Z" opacity="0.1"/><rect x="4.00022" y="18" width="28" height="28" rx="2.33333" fill="#0078d4"/><path d="M22.58533,26.88121h-6.5472V30.7098h6.14535v2.45375H16.03813V37.1401h6.89609v2.4434h-9.868V24.4165h9.5191Z" fill="#fff"/></svg>
<svg width="55" height="48" xmlns="http://www.w3.org/2000/svg" viewBox="4 8 55 48">
<path d="M55.50977,8h-12.207A3.48835,3.48835,0,0,0,40.835,9.02246L12.02246,37.835A3.48835,3.48835,0,0,0,11,40.30273v12.207A3.49006,3.49006,0,0,0,14.49023,56h12.207A3.48835,3.48835,0,0,0,29.165,54.97754L57.978,26.165A3.48994,3.48994,0,0,0,59,23.69727v-12.207A3.49007,3.49007,0,0,0,55.50977,8Z" fill="#28a8ea"/>
<path d="M55.51,56H43.30275a3.49,3.49,0,0,1-2.4678-1.0222L35,49.14286V38.24A6.24,6.24,0,0,1,41.24,32H52.14286L57.9778,37.835A3.49,3.49,0,0,1,59,40.30275V52.51A3.49,3.49,0,0,1,55.51,56Z" fill="#0078d4"/>
<path d="M14.49,8H26.69725a3.49,3.49,0,0,1,2.4678,1.0222L35,14.85714V25.76A6.24,6.24,0,0,1,28.76,32H17.85714L12.0222,26.16505A3.49,3.49,0,0,1,11,23.69725V11.49A3.49,3.49,0,0,1,14.49,8Z" fill="#50d9ff"/>
<path d="M33,20.33008V46.66992a1.73444,1.73444,0,0,1-.04.3999A2.31378,2.31378,0,0,1,30.66992,49H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/>
<path d="M34,20.33008V44.66992A3.36171,3.36171,0,0,1,30.66992,48H11V17H30.66992A3.34177,3.34177,0,0,1,34,20.33008Z" opacity="0.1"/>
<path d="M33,20.33008V44.66992A2.326,2.326,0,0,1,30.66992,47H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/>
<path d="M32,20.33008V44.66992A2.326,2.326,0,0,1,29.66992,47H11V18H29.66992A2.326,2.326,0,0,1,32,20.33008Z" opacity="0.1"/>
<rect x="4.00022" y="18" width="28" height="28" rx="2.33333" fill="#0078d4"/>
<path d="M22.58533,26.88121h-6.5472V30.7098h6.14535v2.45375H16.03813V37.1401h6.89609v2.4434h-9.868V24.4165h9.5191Z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4 8 55 48"><title>Exchange_64x</title><path d="M55.50977,8h-12.207A3.48835,3.48835,0,0,0,40.835,9.02246L12.02246,37.835A3.48835,3.48835,0,0,0,11,40.30273v12.207A3.49006,3.49006,0,0,0,14.49023,56h12.207A3.48835,3.48835,0,0,0,29.165,54.97754L57.978,26.165A3.48994,3.48994,0,0,0,59,23.69727v-12.207A3.49007,3.49007,0,0,0,55.50977,8Z" fill="#28a8ea"/><path d="M55.51,56H43.30275a3.49,3.49,0,0,1-2.4678-1.0222L35,49.14286V38.24A6.24,6.24,0,0,1,41.24,32H52.14286L57.9778,37.835A3.49,3.49,0,0,1,59,40.30275V52.51A3.49,3.49,0,0,1,55.51,56Z" fill="#0078d4"/><path d="M14.49,8H26.69725a3.49,3.49,0,0,1,2.4678,1.0222L35,14.85714V25.76A6.24,6.24,0,0,1,28.76,32H17.85714L12.0222,26.16505A3.49,3.49,0,0,1,11,23.69725V11.49A3.49,3.49,0,0,1,14.49,8Z" fill="#50d9ff"/><path d="M33,20.33008V46.66992a1.73444,1.73444,0,0,1-.04.3999A2.31378,2.31378,0,0,1,30.66992,49H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/><path d="M34,20.33008V44.66992A3.36171,3.36171,0,0,1,30.66992,48H11V17H30.66992A3.34177,3.34177,0,0,1,34,20.33008Z" opacity="0.1"/><path d="M33,20.33008V44.66992A2.326,2.326,0,0,1,30.66992,47H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/><path d="M32,20.33008V44.66992A2.326,2.326,0,0,1,29.66992,47H11V18H29.66992A2.326,2.326,0,0,1,32,20.33008Z" opacity="0.1"/><rect x="4.00022" y="18" width="28" height="28" rx="2.33333" fill="#0078d4"/><path d="M22.58533,26.88121h-6.5472V30.7098h6.14535v2.45375H16.03813V37.1401h6.89609v2.4434h-9.868V24.4165h9.5191Z" fill="#fff"/></svg>
<svg width="55" height="48" xmlns="http://www.w3.org/2000/svg" viewBox="4 8 55 48">
<path d="M55.50977,8h-12.207A3.48835,3.48835,0,0,0,40.835,9.02246L12.02246,37.835A3.48835,3.48835,0,0,0,11,40.30273v12.207A3.49006,3.49006,0,0,0,14.49023,56h12.207A3.48835,3.48835,0,0,0,29.165,54.97754L57.978,26.165A3.48994,3.48994,0,0,0,59,23.69727v-12.207A3.49007,3.49007,0,0,0,55.50977,8Z" fill="#28a8ea"/>
<path d="M55.51,56H43.30275a3.49,3.49,0,0,1-2.4678-1.0222L35,49.14286V38.24A6.24,6.24,0,0,1,41.24,32H52.14286L57.9778,37.835A3.49,3.49,0,0,1,59,40.30275V52.51A3.49,3.49,0,0,1,55.51,56Z" fill="#0078d4"/>
<path d="M14.49,8H26.69725a3.49,3.49,0,0,1,2.4678,1.0222L35,14.85714V25.76A6.24,6.24,0,0,1,28.76,32H17.85714L12.0222,26.16505A3.49,3.49,0,0,1,11,23.69725V11.49A3.49,3.49,0,0,1,14.49,8Z" fill="#50d9ff"/>
<path d="M33,20.33008V46.66992a1.73444,1.73444,0,0,1-.04.3999A2.31378,2.31378,0,0,1,30.66992,49H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/>
<path d="M34,20.33008V44.66992A3.36171,3.36171,0,0,1,30.66992,48H11V17H30.66992A3.34177,3.34177,0,0,1,34,20.33008Z" opacity="0.1"/>
<path d="M33,20.33008V44.66992A2.326,2.326,0,0,1,30.66992,47H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/>
<path d="M32,20.33008V44.66992A2.326,2.326,0,0,1,29.66992,47H11V18H29.66992A2.326,2.326,0,0,1,32,20.33008Z" opacity="0.1"/>
<rect x="4.00022" y="18" width="28" height="28" rx="2.33333" fill="#0078d4"/>
<path d="M22.58533,26.88121h-6.5472V30.7098h6.14535v2.45375H16.03813V37.1401h6.89609v2.4434h-9.868V24.4165h9.5191Z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4 8 55 48"><title>Exchange_64x</title><path d="M55.50977,8h-12.207A3.48835,3.48835,0,0,0,40.835,9.02246L12.02246,37.835A3.48835,3.48835,0,0,0,11,40.30273v12.207A3.49006,3.49006,0,0,0,14.49023,56h12.207A3.48835,3.48835,0,0,0,29.165,54.97754L57.978,26.165A3.48994,3.48994,0,0,0,59,23.69727v-12.207A3.49007,3.49007,0,0,0,55.50977,8Z" fill="#28a8ea"/><path d="M55.51,56H43.30275a3.49,3.49,0,0,1-2.4678-1.0222L35,49.14286V38.24A6.24,6.24,0,0,1,41.24,32H52.14286L57.9778,37.835A3.49,3.49,0,0,1,59,40.30275V52.51A3.49,3.49,0,0,1,55.51,56Z" fill="#0078d4"/><path d="M14.49,8H26.69725a3.49,3.49,0,0,1,2.4678,1.0222L35,14.85714V25.76A6.24,6.24,0,0,1,28.76,32H17.85714L12.0222,26.16505A3.49,3.49,0,0,1,11,23.69725V11.49A3.49,3.49,0,0,1,14.49,8Z" fill="#50d9ff"/><path d="M33,20.33008V46.66992a1.73444,1.73444,0,0,1-.04.3999A2.31378,2.31378,0,0,1,30.66992,49H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/><path d="M34,20.33008V44.66992A3.36171,3.36171,0,0,1,30.66992,48H11V17H30.66992A3.34177,3.34177,0,0,1,34,20.33008Z" opacity="0.1"/><path d="M33,20.33008V44.66992A2.326,2.326,0,0,1,30.66992,47H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/><path d="M32,20.33008V44.66992A2.326,2.326,0,0,1,29.66992,47H11V18H29.66992A2.326,2.326,0,0,1,32,20.33008Z" opacity="0.1"/><rect x="4.00022" y="18" width="28" height="28" rx="2.33333" fill="#0078d4"/><path d="M22.58533,26.88121h-6.5472V30.7098h6.14535v2.45375H16.03813V37.1401h6.89609v2.4434h-9.868V24.4165h9.5191Z" fill="#fff"/></svg>
<svg width="55" height="48" xmlns="http://www.w3.org/2000/svg" viewBox="4 8 55 48">
<path d="M55.50977,8h-12.207A3.48835,3.48835,0,0,0,40.835,9.02246L12.02246,37.835A3.48835,3.48835,0,0,0,11,40.30273v12.207A3.49006,3.49006,0,0,0,14.49023,56h12.207A3.48835,3.48835,0,0,0,29.165,54.97754L57.978,26.165A3.48994,3.48994,0,0,0,59,23.69727v-12.207A3.49007,3.49007,0,0,0,55.50977,8Z" fill="#28a8ea"/>
<path d="M55.51,56H43.30275a3.49,3.49,0,0,1-2.4678-1.0222L35,49.14286V38.24A6.24,6.24,0,0,1,41.24,32H52.14286L57.9778,37.835A3.49,3.49,0,0,1,59,40.30275V52.51A3.49,3.49,0,0,1,55.51,56Z" fill="#0078d4"/>
<path d="M14.49,8H26.69725a3.49,3.49,0,0,1,2.4678,1.0222L35,14.85714V25.76A6.24,6.24,0,0,1,28.76,32H17.85714L12.0222,26.16505A3.49,3.49,0,0,1,11,23.69725V11.49A3.49,3.49,0,0,1,14.49,8Z" fill="#50d9ff"/>
<path d="M33,20.33008V46.66992a1.73444,1.73444,0,0,1-.04.3999A2.31378,2.31378,0,0,1,30.66992,49H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/>
<path d="M34,20.33008V44.66992A3.36171,3.36171,0,0,1,30.66992,48H11V17H30.66992A3.34177,3.34177,0,0,1,34,20.33008Z" opacity="0.1"/>
<path d="M33,20.33008V44.66992A2.326,2.326,0,0,1,30.66992,47H11V18H30.66992A2.326,2.326,0,0,1,33,20.33008Z" opacity="0.2"/>
<path d="M32,20.33008V44.66992A2.326,2.326,0,0,1,29.66992,47H11V18H29.66992A2.326,2.326,0,0,1,32,20.33008Z" opacity="0.1"/>
<rect x="4.00022" y="18" width="28" height="28" rx="2.33333" fill="#0078d4"/>
<path d="M22.58533,26.88121h-6.5472V30.7098h6.14535v2.45375H16.03813V37.1401h6.89609v2.4434h-9.868V24.4165h9.5191Z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="186 38 76 76"><path fill="#fff" d="M244 56h-40v40h40V56z"/><path fill="#EA4335" d="M244 114l18-18h-18v18z"/><path fill="#FBBC04" d="M262 56h-18v40h18V56z"/><path fill="#34A853" d="M244 96h-40v18h40V96z"/><path fill="#188038" d="M186 96v12c0 3.315 2.685 6 6 6h12V96h-18z"/><path fill="#1967D2" d="M262 56V44c0-3.315-2.685-6-6-6h-12v18h18z"/><path fill="#4285F4" d="M244 38h-52c-3.315 0 -6 2.685-6 6v52h18V56h40V38z"/><path fill="#4285F4" d="M212.205 87.03c-1.495-1.01-2.53-2.485-3.095-4.435l3.47-1.43c.315 1.2.865 2.13 1.65 2.79.78.66 1.73.985 2.84.985 1.135 0 2.11-.345 2.925-1.035s1.225-1.57 1.225-2.635c0-1.09-.43-1.98-1.29-2.67-.86-.69-1.94-1.035-3.23-1.035h-2.005V74.13h1.8c1.11 0 2.045-.3 2.805-.9.76-.6 1.14-1.42 1.14-2.465 0 -.93-.34-1.67-1.02-2.225-.68-.555-1.54-.835-2.585-.835-1.02 0 -1.83.27-2.43.815a4.784 4.784 0 0 0 -1.31 2.005l-3.435-1.43c.455-1.29 1.29-2.43 2.515-3.415 1.225-.985 2.79-1.48 4.69-1.48 1.405 0 2.67.27 3.79.815 1.12.545 2 1.3 2.635 2.26.635.965.95 2.045.95 3.245 0 1.225-.295 2.26-.885 3.11-.59.85-1.315 1.5-2.175 1.955v.205a6.605 6.605 0 0 1 2.79 2.175c.725.975 1.09 2.14 1.09 3.5 0 1.36-.345 2.575-1.035 3.64s-1.645 1.905-2.855 2.515c-1.215.61-2.58.92-4.095.92-1.755.005-3.375-.5-4.87-1.51zM233.52 69.81l-3.81 2.755-1.905-2.89 6.835-4.93h2.62V88h-3.74V69.81z"/></svg>
<svg width="76" height="76" xmlns="http://www.w3.org/2000/svg" viewBox="186 38 76 76"><path fill="#fff" d="M244 56h-40v40h40V56z"/><path fill="#EA4335" d="M244 114l18-18h-18v18z"/><path fill="#FBBC04" d="M262 56h-18v40h18V56z"/><path fill="#34A853" d="M244 96h-40v18h40V96z"/><path fill="#188038" d="M186 96v12c0 3.315 2.685 6 6 6h12V96h-18z"/><path fill="#1967D2" d="M262 56V44c0-3.315-2.685-6-6-6h-12v18h18z"/><path fill="#4285F4" d="M244 38h-52c-3.315 0 -6 2.685-6 6v52h18V56h40V38z"/><path fill="#4285F4" d="M212.205 87.03c-1.495-1.01-2.53-2.485-3.095-4.435l3.47-1.43c.315 1.2.865 2.13 1.65 2.79.78.66 1.73.985 2.84.985 1.135 0 2.11-.345 2.925-1.035s1.225-1.57 1.225-2.635c0-1.09-.43-1.98-1.29-2.67-.86-.69-1.94-1.035-3.23-1.035h-2.005V74.13h1.8c1.11 0 2.045-.3 2.805-.9.76-.6 1.14-1.42 1.14-2.465 0 -.93-.34-1.67-1.02-2.225-.68-.555-1.54-.835-2.585-.835-1.02 0 -1.83.27-2.43.815a4.784 4.784 0 0 0 -1.31 2.005l-3.435-1.43c.455-1.29 1.29-2.43 2.515-3.415 1.225-.985 2.79-1.48 4.69-1.48 1.405 0 2.67.27 3.79.815 1.12.545 2 1.3 2.635 2.26.635.965.95 2.045.95 3.245 0 1.225-.295 2.26-.885 3.11-.59.85-1.315 1.5-2.175 1.955v.205a6.605 6.605 0 0 1 2.79 2.175c.725.975 1.09 2.14 1.09 3.5 0 1.36-.345 2.575-1.035 3.64s-1.645 1.905-2.855 2.515c-1.215.61-2.58.92-4.095.92-1.755.005-3.375-.5-4.87-1.51zM233.52 69.81l-3.81 2.755-1.905-2.89 6.835-4.93h2.62V88h-3.74V69.81z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="17 34 127 113">
<svg width="127" height="113" xmlns="http://www.w3.org/2000/svg" viewBox="17 34 127 113">
<defs>
<linearGradient id="a" x1="28.286" y1="53.757" x2="70.714" y2="127.243" gradientUnits="userSpaceOnUse">
<linearGradient id="gradient" x1="28.286" y1="53.757" x2="70.714" y2="127.243" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#1784d9"/>
<stop offset="0.5" stop-color="#107ad5"/>
<stop offset="1" stop-color="#0a63c9"/>
@ -30,7 +30,7 @@
<path d="M83.5,63.19v57.43a5.45,5.45,0,0,1-1.5,3.82,1.92,1.92,0,0,1-.15.14,4.911,4.911,0,0,1-1.47,1c-.01,0-.02.01-.03.01a5.268,5.268,0,0,1-2.04.41H47v-2h.07V93.02a2.644,2.644,0,0,1,1.33-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13V57.5H78.12A5.21,5.21,0,0,1,82,59.32,5.846,5.846,0,0,1,83.5,63.19Z" opacity="0.1"/>
<path d="M82.75,63.28v56.34a5.735,5.735,0,0,1-.75,2.87,4.989,4.989,0,0,1-2.29,2.09c-.12.05-.25.1-.38.14a5.088,5.088,0,0,1-1.67.28H47v-1h.07V93.02a2.644,2.644,0,0,1,1.33-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13V57.75H77.37A5.373,5.373,0,0,1,82,60.51,5.5,5.5,0,0,1,82.75,63.28Z" opacity="0.125"/>
<path d="M82,63.38v55.24a5.158,5.158,0,0,1-3.74,5.22A4.731,4.731,0,0,1,77,124H47.07V93.02a2.644,2.644,0,0,1,1.33-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13V58H76.62A5.382,5.382,0,0,1,82,63.38Z" opacity="0.2"/>
<rect x="17" y="58" width="65" height="65" rx="5.38" fill="url(#a)"/>
<rect x="17" y="58" width="65" height="65" rx="5.38" fill="url(#gradient)"/>
<path d="M35.041,81.643A14.637,14.637,0,0,1,40.785,75.3a17.366,17.366,0,0,1,9.128-2.287,16.154,16.154,0,0,1,8.444,2.169,14.489,14.489,0,0,1,5.59,6.062,19.581,19.581,0,0,1,1.958,8.916,20.653,20.653,0,0,1-2.017,9.329,14.837,14.837,0,0,1-5.755,6.274,16.788,16.788,0,0,1-8.763,2.228,16.542,16.542,0,0,1-8.632-2.193,14.705,14.705,0,0,1-5.661-6.074A19.091,19.091,0,0,1,33.1,90.913,21.187,21.187,0,0,1,35.041,81.643Zm6.121,14.895a9.5,9.5,0,0,0,3.231,4.175,8.44,8.44,0,0,0,5.048,1.521,8.862,8.862,0,0,0,5.39-1.568,9.107,9.107,0,0,0,3.137-4.187,16.181,16.181,0,0,0,1-5.826,17.723,17.723,0,0,0-.943-5.9A9.345,9.345,0,0,0,55,80.417a8.35,8.35,0,0,0-5.343-1.651A8.718,8.718,0,0,0,44.488,80.3a9.576,9.576,0,0,0-3.3,4.21,16.71,16.71,0,0,0-.024,12.029Z" fill="#fff"/>
<rect width="180" height="180" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -1,22 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2228.833 2073.333">
<path fill="#5059C9" d="M1554.637,777.5h575.713c54.391,0,98.483,44.092,98.483,98.483c0,0,0,0,0,0v524.398 c0,199.901-162.051,361.952-361.952,361.952h0h-1.711c-199.901,0.028-361.975-162-362.004-361.901c0-0.017,0-0.034,0-0.052V828.971 C1503.167,800.544,1526.211,777.5,1554.637,777.5L1554.637,777.5z"/>
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2228.833 2073.333">
<path fill="#5059C9" d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h-1.711c-199.901.028-361.975-162-362.004-361.901V828.971c.001-28.427 23.045-51.471 51.471-51.471z"/>
<circle fill="#5059C9" cx="1943.75" cy="440.583" r="233.25"/>
<circle fill="#7B83EB" cx="1218.083" cy="336.917" r="336.917"/>
<path fill="#7B83EB" d="M1667.323,777.5H717.01c-53.743,1.33-96.257,45.931-95.01,99.676v598.105 c-7.505,322.519,247.657,590.16,570.167,598.053c322.51-7.893,577.671-275.534,570.167-598.053V877.176 C1763.579,823.431,1721.066,778.83,1667.323,777.5z"/>
<path opacity=".1" d="M1244,777.5v838.145c-0.258,38.435-23.549,72.964-59.09,87.598 c-11.316,4.787-23.478,7.254-35.765,7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833 c-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1244z"/>
<path opacity=".2" d="M1192.167,777.5v889.978c-0.002,12.287-2.47,24.449-7.257,35.765 c-14.634,35.541-49.163,58.833-87.598,59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833 c-7.257-17.623-12.958-34.21-18.142-51.833c-18.144-59.476-27.402-121.307-27.472-183.49V877.02 c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z"/>
<path opacity=".2" d="M1192.167,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855h-447.84 c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z"/>
<path opacity=".2" d="M1140.333,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855H649.472 c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1140.333z"/>
<path opacity=".1" d="M1244,509.522v163.275c-8.812,0.518-17.105,1.037-25.917,1.037 c-8.812,0-17.105-0.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003 c-7.153-16.715-12.706-34.071-16.587-51.833h258.648C1201.449,414.866,1243.801,457.217,1244,509.522z"/>
<path opacity=".2" d="M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293 c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z"/>
<path opacity=".2" d="M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293 c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z"/>
<path opacity=".2" d="M1140.333,561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003 h138.395C1097.783,466.699,1140.134,509.051,1140.333,561.355z"/>
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="198.099" y1="1683.0726" x2="942.2344" y2="394.2607" gradientTransform="matrix(1 0 0 -1 0 2075.3333)">
<path fill="#7B83EB" d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/>
<path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598a91.856 91.856 0 0 1-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833a631.287 631.287 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/>
<path opacity=".2" d="M1192.167 777.5v889.978a91.838 91.838 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833-7.257-17.623-12.958-34.21-18.142-51.833a631.282 631.282 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/>
<path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.282 631.282 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/>
<path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.282 631.282 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/>
<path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037-8.812 0-17.105-.518-25.917-1.037a284.472 284.472 0 0 1-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003a288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/>
<path opacity=".2" d="M1192.167 561.355v111.442a284.472 284.472 0 0 1-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/>
<path opacity=".2" d="M1192.167 561.355v111.442a284.472 284.472 0 0 1-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/>
<path opacity=".2" d="M1140.333 561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003h138.395c52.305.199 94.656 42.551 94.855 94.855z"/>
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="198.099" y1="1683.073" x2="942.234" y2="394.261">
<stop offset="0" stop-color="#5a62c3"/>
<stop offset=".5" stop-color="#4d55bd"/>
<stop offset="1" stop-color="#3940ab"/>
</linearGradient>
<path fill="url(#a)" d="M95.01,466.5h950.312c52.473,0,95.01,42.538,95.01,95.01v950.312c0,52.473-42.538,95.01-95.01,95.01 H95.01c-52.473,0-95.01-42.538-95.01-95.01V561.51C0,509.038,42.538,466.5,95.01,466.5z"/>
<path fill="#FFF" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/>
</svg>
<path fill="url(#a)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/>
<path fill="#FFF" d="M820.211 828.193h-189.97v517.297h-121.03V828.193H320.123V727.844h500.088v100.349z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1,17 +1 @@
<?xml version="1.0" standalone="no"?>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M203 2982 c-100 -35 -171 -115 -193 -216 -8 -36 -10 -416 -8 -1296
l3 -1245 28 -57 c32 -64 80 -111 146 -142 l46 -21 1275 0 1275 0 57 28 c64 32
111 80 142 146 l21 46 0 1275 0 1275 -21 46 c-31 66 -78 114 -142 146 l-57 28
-1260 2 c-1189 2 -1263 1 -1312 -15z m1108 -1008 c101 -51 195 -203 284 -464
78 -225 156 -350 220 -350 61 0 163 141 239 329 14 35 34 70 43 77 28 21 79
17 108 -9 38 -32 35 -74 -13 -185 -112 -259 -233 -382 -377 -382 -159 0 -274
141 -391 480 -78 228 -163 363 -227 363 -34 0 -79 -34 -123 -91 -36 -47 -144
-256 -144 -279 0 -19 -51 -53 -80 -53 -34 0 -76 32 -84 64 -7 28 23 114 74
216 86 170 197 283 305 311 44 11 113 0 166 -27z"/>
</g>
</svg>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 300 300"><path d="M20.3 1.8C10.3 5.3 3.2 13.3 1 23.4.2 27 0 65 .2 153l.3 124.5 2.8 5.7c3.2 6.4 8 11.1 14.6 14.2l4.6 2.1h255l5.7-2.8c6.4-3.2 11.1-8 14.2-14.6l2.1-4.6v-255l-2.1-4.6c-3.1-6.6-7.8-11.4-14.2-14.6L277.5.5l-126-.2C32.6.1 25.2.2 20.3 1.8zm110.8 100.8c10.1 5.1 19.5 20.3 28.4 46.4 7.8 22.5 15.6 35 22 35 6.1 0 16.3-14.1 23.9-32.9 1.4-3.5 3.4-7 4.3-7.7 2.8-2.1 7.9-1.7 10.8.9 3.8 3.2 3.5 7.4-1.3 18.5-11.2 25.9-23.3 38.2-37.7 38.2-15.9 0-27.4-14.1-39.1-48-7.8-22.8-16.3-36.3-22.7-36.3-3.4 0-7.9 3.4-12.3 9.1-3.6 4.7-14.4 25.6-14.4 27.9 0 1.9-5.1 5.3-8 5.3-3.4 0-7.6-3.2-8.4-6.4-.7-2.8 2.3-11.4 7.4-21.6 8.6-17 19.7-28.3 30.5-31.1 4.4-1.1 11.3 0 16.6 2.7z"/></svg>

Before

Width:  |  Height:  |  Size: 973 B

After

Width:  |  Height:  |  Size: 761 B

View File

@ -1,9 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28.87 28.87">
<g data-name="Layer 2">
<g data-name="Layer 1">
<rect width="28.87" height="28.87" fill="#6772e5" rx="6.48" ry="6.48" />
<path fill="#fff" fill-rule="evenodd"
d="M13.3 11.2c0-.69.57-1 1.49-1a9.84 9.84 0 0 1 4.37 1.13V7.24a11.6 11.6 0 0 0-4.36-.8c-3.56 0-5.94 1.86-5.94 5 0 4.86 6.68 4.07 6.68 6.17 0 .81-.71 1.07-1.68 1.07A11.06 11.06 0 0 1 9 17.25v4.19a12.19 12.19 0 0 0 4.8 1c3.65 0 6.17-1.8 6.17-5 .03-5.21-6.67-4.27-6.67-6.24z" />
</g>
</g>
<svg width="28" height="28" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28">
<rect width="28" height="28" fill="#6772e5" rx="6" ry="6"/>
<path fill="#fff" d="M13.3 11.2c0-.69.57-1 1.49-1a9.84 9.84 0 0 1 4.37 1.13V7.24a11.6 11.6 0 0 0-4.36-.8c-3.56 0-5.94 1.86-5.94 5 0 4.86 6.68 4.07 6.68 6.17 0 .81-.71 1.07-1.68 1.07A11.06 11.06 0 0 1 9 17.25v4.19a12.19 12.19 0 0 0 4.8 1c3.65 0 6.17-1.8 6.17-5 .03-5.21-6.67-4.27-6.67-6.24z"/>
</svg>

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 448 B

172
packages/lib/OgImages.tsx Normal file
View File

@ -0,0 +1,172 @@
import React from "react";
import { CAL_URL } from "./constants";
// Ensures tw prop is typed.
declare module "react" {
interface HTMLAttributes<T> {
tw?: string;
}
}
export interface MeetingImageProps {
title: string;
profile: { name: string; image?: string | null };
users?: { name: string; username: string }[];
}
export interface AppImageProps {
name: string;
description: string;
slug: string;
}
const joinMultipleNames = (names: string[] = []) => {
const lastName = names.pop();
return `${names.length > 0 ? `${names.join(", ")} & ${lastName}` : lastName}`;
};
/**
* Test urls:
* 1. 1 user http://localhost:3000/api/social/og/image?type=meeting&title=super%20long%20event%20title%20for%20testing%20purposes&meetingProfileName=Pro%20Example&meetingImage=http://localhost:3000/pro/avatar.png&names=Pro%20Example&usernames=pro
* 2. Team event (collection), lot's of people, long title http://localhost:3000/api/social/og/image?type=meeting&title=Getting%20to%20know%20us%20and%20have%20a%20beer%20together&meetingProfileName=Seeded%20Team&names=Team%20Pro%20Example%202&names=Team%20Pro%20Example%203&names=Team%20Pro%20Example%204&names=Team%20Free%20Example&names=Team%20Pro%20Example&usernames=teampro2&usernames=teampro3&usernames=teampro4&usernames=teamfree&usernames=teampro
* 3. Team event of 2 (collection), http://localhost:3000/api/social/og/image?type=meeting&title=Getting%20to%20know%20each%20other&meetingProfileName=Seeded%20Team&names=Team%20Pro%20Example%202&names=Team%20Pro%20Example%203&usernames=teampro2&usernames=teampro3
* 4. Team event (round robin) http://localhost:3000/api/social/og/image?type=meeting&title=Round%20Robin%20Seeded%20Team%20Event&meetingProfileName=Seeded%20Team
* 5. Dynamic collective (2 persons) http://localhost:3000/api/social/og/image?type=meeting&title=15min&meetingProfileName=Team%20Pro%20Example,%20Pro%20Example&names=Team%20Pro%20Example&names=Pro%20Example&usernames=teampro&usernames=pro
*/
export const constructMeetingImage = ({ title, users = [], profile }: MeetingImageProps): string => {
return [
`?type=meeting`,
`&title=${encodeURIComponent(title)}`,
`&meetingProfileName=${encodeURIComponent(profile.name)}`,
profile.image && `&meetingImage=${encodeURIComponent(profile.image)}`,
`${users.map((user) => `&names=${encodeURIComponent(user.name)}`).join("")}`,
`${users.map((user) => `&usernames=${encodeURIComponent(user.username)}`).join("")}`,
// Joinining a multiline string for readability.
].join("");
};
/**
* Test url:
* http://localhost:3000/api/social/og/image?type=app&name=Huddle01&slug=/api/app-store/huddle01video/icon.svg&description=Huddle01%20is%20a%20new%20video%20conferencing%20software%20native%20to%20Web3%20and%20is%20comparable%20to%20a%20decentralized%20version%20of%20Zoom.%20It%20supports%20conversations%20for...
*/
export const constructAppImage = ({ name, slug, description }: AppImageProps): string => {
return [
`?type=app`,
`&name=${encodeURIComponent(name)}`,
`&slug=${encodeURIComponent(slug)}`,
`&description=${encodeURIComponent(description)}`,
// Joinining a multiline string for readability.
].join("");
};
const Wrapper = ({
children,
variant = "light",
}: {
children: React.ReactNode;
variant?: "light" | "dark";
}) => (
<div tw="flex w-full h-full">
<img
tw="flex absolute left-0 top-0 w-full h-[110%]"
src={`${CAL_URL}/social-bg-${variant}.jpg`}
alt="background"
width="1200"
height="300"
/>
<div tw="flex flex-col w-full h-full px-[80px] py-[70px] items-start justify-center">{children}</div>
</div>
);
export const Meeting = ({ title, users = [], profile }: MeetingImageProps) => {
// We filter attendees here based on whether they have an image and filter duplicates.
// Users ALWAYS have an image (albeit a gray empty person avatar), so this mainly filters out
// any non existing images for dynamic collectives, while at the same time removing them from
// the names list, because the profile name of that event is a concatenation of all names.
const attendees = (profile?.image ? [profile, ...users] : users).filter(
(value, index, self) => self.findIndex((v) => v.name === value.name) == index
);
// Construct list of avatar urls, removes duplicates and empty profile images
const avatars = attendees
.map((user) => {
if ("image" in user && user?.image) return user.image;
if ("username" in user && user?.username) return `${CAL_URL}/${user.username}/avatar.png`;
return null;
})
.filter(Boolean) as string[];
// In case there is NO other attendee than the single meeting profile without an image, we add
// that name back in here, since the event probably is a round robin event.
const names = attendees.length > 0 ? attendees.map((user) => user.name) : [profile.name];
return (
<Wrapper>
<div tw="h-full flex flex-col justify-start">
<div tw="flex items-center justify-center" style={{ fontFamily: "cal", fontWeight: "300" }}>
<img src={`${CAL_URL}/cal-logo-word-black.svg`} width="350" alt="Logo" />
{avatars.length > 0 && <div tw="font-bold text-black text-[92px] mx-8 bottom-2">/</div>}
<div tw="flex flex-row">
{avatars.slice(0, 3).map((avatar) => (
<img
tw="rounded-full mr-[-36px] border-[6px] border-black"
key={avatar}
src={avatar}
alt="Profile picture"
width="160"
/>
))}
{avatars.length > 3 && (
<div
tw="flex items-center top-[50%] justify-center w-32 h-32 rounded-full bg-black text-white text-4xl font-bold"
style={{ transform: "translateY(-50%)" }}>
+{avatars.length - 3}
</div>
)}
</div>
</div>
<div tw="relative flex text-[54px] w-full flex-col text-black mt-auto">
<div tw="flex">
Meet{" "}
<strong tw="flex ml-4 font-medium" style={{ whiteSpace: "nowrap" }}>
{joinMultipleNames(names)}
</strong>
</div>
<div tw="flex mt-2" style={{ whiteSpace: "nowrap" }}>
{title}
</div>
{/* Adds overlay gradient for long text */}
<div
tw="absolute flex w-[200px] h-full left-[920px] top-0"
style={{
background: "linear-gradient(90deg, rgba(198,203,212,0) 0px, rgba(198,203,212,1) 120px)",
}}
/>
</div>
</div>
</Wrapper>
);
};
export const App = ({ name, description, slug }: AppImageProps) => (
<Wrapper variant="dark">
<img
src={`${CAL_URL}/cal-logo-word-dark.svg`}
width="150"
alt="Logo"
tw="absolute right-[48px] top-[32px]"
/>
<div tw="flex items-center justify-center w-full">
<div tw="flex items-center justify-center flex-row-reverse bg-[rgba(255,255,255,0.7)] p-8 rounded-lg w-[172px] h-[172px]">
<img src={`${CAL_URL}${slug}`} alt="App icon" width="125" />
</div>
</div>
<div tw="flex mt-7 text-center items-center justify-center w-full flex-col text-[#f9fafb]">
<div tw="flex text-[56px] mb-7" style={{ fontFamily: "cal", fontWeight: "300" }}>
{name}
</div>
<div tw="flex text-[40px]">{description}</div>
</div>
</Wrapper>
);

View File

@ -34,5 +34,5 @@ export const POWERED_BY_URL = `${WEBSITE_URL}/?utm_source=embed&utm_medium=power
export const DOCS_URL = "https://docs.cal.com";
export const DEVELOPER_DOCS = "https://developer.cal.com";
export const SEO_IMG_DEFAULT = `${WEBSITE_URL}/og-image.png`;
export const SEO_IMG_OGIMG = "https://og-image-one-pi.vercel.app/";
export const SEO_IMG_OGIMG = `${CAL_URL}/api/social/og/image`;
export const SEO_IMG_OGIMG_VIDEO = `${WEBSITE_URL}/video-og-image.png`;

View File

@ -2,15 +2,17 @@ import { DefaultSeoProps, NextSeoProps } from "next-seo";
import { SEO_IMG_DEFAULT, SEO_IMG_OGIMG } from "@calcom/lib/constants";
import { AppImageProps, MeetingImageProps } from "./OgImages";
export type HeadSeoProps = {
title: string;
description: string;
siteName?: string;
name?: string;
url?: string;
username?: string;
canonical?: string;
nextSeoProps?: NextSeoProps;
app?: AppImageProps;
meeting?: MeetingImageProps;
};
const seoImages = {

View File

@ -15,6 +15,7 @@
"@calcom/dayjs": "*",
"@prisma/client": "^4.2.1",
"@sendgrid/client": "^7.7.0",
"@vercel/og": "^0.0.19",
"bcryptjs": "^2.4.3",
"ical.js": "^1.4.0",
"ics": "^2.37.0",

17
packages/lib/text.ts Normal file
View File

@ -0,0 +1,17 @@
export const truncate = (text: string, maxLength: number, ellipsis = true) => {
if (text.length <= maxLength) return text;
return `${text.slice(0, maxLength - 3)}${ellipsis ? "..." : ""}`;
};
export const truncateOnWord = (text: string, maxLength: number, ellipsis = true) => {
if (text.length <= maxLength) return text;
// First split on maxLength chars
let truncatedText = text.substring(0, 148);
// Then split on the last space, this way we split on the last word,
// which looks just a bit nicer.
truncatedText = truncatedText.substring(0, Math.min(truncatedText.length, truncatedText.lastIndexOf(" ")));
if (ellipsis) truncatedText += "...";
return truncatedText;
};

View File

@ -126,14 +126,16 @@ const Layout = (props: LayoutProps) => {
return (
<>
<HeadSeo
title={pageTitle ?? "Cal.com"}
description={props.subtitle ? props.subtitle?.toString() : ""}
nextSeoProps={{
nofollow: true,
noindex: true,
}}
/>
{!props.withoutSeo && (
<HeadSeo
title={pageTitle ?? "Cal.com"}
description={props.subtitle ? props.subtitle?.toString() : ""}
nextSeoProps={{
nofollow: true,
noindex: true,
}}
/>
)}
<div>
<Toaster position="bottom-right" />
</div>
@ -173,6 +175,8 @@ type LayoutProps = {
flexChildrenContainer?: boolean;
isPublic?: boolean;
withoutMain?: boolean;
// Gives you the option to skip HeadSEO and render your own.
withoutSeo?: boolean;
};
const CustomBrandingContainer = () => {

View File

@ -1,9 +1,10 @@
import merge from "lodash/merge";
import { NextSeo, NextSeoProps } from "next-seo";
import React from "react";
import { constructAppImage, constructMeetingImage } from "@calcom/lib/OgImages";
import { getBrowserInfo } from "@calcom/lib/browser/browser.utils";
import { seoConfig, getSeoImage, HeadSeoProps } from "@calcom/lib/next-seo.config";
import { truncate, truncateOnWord } from "@calcom/lib/text";
/**
* Build full seo tags from title, desc, canonical and url
@ -52,32 +53,15 @@ const buildSeoMeta = (pageProps: {
};
};
const constructImage = (name: string, description: string, username: string): string => {
return (
encodeURIComponent("Meet **" + name + "** <br>" + description).replace(/'/g, "%27") +
".png?md=1&images=https%3A%2F%2Fcal.com%2Flogo-white.svg&images=" +
(process.env.NEXT_PUBLIC_WEBSITE_URL || process.env.NEXT_PUBLIC_WEBAPP_URL) +
"/" +
username +
"/avatar.png"
);
};
export const HeadSeo = (props: HeadSeoProps): JSX.Element => {
const defaultUrl = getBrowserInfo()?.url;
const image = getSeoImage("default");
const {
title,
description,
name = null,
username = null,
siteName,
canonical = defaultUrl,
nextSeoProps = {},
} = props;
const { title, description, siteName, canonical = defaultUrl, nextSeoProps = {}, app, meeting } = props;
const truncatedDescription = truncate(description, 24);
const longerTruncatedDescriptionOnWords = truncateOnWord(description, 148);
const truncatedDescription = description.length > 24 ? description.substring(0, 23) + "..." : description;
const pageTitle = title + " | Cal.com";
let seoObject = buildSeoMeta({
title: pageTitle,
@ -87,8 +71,20 @@ export const HeadSeo = (props: HeadSeoProps): JSX.Element => {
siteName,
});
if (name && username) {
const pageImage = getSeoImage("ogImage") + constructImage(name, truncatedDescription, username);
if (meeting) {
const pageImage = getSeoImage("ogImage") + constructMeetingImage(meeting);
seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,
image: pageImage,
canonical,
siteName,
});
}
if (app) {
const pageImage =
getSeoImage("ogImage") + constructAppImage({ ...app, description: longerTruncatedDescriptionOnWords });
seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,

127
yarn.lock
View File

@ -4549,24 +4549,6 @@
aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0"
"@radix-ui/react-slider@^0.1.1":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-0.1.4.tgz#a7b7a480ee00158195794b08cd3f1583cf102518"
integrity sha512-0z3bCcdrAi+FIcoLXS6r0ESVWuuyMnUJoCsFm7tC7Rtv95x34YtaI8YfSyQmzuMVS4rTsNtCCTZ/s727uRaVkQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/number" "0.1.0"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-collection" "0.1.4"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-direction" "0.1.0"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-use-size" "0.1.1"
"@radix-ui/react-slider@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-1.0.0.tgz#4cabadd243aa088eb45ac710cd7cdc518fafb07e"
@ -4712,13 +4694,6 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "1.0.0"
"@radix-ui/react-use-direction@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-direction/-/react-use-direction-0.1.0.tgz#97ac1d52e497c974389e7988f809238ed72e7df7"
integrity sha512-NajpY/An9TCPSfOVkgWIdXJV+VuWl67PxB6kOKYmtNAFHvObzIoh8o0n9sAuwSAyFCZVq211FEf9gvVDRhOyiA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-escape-keydown@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-0.1.0.tgz#dc80cb3753e9d1bd992adbad9a149fb6ea941874"
@ -4771,13 +4746,6 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/rect" "1.0.0"
"@radix-ui/react-use-size@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.1.tgz#f6b75272a5d41c3089ca78c8a2e48e5f204ef90f"
integrity sha512-pTgWM5qKBu6C7kfKxrKPoBI2zZYZmp2cSXzpUiGM3qEBQlMLtYhaY2JXdXUCxz+XmD1YEjc8oRwvyfsD4AG4WA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-size@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz#a0b455ac826749419f6354dc733e2ca465054771"
@ -4860,6 +4828,11 @@
tiny-warning "^1.0.3"
tslib "^2.3.0"
"@resvg/resvg-wasm@2.0.0-alpha.4":
version "2.0.0-alpha.4"
resolved "https://registry.yarnpkg.com/@resvg/resvg-wasm/-/resvg-wasm-2.0.0-alpha.4.tgz#fc2f86186a9641df030d8f9f3f9d995899cd1ecb"
integrity sha512-pWIG9a/x1ky8gXKRhPH1OPKpHFoMN1ISLbJ+O+gPXQHIAKhNd5I28RlWf7q576hAOQA9JZTlo3p/M2uyLzJmmw==
"@rollup/pluginutils@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
@ -5030,6 +5003,14 @@
dependencies:
"@sentry/cli" "^1.73.0"
"@shuding/opentype.js@1.4.0-beta.0":
version "1.4.0-beta.0"
resolved "https://registry.yarnpkg.com/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz#5d1e7e9e056f546aad41df1c5043f8f85d39e24b"
integrity sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==
dependencies:
fflate "^0.7.3"
string.prototype.codepointat "^0.2.1"
"@sinclair/typebox@^0.24.1":
version "0.24.19"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.19.tgz#5297278e0d8a1aea084685a3216074910ac6c113"
@ -6289,11 +6270,6 @@
lodash.isplainobject "^4.0.6"
lodash.merge "^4.6.2"
"@tanstack/query-core@4.10.3":
version "4.10.3"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.10.3.tgz#a6477bab9ed1ae4561ca0a59ae06f8c615c692b7"
integrity sha512-+ME02sUmBfx3Pjd+0XtEthK8/4rVMD2jcxvnW9DSgFONpKtpjzfRzjY4ykzpDw1QEo2eoPvl7NS8J5mNI199aA==
"@tanstack/query-core@4.2.3":
version "4.2.3"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.2.3.tgz#52d75430c9662cc85c160761c1421de483c7791f"
@ -6323,14 +6299,6 @@
"@types/use-sync-external-store" "^0.0.3"
use-sync-external-store "^1.2.0"
"@tanstack/react-query@^4.2.3":
version "4.10.3"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.10.3.tgz#294deefa0fb6ada88bc4631d346ef5d5551c5443"
integrity sha512-4OEJjkcsCTmG3ui7RjsVzsXerWQvInTe95CBKFyOV/GchMUlNztoFnnYmlMhX7hLUqJMhbG9l7M507V7+xU8Hw==
dependencies:
"@tanstack/query-core" "4.10.3"
use-sync-external-store "^1.2.0"
"@tanstack/react-query@^4.3.9":
version "4.3.9"
resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.3.9.tgz#13332a1d4dd404baec24c2853883bcb3cc61ea92"
@ -7396,6 +7364,15 @@
clsx "^1.1.1"
next-transpile-modules "^8.0.0"
"@vercel/og@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@vercel/og/-/og-0.0.19.tgz#f0a796244c90b14aca40ee26225f2e046ccfda08"
integrity sha512-1LrPUlCoe8T+aEI1fGdvJDhwewK1/wpYlQnF+E9ERgJMFCnmWH4FA+NYBkQaJqxUUeHoutIxNZTHQFT39+I+BQ==
dependencies:
"@resvg/resvg-wasm" "2.0.0-alpha.4"
satori "0.0.42"
yoga-wasm-web "0.1.2"
"@vitejs/plugin-react@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-1.3.2.tgz#2fcf0b6ce9bcdcd4cec5c760c199779d5657ece1"
@ -9683,6 +9660,11 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
camelize@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
integrity sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==
caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001359:
version "1.0.30001365"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001365.tgz#72c2c3863b1a545cfd3d9953535bd2ee17568158"
@ -10702,6 +10684,21 @@ crypto@^1.0.1:
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
css-background-parser@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/css-background-parser/-/css-background-parser-0.1.0.tgz#48a17f7fe6d4d4f1bca3177ddf16c5617950741b"
integrity sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==
css-box-shadow@1.0.0-3:
version "1.0.0-3"
resolved "https://registry.yarnpkg.com/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz#9eaeb7140947bf5d649fc49a19e4bbaa5f602713"
integrity sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==
css-loader@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645"
@ -10753,6 +10750,15 @@ css-select@^4.1.3:
domutils "^2.8.0"
nth-check "^2.0.1"
css-to-react-native@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756"
integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
css-what@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe"
@ -13064,6 +13070,11 @@ fetch@^1.0.1, fetch@^1.1.0:
biskviit "1.0.1"
encoding "0.1.12"
fflate@^0.7.3:
version "0.7.4"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
fictional@^0.4.13:
version "0.4.13"
resolved "https://registry.yarnpkg.com/fictional/-/fictional-0.4.13.tgz#3103cece766b62e8221d3a5c8dc5de043c9280ac"
@ -20006,7 +20017,7 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.10, postcss-selecto
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@ -21960,6 +21971,18 @@ sass-loader@^12.4.0:
klona "^2.0.4"
neo-async "^2.6.2"
satori@0.0.42:
version "0.0.42"
resolved "https://registry.yarnpkg.com/satori/-/satori-0.0.42.tgz#3cc4d7522e9264d1769a35f8da413811bb3b9752"
integrity sha512-ZZaNp2Ya1ACJ4Yf++e9Ccc1Rb6GcKjC9Nhcl4SQFcdM4VqCLfWHR6Iq7AdRCTcJDDuO8u/qGE2Cs+McVoqJenw==
dependencies:
"@shuding/opentype.js" "1.4.0-beta.0"
css-background-parser "^0.1.0"
css-box-shadow "1.0.0-3"
css-to-react-native "^3.0.0"
postcss-value-parser "^4.2.0"
yoga-layout-prebuilt "^1.10.0"
sax@>=0.6.0, sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@ -22843,6 +22866,11 @@ string-width@^5.0.0:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
string.prototype.codepointat@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
"string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d"
@ -25906,13 +25934,18 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
yoga-layout-prebuilt@^1.9.6:
yoga-layout-prebuilt@^1.10.0, yoga-layout-prebuilt@^1.9.6:
version "1.10.0"
resolved "https://registry.yarnpkg.com/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz#2936fbaf4b3628ee0b3e3b1df44936d6c146faa6"
integrity sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==
dependencies:
"@types/yoga-layout" "1.9.2"
yoga-wasm-web@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yoga-wasm-web/-/yoga-wasm-web-0.1.2.tgz#05d3fc9cbdfd57ac9682debb5001aca075489a39"
integrity sha512-8SkgawHcA0RUbMrnhxbaQkZDBi8rMed8pQHixkFF9w32zGhAwZ9/cOHWlpYfr6RCx42Yp3siV45/jPEkJxsk6w==
yup@^0.32.9:
version "0.32.11"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5"