mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
media grids
This commit is contained in:
parent
42402eb5c7
commit
e7981539e6
16 changed files with 136 additions and 112 deletions
|
@ -42,6 +42,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/line-clamp": "^0.4.2",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/node": "^17.0.15",
|
"@types/node": "^17.0.15",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function SearchBarInput(props: SearchBarProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col rounded-[28px] bg-denim-300 transition-colors focus-within:bg-denim-400 hover:bg-denim-400 sm:flex-row sm:items-center">
|
<div className="relative flex flex-col rounded-[28px] bg-denim-400 transition-colors focus-within:bg-denim-400 hover:bg-denim-500 sm:flex-row sm:items-center">
|
||||||
<div className="pointer-events-none absolute left-5 top-0 bottom-0 flex max-h-14 items-center">
|
<div className="pointer-events-none absolute left-5 top-0 bottom-0 flex max-h-14 items-center">
|
||||||
<Icon icon={Icons.SEARCH} />
|
<Icon icon={Icons.SEARCH} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,11 +6,7 @@ import React, {
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
|
||||||
import {
|
import { BackdropContainer, useBackdrop } from "@/components/layout/Backdrop";
|
||||||
Backdrop,
|
|
||||||
BackdropContainer,
|
|
||||||
useBackdrop,
|
|
||||||
} from "@/components/layout/Backdrop";
|
|
||||||
import { ButtonControlProps, ButtonControl } from "./ButtonControl";
|
import { ButtonControlProps, ButtonControl } from "./ButtonControl";
|
||||||
|
|
||||||
export interface OptionItem {
|
export interface OptionItem {
|
||||||
|
|
|
@ -12,9 +12,9 @@ export function IconPatch(props: IconPatchProps) {
|
||||||
return (
|
return (
|
||||||
<div className={props.className || undefined} onClick={props.onClick}>
|
<div className={props.className || undefined} onClick={props.onClick}>
|
||||||
<div
|
<div
|
||||||
className={`flex h-12 w-12 items-center justify-center rounded-full border-2 border-transparent bg-denim-300 transition-[color,transform,border-color] duration-75 ${
|
className={`flex h-12 w-12 items-center justify-center rounded-full border-2 border-transparent bg-denim-500 transition-[color,transform,border-color] duration-75 ${
|
||||||
props.clickable
|
props.clickable
|
||||||
? "cursor-pointer hover:scale-110 hover:bg-denim-400 hover:text-white active:scale-125"
|
? "cursor-pointer hover:scale-110 hover:bg-denim-600 hover:text-white active:scale-125"
|
||||||
: ""
|
: ""
|
||||||
} ${props.active ? "border-bink-600 bg-bink-100 text-bink-600" : ""}`}
|
} ${props.active ? "border-bink-600 bg-bink-100 text-bink-600" : ""}`}
|
||||||
>
|
>
|
||||||
|
|
|
@ -40,7 +40,7 @@ export function useBackdrop(): [
|
||||||
return [setBackdrop, backdropProps, highlightedProps];
|
return [setBackdrop, backdropProps, highlightedProps];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Backdrop(props: BackdropProps) {
|
function Backdrop(props: BackdropProps) {
|
||||||
const clickEvent = props.onClick || (() => {});
|
const clickEvent = props.onClick || (() => {});
|
||||||
const animationEvent = props.onBackdropHide || (() => {});
|
const animationEvent = props.onBackdropHide || (() => {});
|
||||||
const [isVisible, setVisible, fadeProps] = useFade();
|
const [isVisible, setVisible, fadeProps] = useFade();
|
||||||
|
@ -59,7 +59,7 @@ export function Backdrop(props: BackdropProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`fixed left-0 right-0 top-0 h-screen w-screen bg-black bg-opacity-50 opacity-100 transition-opacity ${
|
className={`pointer-events-auto fixed left-0 right-0 top-0 h-screen w-screen bg-black bg-opacity-50 opacity-100 transition-opacity ${
|
||||||
!isVisible ? "opacity-0" : ""
|
!isVisible ? "opacity-0" : ""
|
||||||
}`}
|
}`}
|
||||||
{...fadeProps}
|
{...fadeProps}
|
||||||
|
@ -99,9 +99,9 @@ export function BackdropContainer(
|
||||||
return (
|
return (
|
||||||
<div ref={root}>
|
<div ref={root}>
|
||||||
{createPortal(
|
{createPortal(
|
||||||
<div className="absolute top-0 left-0 z-[999]">
|
<div className="pointer-events-none fixed top-0 left-0 z-[999]">
|
||||||
<Backdrop active={props.active} {...props} />
|
<Backdrop active={props.active} {...props} />
|
||||||
<div ref={copy} className="absolute">
|
<div ref={copy} className="pointer-events-auto absolute">
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
|
|
|
@ -6,13 +6,14 @@ export function BrandPill(props: { clickable?: boolean }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center space-x-2 rounded-full bg-bink-100 bg-opacity-50 px-4 py-2 text-bink-600 ${props.clickable
|
className={`flex items-center space-x-2 rounded-full bg-bink-300 bg-opacity-50 px-4 py-2 text-bink-600 ${
|
||||||
? "transition-[transform,background-color] hover:scale-105 hover:bg-bink-200 hover:text-bink-700 active:scale-95"
|
props.clickable
|
||||||
|
? "transition-[transform,background-color] hover:scale-105 hover:bg-bink-400 hover:text-bink-700 active:scale-95"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon className="text-xl" icon={Icons.MOVIE_WEB} />
|
<Icon className="text-xl" icon={Icons.MOVIE_WEB} />
|
||||||
<span className="font-semibold text-white">{t('global.name')}</span>
|
<span className="font-semibold text-white">{t("global.name")}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
18
src/components/layout/WideContainer.tsx
Normal file
18
src/components/layout/WideContainer.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
interface WideContainerProps {
|
||||||
|
classNames?: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WideContainer(props: WideContainerProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`mx-auto w-[700px] max-w-full px-8 sm:px-4 ${
|
||||||
|
props.classNames || ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,23 +5,20 @@ import {
|
||||||
MWMediaMeta,
|
MWMediaMeta,
|
||||||
MWMediaType,
|
MWMediaType,
|
||||||
} from "@/providers";
|
} from "@/providers";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
|
||||||
import { serializePortableMedia } from "@/hooks/usePortableMedia";
|
import { serializePortableMedia } from "@/hooks/usePortableMedia";
|
||||||
import { DotList } from "@/components/text/DotList";
|
import { DotList } from "@/components/text/DotList";
|
||||||
|
|
||||||
export interface MediaCardProps {
|
export interface MediaCardProps {
|
||||||
media: MWMediaMeta;
|
media: MWMediaMeta;
|
||||||
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
watchedPercentage: number;
|
watchedPercentage: number;
|
||||||
linkable?: boolean;
|
linkable?: boolean;
|
||||||
series?: boolean;
|
series?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MediaCardContent({
|
// TODO add progress back
|
||||||
media,
|
|
||||||
linkable,
|
function MediaCardContent({ media, series, linkable }: MediaCardProps) {
|
||||||
watchedPercentage,
|
|
||||||
series,
|
|
||||||
}: MediaCardProps) {
|
|
||||||
const provider = getProviderFromId(media.providerId);
|
const provider = getProviderFromId(media.providerId);
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
|
@ -29,52 +26,31 @@ function MediaCardContent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<div
|
||||||
className={`group relative mb-4 flex overflow-hidden rounded bg-denim-300 py-4 px-5 ${
|
className={`group -m-3 mb-2 rounded-xl bg-denim-300 bg-opacity-0 transition-colors duration-100 ${
|
||||||
linkable ? "hover:bg-denim-400" : ""
|
linkable ? "hover:bg-opacity-100" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* progress background */}
|
<article
|
||||||
{watchedPercentage > 0 ? (
|
className={`relative mb-2 p-3 transition-transform duration-100 ${
|
||||||
<div className="absolute top-0 left-0 right-0 bottom-0">
|
linkable ? "group-hover:scale-95" : ""
|
||||||
<div
|
}`}
|
||||||
className="relative h-full bg-bink-300 bg-opacity-30"
|
>
|
||||||
style={{
|
<div className="mb-4 aspect-[2/3] w-full rounded-xl bg-denim-500" />
|
||||||
width: `${watchedPercentage}%`,
|
<h1 className="mb-1 max-h-[4.5rem] text-ellipsis break-words font-bold text-white line-clamp-3">
|
||||||
}}
|
<span>{media.title}</span>
|
||||||
>
|
{series && media.seasonId && media.episodeId ? (
|
||||||
<div className="absolute right-0 top-0 bottom-0 ml-auto w-40 bg-gradient-to-l from-bink-400 to-transparent opacity-40" />
|
<span className="ml-2 text-xs text-denim-700">
|
||||||
</div>
|
S{media.seasonId} E{media.episodeId}
|
||||||
</div>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
</h1>
|
||||||
<div className="relative flex flex-1">
|
<DotList
|
||||||
{/* card content */}
|
className="text-xs"
|
||||||
<div className="flex-1">
|
content={[provider.displayName, media.mediaType, media.year]}
|
||||||
<h1 className="mb-1 font-bold text-white">
|
/>
|
||||||
{media.title}
|
</article>
|
||||||
{series && media.seasonId && media.episodeId ? (
|
</div>
|
||||||
<span className="ml-2 text-xs text-denim-700">
|
|
||||||
S{media.seasonId} E{media.episodeId}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</h1>
|
|
||||||
<DotList
|
|
||||||
className="text-xs"
|
|
||||||
content={[provider.displayName, media.mediaType, media.year]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* hoverable chevron */}
|
|
||||||
<div
|
|
||||||
className={`flex translate-x-3 items-center justify-end text-xl text-white opacity-0 transition-[opacity,transform] ${
|
|
||||||
linkable ? "group-hover:translate-x-0 group-hover:opacity-100" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Icon icon={Icons.CHEVRON_RIGHT} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
src/components/media/MediaGrid.tsx
Normal file
11
src/components/media/MediaGrid.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
interface MediaGridProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MediaGrid(props: MediaGridProps) {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-2 gap-6 sm:grid-cols-3">
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,10 +1,15 @@
|
||||||
export interface TitleProps {
|
export interface TitleProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Title(props: TitleProps) {
|
export function Title(props: TitleProps) {
|
||||||
return (
|
return (
|
||||||
<h1 className="mx-auto max-w-xs text-2xl font-bold text-white sm:text-3xl md:text-4xl">
|
<h1
|
||||||
|
className={`text-2xl font-bold text-white sm:text-3xl md:text-4xl ${
|
||||||
|
props.className ?? ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
import { SectionHeading } from "@/components/layout/SectionHeading";
|
import { SectionHeading } from "@/components/layout/SectionHeading";
|
||||||
|
import { MediaGrid } from "@/components/media/MediaGrid";
|
||||||
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
||||||
import {
|
import {
|
||||||
getIfBookmarkedFromPortable,
|
getIfBookmarkedFromPortable,
|
||||||
|
@ -20,9 +21,14 @@ function Bookmarks() {
|
||||||
title={t("search.bookmarks") || "Bookmarks"}
|
title={t("search.bookmarks") || "Bookmarks"}
|
||||||
icon={Icons.BOOKMARK}
|
icon={Icons.BOOKMARK}
|
||||||
>
|
>
|
||||||
{bookmarks.map((v) => (
|
<MediaGrid>
|
||||||
<WatchedMediaCard key={[v.mediaId, v.providerId].join("|")} media={v} />
|
{bookmarks.map((v) => (
|
||||||
))}
|
<WatchedMediaCard
|
||||||
|
key={[v.mediaId, v.providerId].join("|")}
|
||||||
|
media={v}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</MediaGrid>
|
||||||
</SectionHeading>
|
</SectionHeading>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -44,13 +50,15 @@ function Watched() {
|
||||||
title={t("search.continueWatching") || "Continue Watching"}
|
title={t("search.continueWatching") || "Continue Watching"}
|
||||||
icon={Icons.CLOCK}
|
icon={Icons.CLOCK}
|
||||||
>
|
>
|
||||||
{watchedItems.map((v) => (
|
<MediaGrid>
|
||||||
<WatchedMediaCard
|
{watchedItems.map((v) => (
|
||||||
key={[v.mediaId, v.providerId].join("|")}
|
<WatchedMediaCard
|
||||||
media={v}
|
key={[v.mediaId, v.providerId].join("|")}
|
||||||
series
|
media={v}
|
||||||
/>
|
series
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
</MediaGrid>
|
||||||
</SectionHeading>
|
</SectionHeading>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ export function SearchLoadingView() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Loading
|
<Loading
|
||||||
className="my-24"
|
className="mt-40"
|
||||||
text={t("search.loading") || "Fetching your favourite shows..."}
|
text={t("search.loading") || "Fetching your favourite shows..."}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { IconPatch } from "@/components/buttons/IconPatch";
|
import { IconPatch } from "@/components/buttons/IconPatch";
|
||||||
import { Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
import { SectionHeading } from "@/components/layout/SectionHeading";
|
import { SectionHeading } from "@/components/layout/SectionHeading";
|
||||||
|
import { MediaGrid } from "@/components/media/MediaGrid";
|
||||||
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
||||||
import { useLoading } from "@/hooks/useLoading";
|
import { useLoading } from "@/hooks/useLoading";
|
||||||
import { MWMassProviderOutput, MWQuery, SearchProviders } from "@/providers";
|
import { MWMassProviderOutput, MWQuery, SearchProviders } from "@/providers";
|
||||||
|
@ -19,7 +20,7 @@ function SearchSuffix(props: {
|
||||||
const icon: Icons = allFailed ? Icons.WARNING : Icons.EYE_SLASH;
|
const icon: Icons = allFailed ? Icons.WARNING : Icons.EYE_SLASH;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="my-24 flex flex-col items-center justify-center space-y-3 text-center">
|
<div className="mt-40 flex flex-col items-center justify-center space-y-3 text-center">
|
||||||
<IconPatch
|
<IconPatch
|
||||||
icon={icon}
|
icon={icon}
|
||||||
className={`text-xl ${allFailed ? "text-red-400" : "text-bink-600"}`}
|
className={`text-xl ${allFailed ? "text-red-400" : "text-bink-600"}`}
|
||||||
|
@ -83,12 +84,14 @@ export function SearchResultsView({ searchQuery }: { searchQuery: MWQuery }) {
|
||||||
title={t("search.headingTitle") || "Search results"}
|
title={t("search.headingTitle") || "Search results"}
|
||||||
icon={Icons.SEARCH}
|
icon={Icons.SEARCH}
|
||||||
>
|
>
|
||||||
{results.results.map((v) => (
|
<MediaGrid>
|
||||||
<WatchedMediaCard
|
{results.results.map((v) => (
|
||||||
key={[v.mediaId, v.providerId].join("|")}
|
<WatchedMediaCard
|
||||||
media={v}
|
key={[v.mediaId, v.providerId].join("|")}
|
||||||
/>
|
media={v}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
</MediaGrid>
|
||||||
</SectionHeading>
|
</SectionHeading>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { SearchBarInput } from "@/components/SearchBar";
|
||||||
import Sticky from "react-stickynode";
|
import Sticky from "react-stickynode";
|
||||||
import { Title } from "@/components/text/Title";
|
import { Title } from "@/components/text/Title";
|
||||||
import { useSearchQuery } from "@/hooks/useSearchQuery";
|
import { useSearchQuery } from "@/hooks/useSearchQuery";
|
||||||
|
import { WideContainer } from "@/components/layout/WideContainer";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { SearchResultsPartial } from "./SearchResultsPartial";
|
import { SearchResultsPartial } from "./SearchResultsPartial";
|
||||||
|
|
||||||
export function SearchView() {
|
export function SearchView() {
|
||||||
|
@ -21,16 +21,16 @@ export function SearchView() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative z-10">
|
<div className="relative z-10 mb-24">
|
||||||
<Navigation bg={showBg} />
|
<Navigation bg={showBg} />
|
||||||
<ThinContainer>
|
<ThinContainer>
|
||||||
<div className="mt-44 space-y-16 text-center">
|
<div className="mt-44 space-y-16 text-center">
|
||||||
<div className="absolute left-0 bottom-0 right-0 flex h-0 justify-center">
|
<div className="absolute left-0 bottom-0 right-0 flex h-0 justify-center">
|
||||||
<div className="absolute bottom-4 h-[100vh] w-[300vh] rounded-[100%] bg-[#211D30]" />
|
<div className="absolute bottom-4 h-[100vh] w-[300vh] rounded-[100%] bg-denim-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-20">
|
<div className="relative z-20">
|
||||||
<div className="mb-16">
|
<div className="mb-16">
|
||||||
<Title>{t("search.title")}</Title>
|
<Title className="mx-auto max-w-xs">{t("search.title")}</Title>
|
||||||
</div>
|
</div>
|
||||||
<Sticky enabled top={16} onStateChange={stickStateChanged}>
|
<Sticky enabled top={16} onStateChange={stickStateChanged}>
|
||||||
<SearchBarInput
|
<SearchBarInput
|
||||||
|
@ -46,9 +46,9 @@ export function SearchView() {
|
||||||
</div>
|
</div>
|
||||||
</ThinContainer>
|
</ThinContainer>
|
||||||
</div>
|
</div>
|
||||||
<ThinContainer>
|
<WideContainer>
|
||||||
<SearchResultsPartial search={search} />
|
<SearchResultsPartial search={search} />
|
||||||
</ThinContainer>
|
</WideContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,29 +12,29 @@ module.exports = {
|
||||||
"bink-500": "#8D66B5",
|
"bink-500": "#8D66B5",
|
||||||
"bink-600": "#A87FD1",
|
"bink-600": "#A87FD1",
|
||||||
"bink-700": "#CD97D6",
|
"bink-700": "#CD97D6",
|
||||||
"denim-100": "#131119",
|
"denim-100": "#120F1D",
|
||||||
"denim-200": "#1E1A29",
|
"denim-200": "#191526",
|
||||||
"denim-300": "#282336",
|
"denim-300": "#211D30",
|
||||||
"denim-400": "#322D43",
|
"denim-400": "#2B263D",
|
||||||
"denim-500": "#433D55",
|
"denim-500": "#38334A",
|
||||||
"denim-600": "#5A5370",
|
"denim-600": "#504B64",
|
||||||
"denim-700": "#817998",
|
"denim-700": "#7A758F"
|
||||||
},
|
},
|
||||||
|
|
||||||
/* fonts */
|
/* fonts */
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
"open-sans": "'Open Sans'",
|
"open-sans": "'Open Sans'"
|
||||||
},
|
},
|
||||||
|
|
||||||
/* animations */
|
/* animations */
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"loading-pin": {
|
"loading-pin": {
|
||||||
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
||||||
"20%": { height: "1em", "background-color": "white" },
|
"20%": { height: "1em", "background-color": "white" }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" },
|
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
plugins: [require("tailwind-scrollbar")],
|
plugins: [require("tailwind-scrollbar"), require("@tailwindcss/line-clamp")]
|
||||||
};
|
};
|
||||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -254,6 +254,11 @@
|
||||||
"@swc/core-win32-ia32-msvc" "1.3.22"
|
"@swc/core-win32-ia32-msvc" "1.3.22"
|
||||||
"@swc/core-win32-x64-msvc" "1.3.22"
|
"@swc/core-win32-x64-msvc" "1.3.22"
|
||||||
|
|
||||||
|
"@tailwindcss/line-clamp@^0.4.2":
|
||||||
|
"integrity" "sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw=="
|
||||||
|
"resolved" "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.2.tgz"
|
||||||
|
"version" "0.4.2"
|
||||||
|
|
||||||
"@tootallnate/once@2":
|
"@tootallnate/once@2":
|
||||||
"version" "2.0.0"
|
"version" "2.0.0"
|
||||||
|
|
||||||
|
@ -1942,16 +1947,16 @@
|
||||||
"version" "1.1.4"
|
"version" "1.1.4"
|
||||||
|
|
||||||
"json5@^1.0.1":
|
"json5@^1.0.1":
|
||||||
"integrity" "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow=="
|
"integrity" "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="
|
||||||
"resolved" "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz"
|
"resolved" "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz"
|
||||||
"version" "1.0.1"
|
"version" "1.0.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"minimist" "^1.2.0"
|
"minimist" "^1.2.0"
|
||||||
|
|
||||||
"json5@^2.2.0":
|
"json5@^2.2.0":
|
||||||
"integrity" "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
|
"integrity" "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||||
"resolved" "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz"
|
"resolved" "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
|
||||||
"version" "2.2.1"
|
"version" "2.2.3"
|
||||||
|
|
||||||
"jsonparse@^1.3.1":
|
"jsonparse@^1.3.1":
|
||||||
"version" "1.3.1"
|
"version" "1.3.1"
|
||||||
|
@ -3225,7 +3230,7 @@
|
||||||
"resolved" "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-2.0.1.tgz"
|
"resolved" "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-2.0.1.tgz"
|
||||||
"version" "2.0.1"
|
"version" "2.0.1"
|
||||||
|
|
||||||
"tailwindcss@^3.2.4", "tailwindcss@3.x":
|
"tailwindcss@^3.2.4", "tailwindcss@>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1", "tailwindcss@3.x":
|
||||||
"integrity" "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ=="
|
"integrity" "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ=="
|
||||||
"resolved" "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz"
|
"resolved" "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz"
|
||||||
"version" "3.2.4"
|
"version" "3.2.4"
|
||||||
|
|
Loading…
Reference in a new issue