diff --git a/.gitignore b/.gitignore index febe413..df2f410 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ next-env.d.ts public/rss.xml public/atom*.xml +public/images/placeholders/* diff --git a/package.json b/package.json index 760a59c..a68e0eb 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "react-player": "^2.12.0", "react-quick-pinch-zoom": "^4.9.0", "react-use": "^17.4.0", + "sharp": "^0.33.2", "slugify": "^1.6.6", "typescript": "5.0.4", "use-query-params": "^2.2.1", diff --git a/src/components/PostCard/PostCard.Cover.tsx b/src/components/PostCard/PostCard.Cover.tsx index 9dc8778..eaacfb2 100644 --- a/src/components/PostCard/PostCard.Cover.tsx +++ b/src/components/PostCard/PostCard.Cover.tsx @@ -13,11 +13,13 @@ export type PostCardCoverProps = React.ComponentProps & { imageProps: ResponsiveImageProps imageData: LPE.Image.Document playIcon?: boolean + loadDelay?: number } export const PostCardCover: FC = ({ imageProps, imageData, playIcon, + loadDelay = 0, ...props }) => { return ( @@ -25,7 +27,7 @@ export const PostCardCover: FC = ({ {...props} className={`post-card__cover-image ${props.className}`} > - + {playIcon && ( diff --git a/src/components/PostCard/PostCard.tsx b/src/components/PostCard/PostCard.tsx index ca0e042..80e51d4 100644 --- a/src/components/PostCard/PostCard.tsx +++ b/src/components/PostCard/PostCard.tsx @@ -21,6 +21,7 @@ import { PostCardLabel } from './PostCard.Label' export type PostAppearanceProps = { imageProps?: ResponsiveImageProps + loadDelay?: number } export type PostDataProps = { @@ -47,7 +48,7 @@ export type PostCardProps = CommonProps & export const PostCard = (_props: PostCardProps) => { const { - appearance: { imageProps = {} } = {}, + appearance: { imageProps = {}, loadDelay = 0 } = {}, data: { coverImage = null, date, @@ -77,6 +78,7 @@ export const PostCard = (_props: PostCardProps) => { imageProps={imageProps} imageData={coverImage} playIcon={contentType === LPE.PostTypes.Podcast} + loadDelay={loadDelay} /> ) : (
diff --git a/src/components/PostsGrid/PostsGrid.tsx b/src/components/PostsGrid/PostsGrid.tsx index bc2cb24..b3cae33 100644 --- a/src/components/PostsGrid/PostsGrid.tsx +++ b/src/components/PostsGrid/PostsGrid.tsx @@ -4,7 +4,7 @@ import { SerializedStyles, css } from '@emotion/react' import styled from '@emotion/styled' import React, { useMemo } from 'react' import { LPE } from '../../types/lpe.types' -import { chunkArray } from '../../utils/array.utils' +import { chunkArray, shuffleArray } from '../../utils/array.utils' import { lsdUtils } from '../../utils/lsd.utils' import { lcm } from '../../utils/math.utils' import { PostCard, PostCardProps } from '../PostCard' @@ -54,6 +54,10 @@ export const PostsGrid: React.FC = ({ [theme], ) + const loadingDelayEffectIndexes = useMemo(() => { + return shuffleArray(items.map((_, i) => i)) + }, [items]) + return ( = ({ postCardStyles={postCardStyles} >
- {items.map(({ post, size }) => ( + {items.map(({ post, size }, index) => (
= ({ displayYear={displayYear} displayPodcastShow={displayPodcastShow} data={PostCard.toData(post, shows)} + appearance={{ + loadDelay: loadingDelayEffectIndexes[index] * 100, + }} />
))} diff --git a/src/components/ResponsiveImage/ResponsiveImage.tsx b/src/components/ResponsiveImage/ResponsiveImage.tsx index 391767b..5658d95 100644 --- a/src/components/ResponsiveImage/ResponsiveImage.tsx +++ b/src/components/ResponsiveImage/ResponsiveImage.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled' -import Image, { ImageLoader, ImageProps } from 'next/image' +import Image, { ImageProps } from 'next/image' import React, { useState } from 'react' import { LPE } from '../../types/lpe.types' @@ -8,6 +8,7 @@ export type ResponsiveImageProps = { nextImageProps?: Partial fill?: boolean className?: string + loadDelay?: number } export type Props = { @@ -15,9 +16,6 @@ export type Props = { alt?: string } & ResponsiveImageProps -const unbodyImageLoader: ImageLoader = ({ src, width, quality }) => - `${src}?w=${width}&q=${quality || 75}&auto=format` - export const ResponsiveImage = ({ data, height, @@ -25,22 +23,22 @@ export const ResponsiveImage = ({ nextImageProps, className, children, + loadDelay = 0, }: React.PropsWithChildren) => { const [loaded, setLoaded] = useState(false) - const lazyUrl = `${data.url}?blur=200&px=16&auto=format` - const imageProps: ImageProps = { src: `${data.url}`, width: data.width, height: data.height, alt: data.alt, className: loaded ? 'loaded' : '', - onLoad: () => setLoaded(true), + onLoad: () => { + setTimeout(() => { + setLoaded(true) + }, loadDelay) + }, loading: 'lazy', - ...(data.url.startsWith('https://images.cdn.unbody.io') - ? { loader: unbodyImageLoader } - : {}), ...(nextImageProps || {}), style: { width: '100%', @@ -57,7 +55,11 @@ export const ResponsiveImage = ({ }} >
- {data.alt} + {data.alt} {children}
@@ -94,7 +96,7 @@ const Container = styled.div` &:last-of-type { opacity: 0; - transition: opacity 250ms; + transition: opacity 500ms; &.loaded { opacity: 1; diff --git a/src/configs/consts.configs.ts b/src/configs/consts.configs.ts new file mode 100644 index 0000000..5ae1992 --- /dev/null +++ b/src/configs/consts.configs.ts @@ -0,0 +1 @@ +export const POSTS_IMAGE_PLACEHOLDER_DIR = `public/images/placeholders` diff --git a/src/configs/ui.configs.ts b/src/configs/ui.configs.ts index ef067ae..c377768 100644 --- a/src/configs/ui.configs.ts +++ b/src/configs/ui.configs.ts @@ -15,4 +15,10 @@ export const uiConfigs = { numberOfImagesShowInTopResult: 3, numberOfTotalBlocksInListView: 20, }, + imageRender: { + placeholder: { + pixelation: 0.5, + blur: 0, + }, + }, } diff --git a/src/services/images.service.ts b/src/services/images.service.ts new file mode 100644 index 0000000..62cea8e --- /dev/null +++ b/src/services/images.service.ts @@ -0,0 +1,62 @@ +import { POSTS_IMAGE_PLACEHOLDER_DIR } from '@/configs/consts.configs' +import { uiConfigs } from '@/configs/ui.configs' +import { + getStrapiImageUrlBySize, + transformStrapiImageUrl, +} from '@/services/strapi/transformers/utils' +import axios from 'axios' +import sharp from 'sharp' + +export class PlaceholderService { + cache = new Map() + constructor() {} + + add(key: string, value: string) { + this.cache.set(key, value) + } + + exists(key: string): boolean { + return this.cache.has(key) + } + + emptyCache() { + this.cache.clear() + } + + async pixelate(imagePath: string): Promise { + if (imagePath.length === 0 || imagePath.endsWith('.svg')) return '' + + const fileName = imagePath.split('/').pop() as string + + if (this.exists(fileName)) return this.cache.get(fileName) as string + + const filePath = `${POSTS_IMAGE_PLACEHOLDER_DIR}/${fileName}` + const thumbnailPath = getStrapiImageUrlBySize('thumbnail', imagePath) + const imageUrl = transformStrapiImageUrl(thumbnailPath) + const imageBuffer = ( + await axios({ url: imageUrl, responseType: 'arraybuffer' }) + ).data as Buffer + + try { + await sharp( + await sharp(imageBuffer) + .resize(uiConfigs.imageRender.placeholder.pixelation * 100, null, { + kernel: sharp.kernel.cubic, + }) + .toBuffer(), + ) + .resize(uiConfigs.imageRender.placeholder.pixelation * 400, null, { + kernel: sharp.kernel.nearest, + }) + .toFile(filePath) + + this.add(fileName, filePath) + + return filePath + } catch (e) { + console.log(e) + } + + return '' + } +} diff --git a/src/services/strapi/transformers/Episode.transformer.ts b/src/services/strapi/transformers/Episode.transformer.ts index c18a5e6..816bc93 100644 --- a/src/services/strapi/transformers/Episode.transformer.ts +++ b/src/services/strapi/transformers/Episode.transformer.ts @@ -19,9 +19,9 @@ export const episodeTransformer: Transformer< transform: async (helpers, data, original, root, ctx) => { return { ...data, - credits: transformStrapiHtmlContent({ + credits: await transformStrapiHtmlContent({ html: original.attributes.credits || '', - }).blocks as LPE.Post.TextBlock[], + }).then((c) => c.blocks as LPE.Post.TextBlock[]), channels: original.attributes.channel ? await transformChannels(original.attributes.channel) : [], diff --git a/src/services/strapi/transformers/PodcastShow.transformer.ts b/src/services/strapi/transformers/PodcastShow.transformer.ts index 514b5ca..3173bcc 100644 --- a/src/services/strapi/transformers/PodcastShow.transformer.ts +++ b/src/services/strapi/transformers/PodcastShow.transformer.ts @@ -15,7 +15,7 @@ export const podcastShowTransformer: Transformer< classes: ['podcast'], objectType: 'PodcastShow', isMatch: (helpers, object) => object.__typename === 'PodcastShowEntity', - transform: (helpers, data, original, root, ctx) => { + transform: async (helpers, data, original, root, ctx) => { const { id, attributes } = data return { @@ -25,15 +25,15 @@ export const podcastShowTransformer: Transformer< numberOfEpisodes: 0, url: getPostLink('podcast', { showSlug: attributes.slug }), description: attributes.description, - descriptionText: transformStrapiHtmlContent({ + descriptionText: await transformStrapiHtmlContent({ html: attributes.description || '', - }).text, + }).then((h) => h.text), hosts: attributes.hosts.data.map((host) => ({ id: '', name: host.attributes.name, emailAddress: host.attributes.email_address, })), - logo: transformStrapiImageData(attributes.logo), + logo: await transformStrapiImageData(attributes.logo), } }, } diff --git a/src/services/strapi/transformers/Post.transformer.ts b/src/services/strapi/transformers/Post.transformer.ts index 03226b1..65f03fc 100644 --- a/src/services/strapi/transformers/Post.transformer.ts +++ b/src/services/strapi/transformers/Post.transformer.ts @@ -15,7 +15,7 @@ export const postTransformer: Transformer< classes: ['post'], objectType: 'Post', isMatch: (helpers, object) => object.__typename === 'PostEntity', - transform: (helpers, data, original, root, ctx) => { + transform: async (helpers, data, original, root, ctx) => { const { id, attributes } = data const type = attributes.type @@ -26,7 +26,7 @@ export const postTransformer: Transformer< const isHighlighted = attributes.featured const isDraft = !attributes.publishedAt const coverImage: LPE.Post.Document['coverImage'] = - transformStrapiImageData(attributes.cover_image) + await transformStrapiImageData(attributes.cover_image) const tags: LPE.Tag.Document[] = attributes.tags.data.map((tag) => ({ id: tag.id, name: tag.attributes.name, @@ -38,15 +38,15 @@ export const postTransformer: Transformer< emailAddress: author.attributes.email_address, })) - const summary = transformStrapiHtmlContent({ + const summary = await transformStrapiHtmlContent({ html: attributes.summary || '', - }).text + }).then((h) => h.text) const { blocks: content, toc, text, - } = transformStrapiHtmlContent({ + } = await transformStrapiHtmlContent({ html: attributes.body || '', }) diff --git a/src/services/strapi/transformers/StaticPage.transformer.ts b/src/services/strapi/transformers/StaticPage.transformer.ts index 204bbdf..fd2de3c 100644 --- a/src/services/strapi/transformers/StaticPage.transformer.ts +++ b/src/services/strapi/transformers/StaticPage.transformer.ts @@ -14,7 +14,7 @@ export const staticPageTransformer: Transformer< classes: ['static_page'], objectType: 'StaticPage', isMatch: (helpers, object) => object.__typename === 'PageEntity', - transform: (helpers, data, original, root, ctx) => { + transform: async (helpers, data, original, root, ctx) => { const { id, attributes } = data const title = attributes.title @@ -22,7 +22,7 @@ export const staticPageTransformer: Transformer< const slug = attributes.slug const description = attributes.description - const { blocks: content } = transformStrapiHtmlContent({ + const { blocks: content } = await transformStrapiHtmlContent({ html: attributes.body || '', }) diff --git a/src/services/strapi/transformers/utils.ts b/src/services/strapi/transformers/utils.ts index 1dc3351..2acade7 100644 --- a/src/services/strapi/transformers/utils.ts +++ b/src/services/strapi/transformers/utils.ts @@ -1,3 +1,5 @@ +import { PlaceholderService } from '@/services/images.service' +import { isVercel } from '@/utils/env.utils' import * as htmlParser from 'node-html-parser' import slugify from 'slugify' import { UploadFileEntity } from '../../../lib/strapi/strapi.generated' @@ -7,18 +9,27 @@ import { convertToIframe } from '../../../utils/string.utils' let assetsBaseUrl = process.env.NEXT_PUBLIC_ASSETS_BASE_URL ?? '' if (assetsBaseUrl.endsWith('/')) assetsBaseUrl = assetsBaseUrl.slice(1) -export const transformStrapiImageUrl = (url: string): string => - assetsBaseUrl + url +const placeholderService = new PlaceholderService() +placeholderService.emptyCache() -export const transformStrapiImageData = ( - image: - | Pick - | { - data: { - attributes: Partial - } - }, -): LPE.Image.Document => { +// TODO: remove this and move it to a proper place +type StrapiImage = + | Pick + | { + data: { + attributes: Partial + } + } + +export const getStrapiImageUrlBySize = (size: string, url: string): string => + url.replace('/uploads/', `/uploads/${size}_`) + +export const transformStrapiImageUrl = (filePath: string): string => + assetsBaseUrl + filePath + +export const transformStrapiImageData = async ( + image: StrapiImage, +): Promise => { const attributes = 'data' in image ? image.data.attributes : image.attributes return { @@ -27,19 +38,27 @@ export const transformStrapiImageData = ( caption: attributes.caption || '', alt: attributes.caption || attributes.alternativeText || '', url: attributes.url ? transformStrapiImageUrl(attributes.url) : '', + placeholder: attributes.url + ? isVercel() + ? getStrapiImageUrlBySize( + 'thumbnail', + transformStrapiImageUrl(attributes.url), + ) + : await placeholderService.pixelate(attributes.url) + : '', } } -export const transformStrapiHtmlContent = ({ +export const transformStrapiHtmlContent = async ({ html, }: { html: string -}): { +}): Promise<{ toc: LPE.Post.TocItem[] blocks: LPE.Post.ContentBlock[] html: string text: string -} => { +}> => { const toc: LPE.Post.TocItem[] = [] const blocks: LPE.Post.ContentBlock[] = [] diff --git a/src/types/lpe.types.ts b/src/types/lpe.types.ts index eb70c48..c166a80 100644 --- a/src/types/lpe.types.ts +++ b/src/types/lpe.types.ts @@ -24,6 +24,7 @@ export namespace LPE { height: number caption?: string captionHTML?: string + placeholder?: string } } diff --git a/src/utils/array.utils.ts b/src/utils/array.utils.ts index 99e347c..c5f6998 100644 --- a/src/utils/array.utils.ts +++ b/src/utils/array.utils.ts @@ -18,3 +18,17 @@ export const chunkArray = (arr: T[], ...pattern: number[]): T[][] => { return result } + +export const shuffleArray = (arr: T[]): T[] => { + const result = [...arr] + + for (let i = result.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + + const temp = result[i] + result[i] = result[j] + result[j] = temp + } + + return result +} diff --git a/yarn.lock b/yarn.lock index 0022828..0dcd1a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -658,6 +658,13 @@ tslib "^2.6.1" ws "^8.13.0" +"@emnapi/runtime@^0.45.0": + version "0.45.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-0.45.0.tgz#e754de04c683263f34fd0c7f32adfe718bbe4ddd" + integrity sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w== + dependencies: + tslib "^2.4.0" + "@emotion/babel-plugin@^11.10.6": version "11.10.6" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz#a68ee4b019d661d6f37dec4b8903255766925ead" @@ -1284,6 +1291,119 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@img/sharp-darwin-arm64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz#0a52a82c2169112794dac2c71bfba9e90f7c5bd1" + integrity sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.1" + +"@img/sharp-darwin-x64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz#982e26bb9d38a81f75915c4032539aed621d1c21" + integrity sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.1" + +"@img/sharp-libvips-darwin-arm64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz#81e83ffc2c497b3100e2f253766490f8fad479cd" + integrity sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw== + +"@img/sharp-libvips-darwin-x64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz#fc1fcd9d78a178819eefe2c1a1662067a83ab1d6" + integrity sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog== + +"@img/sharp-libvips-linux-arm64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz#26eb8c556a9b0db95f343fc444abc3effb67ebcf" + integrity sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA== + +"@img/sharp-libvips-linux-arm@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz#2a377b959ff7dd6528deee486c25461296a4fa8b" + integrity sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ== + +"@img/sharp-libvips-linux-s390x@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz#af28ac9ba929204467ecdf843330d791e9421e10" + integrity sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ== + +"@img/sharp-libvips-linux-x64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz#4273d182aa51912e655e1214ea47983d7c1f7f8d" + integrity sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz#d150c92151cea2e8d120ad168b9c358d09c77ce8" + integrity sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg== + +"@img/sharp-libvips-linuxmusl-x64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz#e297c1a4252c670d93b0f9e51fca40a7a5b6acfd" + integrity sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw== + +"@img/sharp-linux-arm64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz#af3409f801a9bee1d11d0c7e971dcd6180f80022" + integrity sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.1" + +"@img/sharp-linux-arm@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz#181f7466e6ac074042a38bfb679eb82505e17083" + integrity sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.1" + +"@img/sharp-linux-s390x@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz#9c171f49211f96fba84410b3e237b301286fa00f" + integrity sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.1" + +"@img/sharp-linux-x64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz#b956dfc092adc58c2bf0fae2077e6f01a8b2d5d7" + integrity sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.1" + +"@img/sharp-linuxmusl-arm64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz#10e0ec5a79d1234c6a71df44c9f3b0bef0bc0f15" + integrity sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.1" + +"@img/sharp-linuxmusl-x64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz#29e0030c24aa27c38201b1fc84e3d172899fcbe0" + integrity sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.1" + +"@img/sharp-wasm32@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz#38d7c740a22de83a60ad1e6bcfce17462b0d4230" + integrity sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ== + dependencies: + "@emnapi/runtime" "^0.45.0" + +"@img/sharp-win32-ia32@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz#09456314e223f68e5417c283b45c399635c16202" + integrity sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g== + +"@img/sharp-win32-x64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz#148e96dfd6e68747da41a311b9ee4559bb1b1471" + integrity sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg== + "@imgix/js-core@^3.1.3": version "3.8.0" resolved "https://registry.yarnpkg.com/@imgix/js-core/-/js-core-3.8.0.tgz#630bc4fba8cb968d8c0e8298a2be71e39d75b8b8" @@ -2328,11 +2448,27 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.1.4, color-name@~1.1.4: +color-name@^1.0.0, color-name@^1.1.4, 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" + colorette@^2.0.16, colorette@^2.0.19: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" @@ -2604,6 +2740,11 @@ detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== +detect-libc@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -3738,6 +3879,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +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-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -5220,6 +5366,13 @@ semver@^7.3.7: dependencies: lru-cache "^6.0.0" +semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + sentence-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f" @@ -5254,6 +5407,35 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== +sharp@^0.33.2: + version "0.33.2" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.2.tgz#fcd52f2c70effa8a02160b1bfd989a3de55f2dfb" + integrity sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ== + dependencies: + color "^4.2.3" + detect-libc "^2.0.2" + semver "^7.5.4" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.2" + "@img/sharp-darwin-x64" "0.33.2" + "@img/sharp-libvips-darwin-arm64" "1.0.1" + "@img/sharp-libvips-darwin-x64" "1.0.1" + "@img/sharp-libvips-linux-arm" "1.0.1" + "@img/sharp-libvips-linux-arm64" "1.0.1" + "@img/sharp-libvips-linux-s390x" "1.0.1" + "@img/sharp-libvips-linux-x64" "1.0.1" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.1" + "@img/sharp-libvips-linuxmusl-x64" "1.0.1" + "@img/sharp-linux-arm" "0.33.2" + "@img/sharp-linux-arm64" "0.33.2" + "@img/sharp-linux-s390x" "0.33.2" + "@img/sharp-linux-x64" "0.33.2" + "@img/sharp-linuxmusl-arm64" "0.33.2" + "@img/sharp-linuxmusl-x64" "0.33.2" + "@img/sharp-wasm32" "0.33.2" + "@img/sharp-win32-ia32" "0.33.2" + "@img/sharp-win32-x64" "0.33.2" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -5290,6 +5472,13 @@ signedsource@^1.0.0: resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a" integrity sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww== +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"