diff --git a/next.config.mjs b/next.config.mjs index cd0f948..70b3b31 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -7,6 +7,11 @@ const nextConfig = { hostname: "pbs.twimg.com", pathname: "**", }, + { + protocol: "https", + hostname: "images.ctfassets.net", + pathname: "**", + } ], }, }; diff --git a/package.json b/package.json index e041d31..1d767a9 100644 --- a/package.json +++ b/package.json @@ -13,20 +13,31 @@ }, "dependencies": { "@amplitude/analytics-browser": "^2.4.1", + "@contentful/rich-text-plain-text-renderer": "^16.2.8", + "@contentful/rich-text-react-renderer": "^15.22.9", + "@contentful/rich-text-types": "^16.8.3", "@headlessui/react": "^1.7.18", "@next/third-parties": "^14.2.5", "@typeform/embed-react": "^3.17.0", + "contentful": "^10.13.1", "embla-carousel-auto-scroll": "^8.1.5", "embla-carousel-autoplay": "^8.1.5", "embla-carousel-react": "^8.1.5", + "lodash.words": "^4.2.0", + "luxon": "^3.5.0", "next": "14.1.0", "numeral": "^2.0.6", "react": "^18", "react-dom": "^18", + "sharp": "^0.33.5", "tailwind-merge": "^2.2.1", + "tailwind-scrollbar-hide": "^1.1.7", + "use-debounce": "^10.0.3", "zod": "^3.22.4" }, "devDependencies": { + "@types/lodash.words": "^4.2.9", + "@types/luxon": "^3.4.2", "@types/node": "^20", "@types/numeral": "^2.0.5", "@types/react": "^18", diff --git a/src/app/(routes)/blog/[slug]/_components/article-content.tsx b/src/app/(routes)/blog/[slug]/_components/article-content.tsx new file mode 100644 index 0000000..7eb1935 --- /dev/null +++ b/src/app/(routes)/blog/[slug]/_components/article-content.tsx @@ -0,0 +1,147 @@ +import { + documentToReactComponents, + RenderMark, + RenderNode, +} from "@contentful/rich-text-react-renderer"; +import { BLOCKS, Document, INLINES, MARKS } from "@contentful/rich-text-types"; +import Link from "next/link"; +import { isExternal } from "util/types"; +import Divider from "./divider"; +import { IframeContainer } from "./iframe-container"; +import { Text } from "@/app/_components/text"; +import { Asset } from "contentful"; +import ContentfulImage from "./contentful-image"; + +// Map text-format types to custom components + +const markRenderers: RenderMark = { + [MARKS.BOLD]: (text) => {text}, + [MARKS.ITALIC]: (text) => {text}, + [MARKS.UNDERLINE]: (text) => {text}, + [MARKS.CODE]: (text) => {text}, + [MARKS.SUPERSCRIPT]: (text) => {text}, + [MARKS.SUBSCRIPT]: (text) => {text}, +}; + +const nodeRenderers: RenderNode = { + [INLINES.HYPERLINK]: (node, children) => { + const href = node.data.uri as string; + if ( + href.includes("youtube.com/embed") || + href.includes("player.vimeo.com") || + children?.toString().toLowerCase().includes("iframe") // to handle uncommon cases, creator can set the text to "iframe" + ) { + return ( + + + + ); + } + return ( + + {children} + + ); + }, + [BLOCKS.DOCUMENT]: (_, children) => children, + [BLOCKS.PARAGRAPH]: (_, children) => ( + +

{children}

+
+ ), + [BLOCKS.HEADING_1]: (_, children) => ( + +

{children}

+
+ ), + [BLOCKS.HEADING_2]: (_, children) => ( + +

{children}

+
+ ), + [BLOCKS.HEADING_3]: (_, children) => ( + +

{children}

+
+ ), + [BLOCKS.HEADING_4]: (_, children) => ( + +

{children}

+
+ ), + [BLOCKS.HEADING_5]: (_, children) => ( + +
{children}
+
+ ), + [BLOCKS.HEADING_6]: (_, children) => ( + +
{children}
+
+ ), + [BLOCKS.EMBEDDED_RESOURCE]: (_, children) =>
{children}
, + [BLOCKS.UL_LIST]: (_, children) => , + [BLOCKS.OL_LIST]: (_, children) =>
    {children}
, + [BLOCKS.LIST_ITEM]: (_, children) =>
  • {children}
  • , + [BLOCKS.QUOTE]: (_, children) =>
    {children}
    , + [BLOCKS.HR]: () => , + [BLOCKS.TABLE]: (_, children) => ( + + {children} +
    + ), + [BLOCKS.TABLE_ROW]: (_, children) => {children}, + [BLOCKS.TABLE_HEADER_CELL]: (_, children) => {children}, + [BLOCKS.TABLE_CELL]: (_, children) => {children}, + [BLOCKS.EMBEDDED_ASSET]: (node) => { + const data = node.data.target as Asset<"WITHOUT_UNRESOLVABLE_LINKS", string>; + const { file, description, title } = data.fields; + const mimeGroup = file?.contentType.split("/")[0]; // image / video etc + switch (mimeGroup) { + case "image": + return ; + // TODO: test this, make custom component if necessary + case "video": + return ( + + ); + // TODO: add other asset types, handle them + default: + return

    unknown file type

    ; + } + }, +}; + +const options = { + renderNode: nodeRenderers, + renderMark: markRenderers, + preserveWhitespace: true, +}; + +export default function ArticleContent({ content }: { content: Document }) { + return ( +
    + {documentToReactComponents(content, options)} +
    + ); +} diff --git a/src/app/(routes)/blog/[slug]/_components/breadcrumb.tsx b/src/app/(routes)/blog/[slug]/_components/breadcrumb.tsx new file mode 100644 index 0000000..8e80d8e --- /dev/null +++ b/src/app/(routes)/blog/[slug]/_components/breadcrumb.tsx @@ -0,0 +1,16 @@ +import Link from "next/link"; +import { ChevronDownIcon } from "@/app/_components/icons"; + +export default function Breadcrumb({ fullTitle }: { fullTitle: string }) { + // Max title to 40 characters + const title = fullTitle.length > 40 ? fullTitle.slice(0, 40) + "..." : fullTitle; + return ( +
    + + Blog + + +
    {title}
    +
    + ); +} diff --git a/src/app/(routes)/blog/[slug]/_components/contentful-image.tsx b/src/app/(routes)/blog/[slug]/_components/contentful-image.tsx new file mode 100644 index 0000000..9a4eef2 --- /dev/null +++ b/src/app/(routes)/blog/[slug]/_components/contentful-image.tsx @@ -0,0 +1,49 @@ +import { Asset } from "contentful"; +import Image from "next/image"; +import { object } from "zod"; + +export default function ContentfulImage({ + image, + borderless, + displayDescription, + fillDisplay, +}: { + image?: Asset<"WITHOUT_UNRESOLVABLE_LINKS", string>; + borderless?: boolean; + displayDescription?: boolean; + fillDisplay?: boolean; +}) { + if (!image) { + return null; + } + + const { file, description, title } = image.fields; + const url = file?.url; + if (!url) { + return null; + } + const urlWithProtocol = `https:${url}`; + + const classes = borderless ? "" : "rounded-3xl border border-white-translucent"; + + const props = fillDisplay + ? { fill: true, objectFit: "cover" } + : { + height: file.details.image?.height, + width: file.details.image?.width, + }; + + return ( +
    + {description + {description && displayDescription &&

    {description}

    } +
    + ); +} diff --git a/src/app/(routes)/blog/[slug]/_components/divider.tsx b/src/app/(routes)/blog/[slug]/_components/divider.tsx new file mode 100644 index 0000000..6c06851 --- /dev/null +++ b/src/app/(routes)/blog/[slug]/_components/divider.tsx @@ -0,0 +1,9 @@ +import { twMerge } from "@/app/_lib/tw-merge"; + +export default function Divider({ className }: { className?: string }) { + return ( +
    + ); +} diff --git a/src/app/(routes)/blog/[slug]/_components/iframe-container.tsx b/src/app/(routes)/blog/[slug]/_components/iframe-container.tsx new file mode 100644 index 0000000..a1e4489 --- /dev/null +++ b/src/app/(routes)/blog/[slug]/_components/iframe-container.tsx @@ -0,0 +1,13 @@ +import { twMerge } from "@/app/_lib/tw-merge"; + +type Props = { + className?: string; +}; + +export function IframeContainer({ className, children }: React.PropsWithChildren) { + return ( + + {children} + + ); +} diff --git a/src/app/(routes)/blog/[slug]/_components/meta-info.tsx b/src/app/(routes)/blog/[slug]/_components/meta-info.tsx new file mode 100644 index 0000000..1bf1a11 --- /dev/null +++ b/src/app/(routes)/blog/[slug]/_components/meta-info.tsx @@ -0,0 +1,33 @@ +import { Text } from "@/app/_components/text"; +import { getReadingTime } from "@/app/_lib/contentful"; +import { Document } from "@contentful/rich-text-types"; +import { DateTime } from "luxon"; +import { twMerge } from "tailwind-merge"; + +export function MetaInfo({ + isoCreatedDate, + content, + preventCenter, + compact, +}: { + isoCreatedDate: string; + content: Document; + preventCenter?: boolean; + compact?: boolean; +}) { + const dateString = DateTime.fromISO(isoCreatedDate).toFormat("MMM dd, yyyy"); + const minutesToRead = getReadingTime(content); + return ( +
    + {dateString}• + + {minutesToRead} min read + +
    + ); +} diff --git a/src/app/(routes)/blog/[slug]/_components/share-link.tsx b/src/app/(routes)/blog/[slug]/_components/share-link.tsx new file mode 100644 index 0000000..e192702 --- /dev/null +++ b/src/app/(routes)/blog/[slug]/_components/share-link.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { Text } from "@/app/_components/text"; +import { BlogPostType } from "@/app/_lib/contentful"; +import { TwitterIcon, LinkIcon } from "@/app/_components/icons"; +import { useEffect, useState } from "react"; +import { twMerge } from "@/app/_lib/tw-merge"; +import { useRouter } from "next/navigation"; + +function LinkButton({ + onClick, + icon: Icon, +}: { + onClick: () => void; + icon: typeof TwitterIcon; +}) { + const [copyClicked, setCopyClicked] = useState(false); + useEffect(() => { + if (copyClicked) { + setTimeout(() => { + setCopyClicked(false); + }, 1000); + } + }, [copyClicked]); + + const borderColor = copyClicked ? "border-aqua-100" : "border-white-translucent"; + const textColor = copyClicked ? "text-aqua-100" : "text-white"; + return ( + + ); +} + +export default function ShareLink({ + entry, + collapsed, +}: { + entry: BlogPostType; + collapsed?: boolean; +}) { + const url = `https://across.to/blog/${entry.fields.slug}`; + const twitterUrl = `https://twitter.com/intent/tweet?url=${url}`; + + const buttons = [ + { + icon: LinkIcon, + onClick: () => { + navigator.clipboard.writeText(url); + }, + }, + { + icon: TwitterIcon, + onClick: () => { + window.open(twitterUrl, "_blank")?.focus(); + }, + }, + ]; + + return ( +
    + Share this article +
    + {buttons.map((button, index) => ( + + ))} +
    +
    + ); +} diff --git a/src/app/(routes)/blog/[slug]/page.tsx b/src/app/(routes)/blog/[slug]/page.tsx new file mode 100644 index 0000000..8ea6e75 --- /dev/null +++ b/src/app/(routes)/blog/[slug]/page.tsx @@ -0,0 +1,134 @@ +import { + resolvePublishDateToIsoDate, + retrieveContentfulEntry, + retrieveContentfulPublishedSlugs, +} from "@/app/_lib/contentful"; +import { redirect } from "next/navigation"; +import { ReactNode } from "react"; +import { twMerge } from "tailwind-merge"; +import Divider from "./_components/divider"; +import ArticleContent from "./_components/article-content"; +import BackgroundBanner from "../_components/background-banner"; +import Breadcrumb from "./_components/breadcrumb"; +import { MetaInfo } from "./_components/meta-info"; +import BackToTopButton from "../_components/back-to-top-button"; +import ContentfulImage from "./_components/contentful-image"; +import ShareLink from "./_components/share-link"; +import ArticleSnippetCard from "../_components/article-snippet-card"; +import { Metadata } from "next"; +import { documentToPlainTextString } from "@contentful/rich-text-plain-text-renderer"; +import { SITE_BASE_URL } from "@/app/_constants/links"; + +type SpecificBlogPageProps = { params: { slug: string } }; + +export async function generateMetadata({ + params, +}: SpecificBlogPageProps): Promise { + const entry = await retrieveContentfulEntry(params.slug); + if (!entry) { + redirect("/404"); + } + const title = entry.fields.title; + const description = + documentToPlainTextString(entry.fields.content).substring(0, 50) + "..."; + const imageUrl = `https:${entry.fields.featuredImage?.fields.file?.url}`; + + return { + keywords: entry.fields.tag ?? [], + metadataBase: new URL(SITE_BASE_URL), + publisher: "Across Protocol", + alternates: { + canonical: `/blog/${params.slug}`, + }, + title, + description, + icons: { + icon: ["/favicon-32x32.png", "/favicon-16x16.png"], + }, + twitter: { + card: "summary_large_image", + site: "@AcrossProtocol", + title, + images: [imageUrl], + }, + openGraph: { + siteName: "Across Protocol", + title, + description, + images: [imageUrl], + url: `/blog/${params.slug}`, + }, + }; +} + +export async function generateStaticParams() { + // Grab all relevant slugs and pipe them into the SSG function + // so that we can generate static blog pages without needing to + // use SSR at runtime as much as possible. + const { slugsForQuery } = await retrieveContentfulPublishedSlugs(); + return slugsForQuery.map((slug) => ({ + slug, + })); +} + +const SubStack = ({ + children, + className, +}: { + children?: ReactNode; + className?: string; +}) => + children && ( +
    + {children} +
    + ); + +export default async function SpecificBlogPage({ params }: SpecificBlogPageProps) { + const entry = await retrieveContentfulEntry(params.slug); + if (!entry) { + redirect("/404"); + } + const fullTitle = entry.fields.title; + const content = entry.fields.content; + return ( + <> + +
    +
    +
    + +
    +
    + + + + +

    + {fullTitle} +

    + {/** Block for Share Links */} + +
    + +
    + +
    + + + + +
    + {entry.relevantEntries.map((entry) => ( + + ))} +
    +
    + +
    + + ); +} diff --git a/src/app/(routes)/blog/_components/article-full-card.tsx b/src/app/(routes)/blog/_components/article-full-card.tsx new file mode 100644 index 0000000..edd9e13 --- /dev/null +++ b/src/app/(routes)/blog/_components/article-full-card.tsx @@ -0,0 +1,41 @@ +import { + resolvePublishDateToIsoDate, + retrieveContentfulEntry, +} from "@/app/_lib/contentful"; +import ContentfulImage from "../[slug]/_components/contentful-image"; +import { MetaInfo } from "../[slug]/_components/meta-info"; +import { documentToPlainTextString } from "@contentful/rich-text-plain-text-renderer"; +import { Text } from "@/app/_components/text"; +import Link from "next/link"; + +export default async function ArticleFullCard({ slug }: { slug: string }) { + const article = await retrieveContentfulEntry(slug); + if (!article) { + return null; + } + const description = + article.fields.description ?? documentToPlainTextString(article.fields.content); + + return ( + +
    + + + {article.fields.title} + + + {description} + +
    +
    + +
    + + ); +} diff --git a/src/app/(routes)/blog/_components/article-snippet-card.tsx b/src/app/(routes)/blog/_components/article-snippet-card.tsx new file mode 100644 index 0000000..db2501c --- /dev/null +++ b/src/app/(routes)/blog/_components/article-snippet-card.tsx @@ -0,0 +1,31 @@ +import { BlogPostType, resolvePublishDateToIsoDate } from "@/app/_lib/contentful"; +import { Text } from "@/app/_components/text"; +import Link from "next/link"; +import ContentfulImage from "../[slug]/_components/contentful-image"; +import { MetaInfo } from "../[slug]/_components/meta-info"; + +export default function ArticleSnippetCard({ + article, +}: { + article: BlogPostType; + expandedOnDesktop?: boolean; +}) { + return ( + + +
    + + + {article.fields.title} + +
    + + ); +} diff --git a/src/app/(routes)/blog/_components/back-to-top-button.tsx b/src/app/(routes)/blog/_components/back-to-top-button.tsx new file mode 100644 index 0000000..90665c8 --- /dev/null +++ b/src/app/(routes)/blog/_components/back-to-top-button.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { Text } from "@/app/_components/text"; + +export default function BackToTopButton() { + const onClickHandler = () => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }; + return ( +
    + + Back to top + +
    + ); +} diff --git a/src/app/(routes)/blog/_components/background-banner.tsx b/src/app/(routes)/blog/_components/background-banner.tsx new file mode 100644 index 0000000..ee860b7 --- /dev/null +++ b/src/app/(routes)/blog/_components/background-banner.tsx @@ -0,0 +1,14 @@ +import Image from "next/image"; +import backgroundBanner from "@/app/_assets/blog-background.png"; + +export default function BackgroundBanner() { + return ( + background-banner + ); +} diff --git a/src/app/(routes)/blog/_components/filter.tsx b/src/app/(routes)/blog/_components/filter.tsx new file mode 100644 index 0000000..7e0c5f2 --- /dev/null +++ b/src/app/(routes)/blog/_components/filter.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { SearchIcon } from "@/app/_components/icons"; +import { useFilter } from "@/app/_hooks/useFilter"; + +export default function Filter() { + const { text, handleTextChange } = useFilter(); + + return ( +
    +
    + + handleTextChange(e.target.value)} + className="w-80 bg-transparent outline-none" + /> +
    +
    + ); +} diff --git a/src/app/(routes)/blog/_components/pagination.tsx b/src/app/(routes)/blog/_components/pagination.tsx new file mode 100644 index 0000000..4012af6 --- /dev/null +++ b/src/app/(routes)/blog/_components/pagination.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { useFilter } from "@/app/_hooks/useFilter"; +import { twMerge } from "@/app/_lib/tw-merge"; + +export default function Pagination({ + pageLength, + currentPage, + totalCount, +}: { + pageLength: number; + currentPage: number; + totalCount: number; +}) { + const { handlePageChange } = useFilter(); + + const pageChangeHandler = (page: number) => { + handlePageChange(page); + window.scrollTo({ top: 0, behavior: "smooth" }); + }; + + const totalPages = Math.ceil(totalCount / pageLength); + const hasPrevious = currentPage > 1; + const hasNext = currentPage < totalPages; + + return ( +
    + pageChangeHandler(currentPage - 1)} + /> + {Array.from({ length: totalPages }).map((_, index) => ( + pageChangeHandler(index + 1)} + /> + ))} + pageChangeHandler(currentPage + 1)} + /> +
    + ); +} + +const PageButton = ({ + text, + isDisabled, + onClick, + isAqua, +}: { + text: string; + isDisabled: boolean; + isAqua: boolean; + onClick?: () => void; +}) => ( +
    + {text} +
    +); diff --git a/src/app/(routes)/blog/_components/posts.tsx b/src/app/(routes)/blog/_components/posts.tsx new file mode 100644 index 0000000..387c268 --- /dev/null +++ b/src/app/(routes)/blog/_components/posts.tsx @@ -0,0 +1,15 @@ +import { Text } from "@/app/_components/text"; +import ArticleFullCard from "./article-full-card"; + +export async function Posts({ isSearch, slugs }: { isSearch: boolean; slugs: string[] }) { + return ( +
    + + {isSearch ? "Search results" : "Most recent articles"} + + {slugs.map((slug) => ( + + ))} +
    + ); +} diff --git a/src/app/(routes)/blog/page.tsx b/src/app/(routes)/blog/page.tsx new file mode 100644 index 0000000..2a74e91 --- /dev/null +++ b/src/app/(routes)/blog/page.tsx @@ -0,0 +1,95 @@ +import { Text } from "@/app/_components/text"; +import BackToTopButton from "./_components/back-to-top-button"; +import BackgroundBanner from "./_components/background-banner"; +import Filter from "./_components/filter"; + +import { Suspense } from "react"; +import { Posts } from "./_components/posts"; +import { createCacheKey } from "@/app/_lib/cache"; +import { Metadata } from "next"; +import { SITE_BASE_URL } from "@/app/_constants/links"; +import Pagination from "./_components/pagination"; +import { retrieveContentfulPublishedSlugs } from "@/app/_lib/contentful"; + +export type SearchParams = Record; + +type PageProps = { + searchParams: SearchParams; +}; + +export default async function BlogHomePage({ searchParams }: PageProps) { + const key = createCacheKey({ + searchParams, + }); + + const search = searchParams["search"]; + const isSearch = Boolean(!!search); + const page = Number(searchParams["page"]); + + const pageNumber = isNaN(page) || !Number.isInteger(page) || page < 1 ? 1 : page; + const pageLength = 16; + + const { slugsForQuery, totalCount } = await retrieveContentfulPublishedSlugs({ + query: search, + sortByRecent: true, + limit: pageLength, + skip: (pageNumber - 1) * pageLength, + }); + + return ( + <> + +
    + +

    Across Blog

    +
    + + + + Searching... + } + > + + + + +
    + + ); +} + +export async function generateMetadata(): Promise { + const title = "Across Blog"; + const description = + "Explore the latest in cross-chain solutions with the Across blog. Dive into tutorials, updates and announcements that help you leverage our protocol for fast and secure cross-chain transactions."; + + return { + metadataBase: new URL(SITE_BASE_URL), + publisher: "Across Protocol", + alternates: { + canonical: `/blog`, + }, + title, + description, + icons: { + icon: ["/favicon-32x32.png", "/favicon-16x16.png"], + }, + twitter: { + card: "summary_large_image", + site: "@AcrossProtocol", + title, + }, + openGraph: { + siteName: "Across Protocol", + title, + description, + }, + }; +} diff --git a/src/app/_amplitude/index.ts b/src/app/_amplitude/index.ts index 90dc5b6..1c3fbb7 100644 --- a/src/app/_amplitude/index.ts +++ b/src/app/_amplitude/index.ts @@ -177,11 +177,37 @@ export interface IdentifyProperties { WethVolumeUsd?: any; } +export interface BlogSearchProperties { + /** + * | Rule | Value | + * |---|---| + * | Enum Values | splashPage, bridgePage, poolPage, rewardsPage, transactionsPage, stakingPage, referralPage, airdropPage, 404Page, marketingHomePage, marketingBridgePage, marketingAcrossPlusPage, marketingSettlementPage, depositStatusPage, marketingBlogSpecificPage, marketingBlogHomePage | + */ + page: + | "splashPage" + | "bridgePage" + | "poolPage" + | "rewardsPage" + | "transactionsPage" + | "stakingPage" + | "referralPage" + | "airdropPage" + | "404Page" + | "marketingHomePage" + | "marketingBridgePage" + | "marketingAcrossPlusPage" + | "marketingSettlementPage" + | "depositStatusPage" + | "marketingBlogSpecificPage" + | "marketingBlogHomePage"; + search: string; +} + export interface BridgeButtonClickedProperties { /** * | Rule | Value | * |---|---| - * | Enum Values | splashPage, bridgePage, poolPage, rewardsPage, transactionsPage, stakingPage, referralPage, airdropPage, 404Page, marketingHomePage, marketingBridgePage, marketingAcrossPlusPage, marketingSettlementPage, depositStatusPage | + * | Enum Values | splashPage, bridgePage, poolPage, rewardsPage, transactionsPage, stakingPage, referralPage, airdropPage, 404Page, marketingHomePage, marketingBridgePage, marketingAcrossPlusPage, marketingSettlementPage, depositStatusPage, marketingBlogSpecificPage, marketingBlogHomePage | */ page: | "splashPage" @@ -197,7 +223,9 @@ export interface BridgeButtonClickedProperties { | "marketingBridgePage" | "marketingAcrossPlusPage" | "marketingSettlementPage" - | "depositStatusPage"; + | "depositStatusPage" + | "marketingBlogSpecificPage" + | "marketingBlogHomePage"; /** * | Rule | Value | * |---|---| @@ -224,7 +252,7 @@ export interface CtaButtonClickedProperties { /** * | Rule | Value | * |---|---| - * | Enum Values | splashPage, bridgePage, poolPage, rewardsPage, transactionsPage, stakingPage, referralPage, airdropPage, 404Page, marketingHomePage, marketingBridgePage, marketingAcrossPlusPage, marketingSettlementPage, depositStatusPage | + * | Enum Values | splashPage, bridgePage, poolPage, rewardsPage, transactionsPage, stakingPage, referralPage, airdropPage, 404Page, marketingHomePage, marketingBridgePage, marketingAcrossPlusPage, marketingSettlementPage, depositStatusPage, marketingBlogSpecificPage, marketingBlogHomePage | */ page: | "splashPage" @@ -240,7 +268,9 @@ export interface CtaButtonClickedProperties { | "marketingBridgePage" | "marketingAcrossPlusPage" | "marketingSettlementPage" - | "depositStatusPage"; + | "depositStatusPage" + | "marketingBlogSpecificPage" + | "marketingBlogHomePage"; /** * | Rule | Value | * |---|---| @@ -276,7 +306,7 @@ export interface PageViewedProperties { /** * | Rule | Value | * |---|---| - * | Enum Values | splashPage, bridgePage, poolPage, rewardsPage, transactionsPage, stakingPage, referralPage, airdropPage, 404Page, marketingHomePage, marketingBridgePage, marketingAcrossPlusPage, marketingSettlementPage, depositStatusPage | + * | Enum Values | splashPage, bridgePage, poolPage, rewardsPage, transactionsPage, stakingPage, referralPage, airdropPage, 404Page, marketingHomePage, marketingBridgePage, marketingAcrossPlusPage, marketingSettlementPage, depositStatusPage, marketingBlogSpecificPage, marketingBlogHomePage | */ page: | "splashPage" @@ -292,7 +322,9 @@ export interface PageViewedProperties { | "marketingBridgePage" | "marketingAcrossPlusPage" | "marketingSettlementPage" - | "depositStatusPage"; + | "depositStatusPage" + | "marketingBlogSpecificPage" + | "marketingBlogHomePage"; path: string; /** * Address of referee, null if no referral used @@ -320,6 +352,14 @@ export class ApplicationLoaded implements BaseEvent { event_type = "ApplicationLoaded"; } +export class BlogSearch implements BaseEvent { + event_type = "BlogSearch"; + + constructor(public event_properties: BlogSearchProperties) { + this.event_properties = event_properties; + } +} + export class BridgeButtonClicked implements BaseEvent { event_type = "BridgeButtonClicked"; @@ -478,6 +518,23 @@ export class Ampli { return this.track(new ApplicationLoaded(), options); } + /** + * BlogSearch + * + * [View in Tracking Plan](https://data.amplitude.com/risklabs/Risk%20Labs/events/main/latest/BlogSearch) + * + * Event has no description in tracking plan. + * + * @param properties The event's properties (e.g. page) + * @param options Amplitude event options. + */ + blogSearch( + properties: BlogSearchProperties, + options?: EventOptions, + ) { + return this.track(new BlogSearch(properties), options); + } + /** * BridgeButtonClicked * diff --git a/src/app/_assets/blog-background.png b/src/app/_assets/blog-background.png new file mode 100644 index 0000000..af79ab3 Binary files /dev/null and b/src/app/_assets/blog-background.png differ diff --git a/src/app/_components/footer.tsx b/src/app/_components/footer.tsx index 319b346..582eb94 100644 --- a/src/app/_components/footer.tsx +++ b/src/app/_components/footer.tsx @@ -12,6 +12,7 @@ import { DocumentIcon, DiscourseIcon, GitHubIcon, + NewspaperIcon, } from "./icons"; import { IconBox } from "./icon-box"; import { PRODUCT_LINKS, SOCIAL_LINKS, INFORMATION_LINKS } from "@/app/_constants"; @@ -69,6 +70,12 @@ const socials = [ ]; const information = [ + { + ...INFORMATION_LINKS.blog, + Icon: NewspaperIcon, + iconClassName: "h-5 w-5", + iconContainerClassName: "bg-light-100/[.05]", + }, { ...INFORMATION_LINKS.docs, Icon: DocumentIcon, @@ -123,7 +130,7 @@ function FooterBox(props: {
    {props.items.map((item) => (
    - {props.useExternalLinks ? ( + {props.useExternalLinks && !item.href.startsWith("/") ? ( diff --git a/src/app/_components/header-nav/index.tsx b/src/app/_components/header-nav/index.tsx index a648655..44b387d 100644 --- a/src/app/_components/header-nav/index.tsx +++ b/src/app/_components/header-nav/index.tsx @@ -5,7 +5,7 @@ import { usePathname } from "next/navigation"; import { useState } from "react"; import { twMerge } from "@/app/_lib/tw-merge"; -import { PRODUCT_LINKS, SOCIAL_LINKS } from "@/app/_constants"; +import { INFORMATION_LINKS, PRODUCT_LINKS, SOCIAL_LINKS } from "@/app/_constants"; import { AcrossIcon, @@ -18,6 +18,7 @@ import { TwitterIcon, MediumIcon, DiscourseIcon, + NewspaperIcon, } from "../icons"; import { Button } from "../button"; import { Text } from "../text"; @@ -80,6 +81,14 @@ const communityNavigationItems = [ iconContainerClassName: "bg-light-100/[.05]", containerClassName: "group-hover:bg-light-100/[.05]", }, + { + ...INFORMATION_LINKS.blog, + description: "Across Blog", + Icon: NewspaperIcon, + iconClassName: "h-4 w-4", + iconContainerClassName: "bg-light-100/[.05]", + containerClassName: "group-hover:bg-light-100/[.05]", + }, ]; export function HeaderNav() { @@ -109,6 +118,7 @@ export function HeaderNav() {
    Home + Blog ) { + return ( + + + + + + + + + + + + ); +} diff --git a/src/app/_components/icons/newspaper.tsx b/src/app/_components/icons/newspaper.tsx new file mode 100644 index 0000000..3479249 --- /dev/null +++ b/src/app/_components/icons/newspaper.tsx @@ -0,0 +1,38 @@ +import { SVGProps } from "react"; + +export function NewspaperIcon(props: SVGProps) { + return ( + + + + + + + ); +} diff --git a/src/app/_components/icons/search.tsx b/src/app/_components/icons/search.tsx new file mode 100644 index 0000000..2fa94ad --- /dev/null +++ b/src/app/_components/icons/search.tsx @@ -0,0 +1,22 @@ +import { SVGProps } from "react"; + +export function SearchIcon(props: SVGProps) { + return ( + + + + ); +} diff --git a/src/app/_components/link.tsx b/src/app/_components/link.tsx index be3d43a..2c44251 100644 --- a/src/app/_components/link.tsx +++ b/src/app/_components/link.tsx @@ -17,7 +17,7 @@ type CustomLinkProps = LinkProps & { */ function CustomLink({ href, preserveQueryParams, ...props }: CustomLinkProps) { const params = useSearchParams(); - if (preserveQueryParams && !href.toString().includes("?")) { + if (preserveQueryParams && params.size > 0 && !href.toString().includes("?")) { href = `${href.toString()}?${params.toString()}`; } return ; diff --git a/src/app/_components/text.tsx b/src/app/_components/text.tsx index c48811b..1420ad4 100644 --- a/src/app/_components/text.tsx +++ b/src/app/_components/text.tsx @@ -1,5 +1,5 @@ -import { ComponentProps } from "react"; import { twMerge } from "@/app/_lib/tw-merge"; +import { ComponentProps } from "react"; type TextVariant = keyof typeof textBaseClasses; @@ -9,8 +9,12 @@ type Props = ComponentProps<"div"> & { const textBaseClasses = { body: "text-md", + "cap-case-md": + "text-medium text-medium uppercase lining-nums tabular-nums tracking-wide-4", "cap-case-sm": "text-medium text-xs uppercase lining-nums tabular-nums tracking-wide-4 sm:text-sm", + "cap-case-xs": + "text-medium text-xs uppercase lining-nums tabular-nums tracking-wide-4 xs:text-xs", "cap-case": "text-medium text-xs uppercase lining-nums tabular-nums tracking-wide-4", "heading-1": "text-heading-3 font-lighter lining-nums tabular-nums tracking-tight-5 sm:text-heading-2 md:text-heading-1", @@ -20,6 +24,8 @@ const textBaseClasses = { "text-heading-3 font-lighter lining-nums tabular-nums tracking-tight-5 sm:text-heading-3", "heading-4": "text-heading-4 font-lighter lining-nums tabular-nums tracking-tight-5 sm:text-heading-4", + "heading-5": + "text-heading-5 font-lighter lining-nums tabular-nums tracking-tight-5 sm:text-heading-5", "body-nums": "text-md lining-nums tabular-nums sm:text-lg", "body-nums-sm": "text-sm lining-nums tabular-nums", }; diff --git a/src/app/_constants/amplitude.ts b/src/app/_constants/amplitude.ts index 27f0896..803a316 100644 --- a/src/app/_constants/amplitude.ts +++ b/src/app/_constants/amplitude.ts @@ -3,11 +3,14 @@ export type AMPLITUDE_PAGE = | "marketingHomePage" | "marketingBridgePage" | "marketingAcrossPlusPage" - | "marketingSettlementPage"; + | "marketingSettlementPage" + | "marketingBlogSpecificPage" + | "marketingBlogHomePage"; export const AMPLITUDE_PAGE_LOOKUP: Record = { "/": "marketingHomePage", "/across-bridge": "marketingBridgePage", "/across-plus": "marketingAcrossPlusPage", "/across-settlement": "marketingSettlementPage", + "/blog": "marketingBlogHomePage", }; diff --git a/src/app/_constants/environment.ts b/src/app/_constants/environment.ts index 59fb24d..cf9cc09 100644 --- a/src/app/_constants/environment.ts +++ b/src/app/_constants/environment.ts @@ -5,3 +5,7 @@ export const AMPLITUDE_LOGGING = process.env.NEXT_PUBLIC_AMPLITUDE_DEBUG_LOGGING === "true"; export const GIT_COMMIT_HASH = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ?? ""; export const GOOGLE_ANALYTICS_TAG_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID; + +// Server side environment variables +export const CONTENTFUL_SPACE_ID = process.env.CONTENTFUL_SPACE_ID; +export const CONTENTFUL_ACCESS_TOKEN = process.env.CONTENTFUL_ACCESS_TOKEN; diff --git a/src/app/_constants/links.ts b/src/app/_constants/links.ts index 6eac5d5..825c81f 100644 --- a/src/app/_constants/links.ts +++ b/src/app/_constants/links.ts @@ -48,6 +48,10 @@ export const INFORMATION_LINKS = { label: "Docs", href: "https://docs.across.to/v/v3-developer-docs/introduction/what-is-across", }, + blog: { + label: "Blog", + href: "/blog", + }, }; export const INTEGRATION_LINKS = { @@ -57,4 +61,6 @@ export const INTEGRATION_LINKS = { "https://docs.across.to/v/v3-developer-docs/concepts/intents-architecture-in-across", }; +export const SITE_BASE_URL = "https://across.to" as const; + export const TERMS_OF_SERVICE = "/terms-of-service"; diff --git a/src/app/_hooks/useFilter.ts b/src/app/_hooks/useFilter.ts new file mode 100644 index 0000000..2e4d737 --- /dev/null +++ b/src/app/_hooks/useFilter.ts @@ -0,0 +1,47 @@ +import { useSetQueryParams } from "./useSetQueryParams"; +import { useState, useCallback } from "react"; +import { useDebouncedCallback } from "use-debounce"; +import { ampli } from "../_amplitude"; + +export function useFilter() { + const { params, setParams, removeParams } = useSetQueryParams(["page", "search"]); + const [text, setText] = useState(params.search ?? ""); + + const debouncedSetParam = useDebouncedCallback((value: string) => { + setParams({ + search: value, + page: "1", // Reset page to 1 when searching + }); + if (!!value) { + ampli.blogSearch({ search: value, page: "marketingBlogHomePage" }); + } + }, 300); + + function handleTextChange(value: string) { + setText(value); + debouncedSetParam(value); + } + + function handlePageChange(value: number) { + setParams({ + page: String(value), + }); + } + + const clearAll = useCallback(() => { + setText(""); + removeParams(["page", "search"]); + }, [removeParams]); + + const hasParams = text || params.tag ? true : false; + + return { + text, + handleTextChange, + productParam: params.product, + tag: params.tag, + handlePageChange, + clearAll, + hasParams, + }; +} diff --git a/src/app/_hooks/useSetQueryParams.ts b/src/app/_hooks/useSetQueryParams.ts new file mode 100644 index 0000000..70714fe --- /dev/null +++ b/src/app/_hooks/useSetQueryParams.ts @@ -0,0 +1,70 @@ +"use client"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useState } from "react"; + +type QueryParams = Record; +type QueryParamKeys = (keyof QueryParams)[]; + +export function useSetQueryParams(paramKeys: QueryParamKeys) { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const [paramValues, setParamValues] = useState(() => { + const initialValues: QueryParams = {}; + paramKeys.forEach((key) => { + const fromUrl = searchParams.get(key); + Object.assign(initialValues, { + [key]: fromUrl ? decodeURIComponent(fromUrl) : undefined, + }); + }); + return initialValues; + }); + + const setParams = useCallback( + (params: QueryParams) => { + const newParams = new URLSearchParams(searchParams.toString()); + Object.keys(params).forEach((key) => { + const value = params[key]; + if (value === undefined || value === "") { + newParams.delete(key); + } else { + newParams.set(key, encodeURIComponent(value)); + } + }); + router.push(`${pathname}?${newParams.toString()}`, { + scroll: false, + }); + setParamValues((prev) => ({ ...prev, ...params })); + }, + [pathname, router, searchParams], + ); + + const removeParams = useCallback( + (keys: string[]) => { + const newParams = new URLSearchParams(searchParams.toString()); + keys.forEach((key) => { + newParams.delete(key); + }); + const queryString = newParams.toString(); + const path = queryString ? `${pathname}?${queryString}` : pathname; + router.push(path, { + scroll: false, + }); + setParamValues((prev) => { + const updatedValues = { ...prev }; + keys.forEach((key) => { + delete updatedValues[key]; + }); + return updatedValues; + }); + }, + [pathname, router, searchParams], + ); + + return { + params: paramValues, + setParams, + removeParams, + }; +} diff --git a/src/app/_lib/amplitude.ts b/src/app/_lib/amplitude.ts index 8ab13fe..a5c8238 100644 --- a/src/app/_lib/amplitude.ts +++ b/src/app/_lib/amplitude.ts @@ -39,5 +39,8 @@ export async function initializeAmplitude(setLoaded: (loaded: boolean) => void) } export function pageLookup(pathname: string) { - return AMPLITUDE_PAGE_LOOKUP[pathname] ?? "404Page"; + const isSpecificBlogPage = /^\/blog\/[a-zA-Z0-9-]+$/.test(pathname); + return isSpecificBlogPage + ? "marketingBlogSpecificPage" + : AMPLITUDE_PAGE_LOOKUP[pathname] ?? "404Page"; } diff --git a/src/app/_lib/cache.ts b/src/app/_lib/cache.ts new file mode 100644 index 0000000..548de9d --- /dev/null +++ b/src/app/_lib/cache.ts @@ -0,0 +1,13 @@ +import { SearchParams } from "../(routes)/blog/page"; + +export function createCacheKey(options: { searchParams: SearchParams }) { + const { searchParams } = options; + const newParamString = new URLSearchParams(); + Object.entries(searchParams).forEach(([key, value]) => { + if (typeof value === "string") { + newParamString.set(key, value); + } + }); + + return newParamString.toString(); +} diff --git a/src/app/_lib/contentful.ts b/src/app/_lib/contentful.ts new file mode 100644 index 0000000..7bb3275 --- /dev/null +++ b/src/app/_lib/contentful.ts @@ -0,0 +1,141 @@ +import type { + ChainModifiers, + Entry, + EntryFieldTypes, + EntrySkeletonType, + LocaleCode, +} from "contentful"; +import { createClient } from "contentful"; +import { CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_SPACE_ID } from "../_constants"; +import { documentToPlainTextString } from "@contentful/rich-text-plain-text-renderer"; +import { Document } from "@contentful/rich-text-types"; +import words from "lodash.words"; + +const contentType = "acrossBlogPost"; +const averageReadingSpeed = 238; // words per minute + +type TypeAcrossBlogPostFields = { + title: EntryFieldTypes.Symbol; + slug: EntryFieldTypes.Symbol; + content: EntryFieldTypes.RichText; + tag: EntryFieldTypes.Array; + featuredImage: EntryFieldTypes.AssetLink; + publishDate: EntryFieldTypes.Date; + description: EntryFieldTypes.Symbol; +}; + +type TypeAcrossBlogPostSkeleton = EntrySkeletonType< + TypeAcrossBlogPostFields, + "acrossBlogPost" +>; +export type BlogPostType = Entry< + TypeAcrossBlogPostSkeleton, + "WITHOUT_UNRESOLVABLE_LINKS", + string +>; + +export type BlogPostWithRelevantEntries = BlogPostType & { + relevantEntries: BlogPostType[]; +}; + +function getProductionClient() { + return createClient({ + space: CONTENTFUL_SPACE_ID ?? "", + accessToken: CONTENTFUL_ACCESS_TOKEN ?? "", + }); +} + +export async function retrieveContentfulPublishedSlugs({ + query, + limit, + avoidTags, + includeTags, + sortByRecent, + skip, +}: { + query?: string; + limit?: number; + avoidTags?: string[]; + includeTags?: string[]; + sortByRecent?: boolean; + skip?: number; +} = {}): Promise<{ slugsForQuery: string[]; totalCount: number }> { + const client = getProductionClient(); + const options = { + content_type: contentType, + select: "fields.slug", + "fields.content[exists]": true, + "fields.slug[exists]": true, + ...(limit ? { limit } : {}), + ...(query ? { query: decodeURI(query) } : {}), + ...(avoidTags ? { "fields.tag[nin]": avoidTags.join(",").toLowerCase() } : {}), + ...(includeTags ? { "fields.tag[in]": includeTags.join(",").toLowerCase() } : {}), + ...(sortByRecent ? { order: "-fields.publishDate" } : {}), + ...(skip ? { skip } : {}), + } as const; + const entries = + await client.withoutUnresolvableLinks.getEntries(options); + + return { + slugsForQuery: entries.items.map((item) => item.fields.slug), + totalCount: entries.total, + }; +} + +export async function retrieveContentfulEntry( + entrySlugId: string, + relevantEntryCount = 4, +): Promise { + const client = getProductionClient(); + const options = { + content_type: contentType, + limit: 1, + "fields.slug": entrySlugId, + } as const; + const entries = + await client.withoutUnresolvableLinks.getEntries(options); + const entry = entries.items[0]; + if (!entry) { + return undefined; + } + const relevantEntries = await retrieveRelevantContentfulEntries( + entrySlugId, + entry.fields.tag ?? [], + relevantEntryCount, + ); + return { + ...entry, + relevantEntries, + }; +} + +export async function retrieveRelevantContentfulEntries( + entrySlugId: string, + tags: string[], + limit: number, +): Promise { + const client = getProductionClient(); + const options = { + content_type: contentType, + limit, + "fields.content[exists]": true, // no empty posts + "fields.tag[in]": tags.join(",").toLowerCase(), // get posts with same tags + "fields.slug[nin]": entrySlugId, // don't include current post + "fields.slug[exists]": true, // no empty slugs + "fields.publishDate[exists]": true, // no empty dates + order: "-fields.publishDate", // sorted latest first + } as const; + const entries = + await client.withoutUnresolvableLinks.getEntries(options); + return entries.items; +} + +export function getReadingTime(content: Document): number { + const rawText = documentToPlainTextString(content); + const wordCount = words(rawText).length; + return Math.round(wordCount / averageReadingSpeed); +} + +export function resolvePublishDateToIsoDate(entry: BlogPostType): string { + return entry.fields.publishDate ?? entry.sys.createdAt; +} diff --git a/tailwind.config.ts b/tailwind.config.ts index cc2a4bc..a8d4870 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -9,6 +9,7 @@ const config: Config = { theme: { colors: { transparent: "transparent", + "white-translucent": "#ffffff08", light: { 100: "#fff", 200: "#F0FFFB", @@ -46,6 +47,7 @@ const config: Config = { "heading-2": ["3rem", "3.3rem"], // [48px, 52.8px] "heading-3": ["2rem", "2.2rem"], // [32px, 35.2px] "heading-4": ["1.5rem", "1.65rem"], // [24px, 26.4px] + "heading-5": ["1.25rem", "1.375rem"], // [20px, 22px] }, letterSpacing: { "tight-1": "-0.28rem", // -4.48px @@ -117,6 +119,6 @@ const config: Config = { }, }, }, - plugins: [], + plugins: [require("tailwind-scrollbar-hide")], }; export default config; diff --git a/yarn.lock b/yarn.lock index ee5ff4c..8d37d10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -78,6 +78,40 @@ dependencies: regenerator-runtime "^0.14.0" +"@contentful/content-source-maps@^0.6.0": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@contentful/content-source-maps/-/content-source-maps-0.6.1.tgz#a12828d287bdcd9f31c132f2a230bd1ac3005b77" + integrity sha512-IjsyhakG17OC5xtIa5agVRsnjjxiJf9HZjpDIV6Ix036Pd5YHJrbyyiF4KNEmLlIqe3hQ0kHGWEs9S/HfECmRQ== + dependencies: + "@vercel/stega" "^0.1.2" + json-pointer "^0.6.2" + +"@contentful/rich-text-plain-text-renderer@^16.2.8": + version "16.2.8" + resolved "https://registry.yarnpkg.com/@contentful/rich-text-plain-text-renderer/-/rich-text-plain-text-renderer-16.2.8.tgz#5ba85e7a12ab706eccfeb3de551b0467c4332403" + integrity sha512-5YQPg1rERTTla1XpQqvLYUtmyz7WPZnNX6o66SYQ7kF8FSjK4HKAVJEpzs8qMlyQCKdlzXUVbu9ItrlStORh1g== + dependencies: + "@contentful/rich-text-types" "^16.8.3" + +"@contentful/rich-text-react-renderer@^15.22.9": + version "15.22.9" + resolved "https://registry.yarnpkg.com/@contentful/rich-text-react-renderer/-/rich-text-react-renderer-15.22.9.tgz#aee71e5d0c923038041c5f4000e752864ce56aee" + integrity sha512-ubzuQKaIwB7AeUi1zHQ6EMvpLOMJ9p5LGxZznkcHG1Sn91LNZorQXfMt9K4tSoOR9Cn8KxN6ca8gMNNh5zGbig== + dependencies: + "@contentful/rich-text-types" "^16.8.3" + +"@contentful/rich-text-types@^16.0.2", "@contentful/rich-text-types@^16.8.3": + version "16.8.3" + resolved "https://registry.yarnpkg.com/@contentful/rich-text-types/-/rich-text-types-16.8.3.tgz#f0effea5d0ed23f9abfd992c9b1be8a00f4d63e9" + integrity sha512-vXwXDQMDbqITCWfTkU5R/q+uvXWCc1eYNvdZyjtrs0YDIYr4L7QJ2s1r4ZheIs3iVf3AFucKIHgDSpwCAm2wKA== + +"@emnapi/runtime@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.2.0.tgz#71d018546c3a91f3b51106530edbc056b9f2f2e3" + integrity sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ== + dependencies: + tslib "^2.4.0" + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -137,6 +171,119 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@img/sharp-darwin-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.4" + +"@img/sharp-darwin-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.4" + +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== + +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== + +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== + +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== + +"@img/sharp-libvips-linux-s390x@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" + integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== + +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" + integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== + +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + +"@img/sharp-linux-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.4" + +"@img/sharp-linux-arm@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.5" + +"@img/sharp-linux-s390x@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" + integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.4" + +"@img/sharp-linux-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-linuxmusl-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" + integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + +"@img/sharp-linuxmusl-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + +"@img/sharp-wasm32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" + integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== + dependencies: + "@emnapi/runtime" "^1.2.0" + +"@img/sharp-win32-ia32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" + integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== + +"@img/sharp-win32-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -313,6 +460,23 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash.words@^4.2.9": + version "4.2.9" + resolved "https://registry.yarnpkg.com/@types/lodash.words/-/lodash.words-4.2.9.tgz#06f10d82241371186dd7219b29f708c451f55182" + integrity sha512-t7jYEhzm/JJxYZzU4BNeySF9zXOQwtd5t1pJBcK9ZZf62HUbcFuiLN69G5N9A/QUmE44OsBVrmZcAxbnWm0IXg== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.17.7" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.7.tgz#2f776bcb53adc9e13b2c0dfd493dfcbd7de43612" + integrity sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA== + +"@types/luxon@^3.4.2": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7" + integrity sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA== + "@types/node@^20": version "20.11.16" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.16.tgz#4411f79411514eb8e2926f036c86c9f0e4ec6708" @@ -402,6 +566,11 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vercel/stega@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@vercel/stega/-/stega-0.1.2.tgz#0c20c5c9419c4288b1de58a64b5f9f26c763b25f" + integrity sha512-P7mafQXjkrsoyTRppnt0N21udKS9wUmLXHRyP9saLXLHw32j/FgUJ3FscSWgvSqRs4cj7wKZtwqJEvWJ2jbGmA== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -565,6 +734,11 @@ asynciterator.prototype@^1.0.0: dependencies: has-symbols "^1.0.3" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + autoprefixer@^10.0.1: version "10.4.17" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.17.tgz#35cd5695cbbe82f536a50fa025d561b01fdec8be" @@ -587,6 +761,15 @@ axe-core@=4.7.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== +axios@~1.6.8: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -652,6 +835,17 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5: get-intrinsic "^1.2.1" set-function-length "^1.1.1" +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -702,11 +896,34 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -717,6 +934,37 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +contentful-resolve-response@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/contentful-resolve-response/-/contentful-resolve-response-1.9.0.tgz#7a89e6f332d32b98c6e98af9406ce7aa64707711" + integrity sha512-LtgPx/eREpHXOX82od48zFZbFhXzYw/NfUoYK4Qf1OaKpLzmYPE4cAY4aD+rxVgnMM5JN/mQaPCsofUlJRYEUA== + dependencies: + fast-copy "^2.1.7" + +contentful-sdk-core@^8.1.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/contentful-sdk-core/-/contentful-sdk-core-8.3.1.tgz#773d59f286d1bd8e50a3ffd9e3f4f410da6fb929" + integrity sha512-HYy4ecFA76ERxz7P0jW7hgDcL8jH+bRckv2QfAwQ4k1yPP9TvxpZwrKnlLM69JOStxVkCXP37HvbjbFnjcoWdg== + dependencies: + fast-copy "^2.1.7" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + p-throttle "^4.1.1" + qs "^6.11.2" + +contentful@^10.13.1: + version "10.13.1" + resolved "https://registry.yarnpkg.com/contentful/-/contentful-10.13.1.tgz#8b14ea44f2816e7a109894145ae6fd161b3a26c6" + integrity sha512-GczP0vSWzZGKllzUOsUnCHTKuW92E9oR44uGrLdA7cpel75gJqSB2KpVBV1KT4oaUQn773MAU/J6tWNqCYadfw== + dependencies: + "@contentful/content-source-maps" "^0.6.0" + "@contentful/rich-text-types" "^16.0.2" + axios "~1.6.8" + contentful-resolve-response "^1.9.0" + contentful-sdk-core "^8.1.0" + json-stringify-safe "^5.0.1" + type-fest "^4.0.0" + cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -769,6 +1017,15 @@ define-data-property@^1.0.1, define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" @@ -778,11 +1035,21 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +detect-libc@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" @@ -915,6 +1182,18 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + es-iterator-helpers@^1.0.12, es-iterator-helpers@^1.0.15: version "1.0.15" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" @@ -1181,6 +1460,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +fast-copy@^2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-2.1.7.tgz#affc9475cb4b555fb488572b2a44231d0c9fa39e" + integrity sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1250,6 +1534,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -1257,6 +1546,11 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreach@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.6.tgz#87bcc8a1a0e74000ff2bf9802110708cfb02eb6e" + integrity sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg== + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" @@ -1265,6 +1559,15 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fraction.js@^4.3.7: version "4.3.7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" @@ -1310,6 +1613,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -1422,6 +1736,13 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.1: dependencies: get-intrinsic "^1.2.2" +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + has-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" @@ -1495,6 +1816,11 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: get-intrinsic "^1.2.0" is-typed-array "^1.1.10" +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-async-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" @@ -1714,6 +2040,13 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== +json-pointer@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.2.tgz#f97bd7550be5e9ea901f8c9264c9d436a22a93cd" + integrity sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw== + dependencies: + foreach "^2.0.4" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -1724,6 +2057,11 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -1790,11 +2128,26 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.words@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-4.2.0.tgz#5ecfeaf8ecf8acaa8e0c8386295f1993c9cf4036" + integrity sha512-mXxqd8Yx9BGPij3lZKFSdOsjOTbL4krbCCp9slEozaN4EMppA2dFmK/f8HeohodprY6W0vOdiQ5WFgPaTI75xQ== + loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -1814,6 +2167,11 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== +luxon@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20" + integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1827,6 +2185,18 @@ micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + minimatch@9.0.3, minimatch@^9.0.1: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" @@ -2031,6 +2401,11 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/p-throttle/-/p-throttle-4.1.1.tgz#80b1fbd358af40a8bfa1667f9dc8b72b714ad692" + integrity sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -2177,11 +2552,23 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +qs@^6.11.2: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -2332,6 +2719,11 @@ semver@^7.5.4: dependencies: lru-cache "^6.0.0" +semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + set-function-length@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.0.tgz#2f81dc6c16c7059bda5ab7c82c11f03a515ed8e1" @@ -2343,6 +2735,18 @@ set-function-length@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.1" +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + 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" + set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -2352,6 +2756,35 @@ set-function-name@^2.0.0, set-function-name@^2.0.1: functions-have-names "^1.2.3" has-property-descriptors "^1.0.0" +sharp@^0.33.5: + version "0.33.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e" + integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== + dependencies: + color "^4.2.3" + detect-libc "^2.0.3" + semver "^7.6.3" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.5" + "@img/sharp-darwin-x64" "0.33.5" + "@img/sharp-libvips-darwin-arm64" "1.0.4" + "@img/sharp-libvips-darwin-x64" "1.0.4" + "@img/sharp-libvips-linux-arm" "1.0.5" + "@img/sharp-libvips-linux-arm64" "1.0.4" + "@img/sharp-libvips-linux-s390x" "1.0.4" + "@img/sharp-libvips-linux-x64" "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + "@img/sharp-linux-arm" "0.33.5" + "@img/sharp-linux-arm64" "0.33.5" + "@img/sharp-linux-s390x" "0.33.5" + "@img/sharp-linux-x64" "0.33.5" + "@img/sharp-linuxmusl-arm64" "0.33.5" + "@img/sharp-linuxmusl-x64" "0.33.5" + "@img/sharp-wasm32" "0.33.5" + "@img/sharp-win32-ia32" "0.33.5" + "@img/sharp-win32-x64" "0.33.5" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2373,11 +2806,28 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -2393,7 +2843,16 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2453,7 +2912,14 @@ string.prototype.trimstart@^1.0.7: define-properties "^1.2.0" es-abstract "^1.22.1" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -2516,6 +2982,11 @@ tailwind-merge@^2.2.1: dependencies: "@babel/runtime" "^7.23.7" +tailwind-scrollbar-hide@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/tailwind-scrollbar-hide/-/tailwind-scrollbar-hide-1.1.7.tgz#90b481fb2e204030e3919427416650c54f56f847" + integrity sha512-X324n9OtpTmOMqEgDUEA/RgLrNfBF/jwJdctaPZDzB3mppxJk7TLIDmOreEDm1Bq4R9LSPu4Epf8VSdovNU+iA== + tailwindcss@^3.3.0: version "3.4.1" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" @@ -2617,6 +3088,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^4.0.0: + version "4.23.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.23.0.tgz#8196561a6b835175473be744f3e41e2dece1496b" + integrity sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w== + typed-array-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" @@ -2691,6 +3167,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-debounce@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-10.0.3.tgz#636094a37f7aa2bcc77b26b961481a0b571bf7ea" + integrity sha512-DxQSI9ZKso689WM1mjgGU3ozcxU1TJElBJ3X6S4SMzMNcm2lVH0AHmyXB+K7ewjz2BSUKJTDqTcwtSMRfB89dg== + util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"