Upgrade to Tailwind 4

This commit is contained in:
Sam Becker 2025-02-08 16:21:26 -06:00
parent d17dc81720
commit 7ab319142f
41 changed files with 581 additions and 790 deletions

View File

@ -9,6 +9,7 @@
"camelcase",
"cloudflarestorage",
"cmdk",
"Consolas",
"CredentialsSignin",
"datetime",
"Eterna",

View File

@ -48,6 +48,7 @@
"@next/bundle-analyzer": "15.1.6",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.0.5",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
@ -57,7 +58,6 @@
"@types/react": "19.0.8",
"@types/react-dom": "19.0.3",
"@types/sanitize-html": "^2.13.0",
"autoprefixer": "10.4.20",
"clsx": "^2.1.1",
"cross-fetch": "^4.1.0",
"eslint": "9.20.0",
@ -65,7 +65,7 @@
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"postcss": "8.5.1",
"tailwindcss": "3.4.17",
"tailwindcss": "4.0.5",
"ts-node": "^10.9.2",
"typescript": "5.7.3"
}

837
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'@tailwindcss/postcss': {},
},
}

View File

@ -105,7 +105,7 @@ export default function AdminAddAllUploads({
<div className="w-full space-y-4 py-1">
<div className="flex">
<div className={clsx(
'flex-grow',
'grow',
tagErrorMessage ? 'text-error' : 'text-main',
)}>
{showTags

View File

@ -156,11 +156,11 @@ export default function AdminBatchEditPanelClient({
color="gray"
className={clsx(
'min-h-[3.5rem]',
'backdrop-blur-lg !border-transparent',
'!text-gray-900 dark:!text-gray-100',
'!bg-gray-100/90 dark:!bg-gray-900/70',
'backdrop-blur-lg border-transparent!',
'text-gray-900! dark:text-gray-100!',
'bg-gray-100/90! dark:bg-gray-900/70!',
// Override default <Note /> content spacing
'[&>*>*:first-child]:gap-1.5 [&>*>*:first-child]:sm:gap-2.5',
'[&>*>*:first-child]:gap-1.5 sm:[&>*>*:first-child]:gap-2.5',
)}
padding={isInTagMode ? 'tight-cta-right-left' : 'tight-cta-right'}
cta={<div className="flex items-center gap-1.5 sm:gap-2.5">

View File

@ -67,7 +67,7 @@ export default function AdminNavClient({
)}>
<div className={clsx(
'flex gap-0.5 md:gap-1.5 -mx-1',
'flex-grow overflow-x-auto',
'grow overflow-x-auto',
)}>
{items.map(({ label, href, count }) =>
<LinkWithStatus
@ -101,7 +101,7 @@ export default function AdminNavClient({
</LinkWithLoader>
</div>
{shouldShowBanner &&
<Note icon={<FaRegClock className="flex-shrink-0" />}>
<Note icon={<FaRegClock className="shrink-0" />}>
Photo updates detectedthey may take several minutes to show up
for visitors
</Note>}

View File

@ -42,7 +42,7 @@ export default function AdminPhotosClient({
return (
<SiteGrid
contentMain={
<div className="space-y-4">
<div>
<div className="flex">
<div className="grow min-w-0">
<PhotoUpload

View File

@ -79,7 +79,7 @@ export default function AdminPhotosTable({
</span>
{photo.priorityOrder !== null &&
<span className={clsx(
'text-xs leading-none px-1.5 py-1 rounded-sm',
'text-xs leading-none px-1.5 py-1 rounded-xs',
'dark:text-gray-300',
'bg-gray-100 dark:bg-gray-800',
)}>

View File

@ -35,6 +35,6 @@ export default function AdminTagBadge({
return (
hideBadge
? renderBadgeContent()
: <Badge className="!py-[3px]">{renderBadgeContent()}</Badge>
: <Badge className="py-[3px]!">{renderBadgeContent()}</Badge>
);
}

View File

@ -14,11 +14,11 @@ export default function DeleteButton({
icon={<BiTrash size={16} />}
spinnerColor="text"
className={clsx(
'!text-red-500 dark:!text-red-600',
'active:!bg-red-100/50 active:dark:!bg-red-950/50',
'disabled:!bg-red-100/50 disabled:dark:!bg-red-950/50',
'!border-red-200 hover:!border-red-300',
'dark:!border-red-900/75 dark:hover:!border-red-900',
'text-red-500! dark:text-red-600!',
'active:bg-red-100/50! dark:active:bg-red-950/50!',
'disabled:bg-red-100/50! dark:disabled:bg-red-950/50!',
'border-red-200! hover:border-red-300!',
'dark:border-red-900/75! dark:hover:border-red-900!',
className,
)}
/>

View File

@ -35,11 +35,11 @@ export default function DeleteFormButton (
spinnerColor="text"
className={clsx(
className,
'!text-red-500 dark:!text-red-600',
'active:!bg-red-100/50 active:dark:!bg-red-950/50',
'disabled:!bg-red-100/50 disabled:dark:!bg-red-950/50',
'!border-red-200 hover:!border-red-300',
'dark:!border-red-900/75 dark:hover:!border-red-900',
'text-red-500! dark:text-red-600!',
'active:bg-red-100/50! dark:active:bg-red-950/50!',
'disabled:bg-red-100/50! dark:disabled:bg-red-950/50!',
'border-red-200! hover:border-red-300!',
'dark:border-red-900/75! dark:hover:border-red-900!',
)}
onFormSubmit={onFormSubmit}
/>;

View File

@ -49,7 +49,7 @@ export default function GitHubForkStatusBadgeClient({
'border transition-colors',
'select-none',
'pl-[4.5px] pr-2.5 py-[3px]',
'rounded-full shadow-sm',
'rounded-full shadow-xs',
classNameForStyle(),
)}>
{!label

View File

@ -43,7 +43,7 @@ export default function ComponentsPage() {
</div>
<div className={clsx(
'flex gap-1',
'[&>*]:inline-flex [&>*]:gap-1 [&_input]:-translate-y-0.5',
'*:inline-flex *:gap-1 [&_input]:-translate-y-0.5',
)}>
<FieldSetWithStatus
id="grid"
@ -62,7 +62,7 @@ export default function ComponentsPage() {
</div>
</h1>
<DivDebugBaselineGrid className="flex gap-8">
<div className="[&>*]:flex">
<div className="*:flex">
<div>
<LabeledIcon
icon={<FaCamera size={12} />}
@ -229,8 +229,8 @@ export default function ComponentsPage() {
</div>
</div>
<div className={clsx(
debugComponents && '[&>*]:bg-gray-300 [&>*]:dark:bg-gray-700',
'[&>*]:flex',
debugComponents && '*:bg-gray-300 dark:*:bg-gray-700',
'*:flex',
)}>
{DEBUG_LINES.map((_, i) =>
<div key={i}>
@ -239,8 +239,8 @@ export default function ComponentsPage() {
)}
</div>
<div className={clsx(
debugComponents && '[&>*]:bg-gray-300 [&>*]:dark:bg-gray-700',
'[&>*]:flex',
debugComponents && '*:bg-gray-300 dark:*:bg-gray-700',
'*:flex',
)}>
{DEBUG_LINES.map((_, i) =>
<PhotoCamera

View File

@ -39,7 +39,7 @@ export default function FilmPage() {
</div>
<div className={clsx(
'absolute top-0 left-[-2px] right-0 bottom-0',
'bg-gradient-to-t',
'bg-linear-to-t',
'from-white to-[rgba(255,255,255,0.5)]',
'dark:from-black dark:to-[rgba(0,0,0,0.5)]',
)} />

View File

@ -1,7 +1,6 @@
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/react';
import { clsx } from 'clsx/lite';
import { IBM_Plex_Mono } from 'next/font/google';
import {
BASE_URL,
DEFAULT_THEME,
@ -25,12 +24,6 @@ import '../site/globals.css';
import '../site/sonner.css';
import '../site/viewerjs.css';
const ibmPlexMono = IBM_Plex_Mono({
subsets: ['latin'],
weight: ['400', '500', '700'],
variable: '--font-ibm-plex-mono',
});
export const metadata: Metadata = {
title: SITE_TITLE,
description: SITE_DESCRIPTION,
@ -79,7 +72,7 @@ export default function RootLayout({
// Suppress hydration errors due to next-themes behavior
suppressHydrationWarning
>
<body className={ibmPlexMono.variable}>
<body>
<AppStateProvider>
<SwrConfigClient>
<ThemeProvider

View File

@ -35,7 +35,7 @@ function AdminChildPage({
)}>
<div className={clsx(
'flex items-center gap-x-1.5 sm:gap-x-3 gap-y-1',
'flex-grow',
'grow',
breadcrumbEllipsis ? 'min-w-0' : 'flex-wrap',
)}>
{backPath &&

View File

@ -53,7 +53,7 @@ export default function Badge({
<span className={clsx(
'max-w-full inline-flex',
// Truncate 1 + 2 levels deep
'truncate [&>*]:truncate',
'truncate *:truncate',
dimContent && 'opacity-50',
)}>
{children}

View File

@ -33,7 +33,7 @@ export default function ChecklistRow({
: optional ? 'optional' : 'missing'}
loading={isPending}
/>
<div className="flex flex-col min-w-0 flex-grow">
<div className="flex flex-col min-w-0 grow">
<div className={clsx(
'flex flex-wrap items-center gap-2 pb-0.5',
'font-bold dark:text-gray-300',

View File

@ -79,7 +79,7 @@ export default function ImageInput({
ref={inputRef}
id={INPUT_ID}
type="file"
className="!hidden"
className="hidden!"
accept={ACCEPTED_PHOTO_FILE_TYPES.join(',')}
disabled={loading}
multiple

View File

@ -60,7 +60,7 @@ export default function OGTile({
className={clsx(
'group',
'block w-full rounded-md overflow-hidden',
'border shadow-sm',
'border shadow-xs',
'border-gray-200 dark:border-gray-800',
riseOnHover && 'hover:-translate-y-1.5 transition-transform',
)}
@ -78,7 +78,7 @@ export default function OGTile({
</div>}
{loadingState === 'failed' &&
<div className={clsx(
'absolute top-0 left-0 right-0 bottom-0 z-[11]',
'absolute top-0 left-0 right-0 bottom-0 z-11',
'flex items-center justify-center',
'text-red-400',
)}>
@ -121,8 +121,8 @@ export default function OGTile({
'h-full flex flex-col gap-0.5 p-3',
'font-sans leading-tight',
'bg-gray-50 dark:bg-gray-900/50',
'group-active:bg-gray-50 group-active:dark:bg-gray-900/50',
'group-hover:bg-gray-100 group-hover:dark:bg-gray-900/70',
'group-active:bg-gray-50 dark:group-active:bg-gray-900/50',
'group-hover:bg-gray-100 dark:group-hover:bg-gray-900/70',
'border-t border-gray-200 dark:border-gray-800',
)}>
<div className="text-gray-800 dark:text-white font-medium">

View File

@ -17,7 +17,7 @@ export default function SelectTileOverlay({
return (
<div className={clsx(
'absolute w-full h-full cursor-pointer',
'active:bg-gray-950/40 active:dark:bg-gray-950/60',
'active:bg-gray-950/40 dark:active:bg-gray-950/60',
isPerformingSelectEdit && 'pointer-events-none',
)}>
{/* Admin Select Border */}

View File

@ -16,7 +16,7 @@ export default function Switcher({
type === 'regular'
? 'border-gray-300 dark:border-gray-800'
: 'border-transparent',
type === 'regular' && 'shadow-sm',
type === 'regular' && 'shadow-xs',
)}>
{children}
</div>

View File

@ -33,7 +33,7 @@ export default function SwitcherItem({
? 'text-black dark:text-white'
: 'text-gray-400 dark:text-gray-600',
active
? 'hover:text-black hover:dark:text-white'
? 'hover:text-black dark:hover:text-white'
: 'hover:text-gray-700 dark:hover:text-gray-400',
);

View File

@ -225,7 +225,7 @@ export default function TagInput({
aria-controls={ARIA_ID_TAG_CONTROL}
className={clsx(
className,
'w-full control !px-2 !py-2',
'w-full control px-2! py-2!',
'outline-1 outline-blue-600',
'group-focus-within:outline group-active:outline',
'inline-flex flex-wrap items-center gap-2',
@ -247,7 +247,7 @@ export default function TagInput({
'px-1.5 py-0.5',
'bg-gray-200/60 dark:bg-gray-800',
'active:bg-gray-200 dark:active:bg-gray-900',
'rounded-sm',
'rounded-xs',
)}
onClick={() => removeOption(option)}
>
@ -258,8 +258,8 @@ export default function TagInput({
ref={inputRef}
type="text"
className={clsx(
'grow !min-w-0 !p-0 -my-2 text-xl',
'!border-none !ring-transparent',
'grow min-w-0! p-0! -my-2 text-xl',
'border-none! ring-transparent!',
'placeholder:text-dim placeholder:text-[14px]',
'placeholder:translate-x-[2px]',
'placeholder:translate-y-[-1.5px]',
@ -287,7 +287,7 @@ export default function TagInput({
role="listbox"
ref={optionsRef}
className={clsx(
'control absolute top-0 mt-3 w-full z-10 !px-1.5 !py-1.5',
'control absolute top-0 mt-3 w-full z-10 px-1.5! py-1.5!',
'max-h-[8rem] overflow-y-auto',
'flex flex-col gap-y-1',
'text-xl shadow-lg dark:shadow-xl',
@ -310,13 +310,13 @@ export default function TagInput({
'text-base',
'group flex items-center gap-1',
'cursor-pointer select-none',
'px-1.5 py-1 rounded-sm',
'px-1.5 py-1 rounded-xs',
'hover:bg-gray-100 dark:hover:bg-gray-800',
'active:bg-gray-50 dark:active:bg-gray-900',
'focus:bg-gray-100 dark:focus:bg-gray-800',
index === 0 && selectedOptionIndex === undefined &&
'bg-gray-100 dark:bg-gray-800',
'outline-none',
'outline-hidden',
)}
onClick={() => {
addOptions([value]);

View File

@ -377,13 +377,13 @@ export default function CommandKClient({
<Command.Input
onChangeCapture={(e) => setQueryLive(e.currentTarget.value)}
className={clsx(
'w-full !min-w-0',
'w-full min-w-0!',
'focus:ring-0',
isPlaceholderVisible || isLoading && '!pr-8',
'!border-gray-200 dark:!border-gray-800',
'focus:border-gray-200 focus:dark:border-gray-800',
isPlaceholderVisible || isLoading && 'pr-8!',
'border-gray-200! dark:border-gray-800!',
'focus:border-gray-200 dark:focus:border-gray-800',
'placeholder:text-gray-400/80',
'placeholder:dark:text-gray-700',
'dark:placeholder:text-gray-700',
isPending && 'opacity-20',
)}
placeholder="Search photos, views, settings ..."

View File

@ -32,13 +32,13 @@ export default function CommandKItem({
'px-2',
accessory ? 'py-2' : 'py-1',
'rounded-md cursor-pointer tracking-wide',
'active:!bg-gray-200/75 active:dark:!bg-gray-800/55',
'active:bg-gray-200/75! dark:active:bg-gray-800/55!',
...loading
? [
'data-[selected=true]:dark:bg-gray-900/50',
'dark:data-[selected=true]:bg-gray-900/50',
'data-[selected=true]:bg-gray-100/50',
] : [
'data-[selected=true]:dark:bg-gray-900/75',
'dark:data-[selected=true]:bg-gray-900/75',
'data-[selected=true]:bg-gray-100',
],
disabled && 'opacity-15',

View File

@ -21,9 +21,9 @@ export default function MoreMenu({
<button
className={clsx(
buttonClassName,
'p-1 min-h-0 border-none shadow-none hover:outline-none',
'p-1 min-h-0 border-none shadow-none hover:outline-hidden',
'hover:bg-gray-100 active:bg-gray-100',
'hover:dark:bg-gray-800/75 active:dark:bg-gray-900',
'dark:hover:bg-gray-800/75 dark:active:bg-gray-900',
'text-dim',
)}
aria-label={ariaLabel}

View File

@ -36,9 +36,9 @@ export default function MoreMenuItem({
className={clsx(
'flex items-center h-8',
'px-2 py-1.5 rounded-[3px]',
'select-none hover:outline-none',
'select-none hover:outline-hidden',
'hover:bg-gray-50 active:bg-gray-100',
'hover:dark:bg-gray-900/75 active:dark:bg-gray-900',
'dark:hover:bg-gray-900/75 dark:active:bg-gray-900',
'whitespace-nowrap',
isLoading
? 'cursor-not-allowed opacity-50'

View File

@ -96,7 +96,7 @@ export default function EntityLink({
{renderLabel()}
</Badge>
: <span className={clsx(
truncate && 'inline-flex max-w-full [&>*]:truncate',
truncate && 'inline-flex max-w-full *:truncate',
)}>
{renderLabel()}
</span>}

View File

@ -44,10 +44,11 @@ export default function LoaderButton(props: {
}
}}
className={clsx(
'font-mono',
...(styleAs !== 'button'
? [
'link h-4 active:text-medium',
'disabled:!bg-transparent',
'disabled:bg-transparent!',
]
: ['h-9']
),
@ -55,7 +56,7 @@ export default function LoaderButton(props: {
styleAs === 'link-without-hover' && 'hover:text-main',
'inline-flex items-center gap-2 self-start whitespace-nowrap',
primary && 'primary',
hideFocusOutline && 'focus:outline-none',
hideFocusOutline && 'focus:outline-hidden',
className,
)}
disabled={isLoading || disabled}

View File

@ -16,7 +16,7 @@ export default function MenuSurface({
className={clsx(
'component-surface',
'px-2 py-1.5 max-w-[14rem]',
'shadow-sm',
'shadow-xs',
'text-[0.8rem] leading-tight',
'text-balance text-center',
className,

View File

@ -36,8 +36,8 @@ export default function PhotoGridPage({
className={clsx(
'absolute left-0 right-0',
side === 'top'
? 'top-0 bg-gradient-to-b from-white dark:from-black'
: 'bottom-0 bg-gradient-to-t from-white dark:from-black',
? 'top-0 bg-linear-to-b from-white dark:from-black'
: 'bottom-0 bg-linear-to-t from-white dark:from-black',
'h-6 z-10 pointer-events-none',
)}
/>;

View File

@ -131,7 +131,7 @@ export default function PhotoLarge({
const renderPhotoLink = () =>
<PhotoLink
photo={photo}
className="font-bold uppercase flex-grow"
className="font-bold uppercase grow"
prefetch={prefetch}
/>;
@ -171,7 +171,7 @@ export default function PhotoLarge({
</div>;
const largePhotoContainerClassName = clsx(arePhotosMatted &&
'flex items-center justify-center aspect-[3/2] bg-gray-100',
'flex items-center justify-center aspect-3/2 bg-gray-100',
);
return (

View File

@ -54,7 +54,7 @@ export default function PhotoMedium({
{isLoading &&
<div className={clsx(
'absolute inset-0 flex items-center justify-center',
'text-white bg-black/25 backdrop-blur-sm',
'text-white bg-black/25 backdrop-blur-xs',
'animate-fade-in',
'z-10',
)}>

View File

@ -388,7 +388,7 @@ export default function PhotoForm({
<div className={clsx(
'absolute -top-16 -left-2 right-0 bottom-0 -z-10',
'pointer-events-none',
'bg-gradient-to-t',
'bg-linear-to-t',
'from-white/90 from-60%',
'dark:from-black/90 dark:from-50%',
)} />

View File

@ -64,7 +64,7 @@ export default function ShareModal({
'text-2xl leading-snug',
)}>
<TbPhotoShare size={22} className="hidden xs:block" />
<div className="flex-grow">
<div className="grow">
{title}
</div>
</div>}

View File

@ -37,7 +37,7 @@ export default function Footer() {
'flex items-center gap-1',
'text-dim min-h-10',
)}>
<div className="flex gap-x-3 xs:gap-x-4 flex-grow flex-wrap">
<div className="flex gap-x-3 xs:gap-x-4 grow flex-wrap">
{isPathAdmin(pathname)
? <>
{userEmail === undefined &&

View File

@ -76,7 +76,7 @@ export default function Nav({
showAdmin={isUserSignedIn}
/>
<div className={clsx(
'flex-grow text-right min-w-0',
'grow text-right min-w-0',
'hidden xs:block',
'translate-y-[-1px]',
)}>

View File

@ -1,6 +1,179 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap");
@import 'tailwindcss';
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--breakpoint-xs: 390px;
--breakpoint-3xl: 1640px;
--text-xs: 0.75rem;
--text-xs--line-height: 1rem;
--text-sm: 0.84375rem;
--text-sm--line-height: 1.1875rem;
--text-base: 0.875rem;
--text-base--line-height: 1.25rem;
--text-lg: 1rem;
--text-lg--line-height: 1.25rem;
--text-xl: 1.125rem;
--text-xl--line-height: 1.25rem;
--text-2xl: 1.25rem;
--text-2xl--line-height: 1.25rem;
--text-3xl: 1.5rem;
--text-3xl--line-height: 1.5rem;
--animate-fade-in: fade-in 0.5s linear both running;
@keyframes fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
--animate-fade-in-from-top: fade-in-from-top 0.25s ease-in-out;
@keyframes fade-in-from-top {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
--animate-fade-in-from-bottom: fade-in-from-bottom 0.25s ease-in-out;
@keyframes fade-in-from-bottom {
0% {
opacity: 0;
transform: translateY(10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
--animate-rotate-pulse: rotate-pulse 0.75s linear infinite normal both running;
@keyframes rotate-pulse {
0% { transform: rotate(0deg) scale(1) }
50% { transform: rotate(180deg) scale(0.8) }
100% { transform: rotate(360deg) scale(1) }
}
--animate-hover-drift: hover-drift 8s linear infinite;
@keyframes hover-drift {
0% { transform: translate(0, 0) }
20% { transform: translate(1px, -2px) }
40% { transform: translate(1px, 1.5px) }
60% { transform: translate(-1px, 2px) }
80% { transform: translate(-1.5px, -1.75px) }
100% { transform: translate(0, 0) }
}
--animate-hover-wobble: hover-wobble 6s linear infinite normal both running;
@keyframes hover-wobble {
0% { transform: rotate(0deg) }
20% { transform: rotate(3.5deg) }
40% { transform: rotate(-2deg) }
60% { transform: rotate(2.5deg) }
80% { transform: rotate(-2.5deg) }
100% { transform: rotate(0deg) }
}
}
/* Text */
@utility text-main {
@apply
text-gray-900 dark:text-gray-100
}
@utility text-invert {
@apply
text-white dark:text-black
}
@utility text-medium {
@apply
text-gray-500 dark:text-gray-400
}
@utility text-dim {
@apply
text-gray-400 dark:text-gray-500
}
@utility text-extra-dim {
@apply
text-gray-400/80 dark:text-gray-400/50
}
@utility text-extra-extra-dim {
@apply
text-gray-200 dark:text-gray-800
}
@utility text-icon {
@apply
text-gray-800 dark:text-gray-200
}
@utility text-error {
@apply
text-red-500 dark:text-red-400
}
/* Border */
@utility border-main {
@apply border-gray-200 dark:border-gray-700
}
@utility border-subtle {
@apply
border border-gray-200 dark:border-gray-800
}
/* Background */
@utility bg-main {
@apply
bg-white dark:bg-black
}
@utility bg-dim {
@apply
bg-gray-100 dark:bg-gray-900/75
}
@utility bg-content {
@apply
bg-white border-gray-200
dark:bg-black dark:border-gray-800
}
@utility bg-invert {
@apply
bg-gray-900 dark:bg-gray-100
}
/* Baseline Grid */
@utility space-y-baseline {
@apply
space-y-[1.1875rem] md:space-y-[1.25rem]
}
@utility gap-y-baseline {
@apply
gap-y-[1.1875rem] md:gap-y-[1.25rem]
}
@utility gap-baseline {
@apply
gap-[1.1875rem] md:gap-[1.25rem]
}
@utility h-baseline {
@apply
h-[1.1875rem] md:h-[1.25rem]
}
@utility max-h-baseline {
@apply
max-h-[1.1875rem] md:max-h-[1.25rem]
}
@utility -mt-baseline {
@apply
-mt-[1.1875rem] md:-mt-[1.25rem]
}
@utility bg-baseline-grid {
@apply
bg-[repeating-linear-gradient(to_bottom,#eee,#eee_1px,transparent_1px,transparent_1.1875rem)]
md:bg-[repeating-linear-gradient(to_bottom,#eee,#eee_1px,transparent_1px,transparent_1.25rem)]
dark:bg-[repeating-linear-gradient(to_bottom,#222,#222_1px,transparent_1px,transparent_1.1875rem)]
dark:md:bg-[repeating-linear-gradient(to_bottom,#222,#222_1px,transparent_1px,transparent_1.25rem)]
}
@layer base {
/* Core */
@ -56,7 +229,7 @@
file:border-solid file:border
file:border-gray-200 dark:file:border-gray-700
file:cursor-pointer
file:shadow-sm
file:shadow-xs
file:active:bg-gray-100
file:disabled:bg-gray-100
file:hover:border-gray-300 file:dark:hover:border-gray-600
@ -80,7 +253,7 @@
inline-flex gap-2 items-center
px-3
text-base
shadow-sm
shadow-xs
active:bg-gray-100 dark:active:bg-gray-900
hover:border-gray-300 dark:hover:border-gray-600
disabled:cursor-not-allowed
@ -127,95 +300,4 @@
bg-content border border-main
rounded-lg
}
/* Utilities: Text */
.text-main {
@apply
text-gray-900 dark:text-gray-100
}
.text-invert {
@apply
text-white dark:text-black
}
.text-medium {
@apply
text-gray-500 dark:text-gray-400
}
.text-dim {
@apply
text-gray-400 dark:text-gray-500
}
.text-extra-dim {
@apply
text-gray-400/80 dark:text-gray-400/50
}
.text-extra-extra-dim {
@apply
text-gray-200 dark:text-gray-800
}
.text-icon {
@apply
text-gray-800 dark:text-gray-200
}
.text-error {
@apply
text-red-500 dark:text-red-400
}
/* Utilities: Border */
.border-main {
@apply border-gray-200 dark:border-gray-700
}
.border-subtle {
@apply
border border-gray-200 dark:border-gray-800
}
/* Utilities: Background */
.bg-main {
@apply
bg-white dark:bg-black
}
.bg-dim {
@apply
bg-gray-100 dark:bg-gray-900/75
}
.bg-content {
@apply
bg-white border-gray-200
dark:bg-black dark:border-gray-800
}
.bg-invert {
@apply
bg-gray-900 dark:bg-gray-100
}
/* Utilities: Baseline Grid */
.space-y-baseline {
@apply
space-y-[1.1875rem] md:space-y-[1.25rem]
}
.gap-y-baseline {
@apply
gap-y-[1.1875rem] md:gap-y-[1.25rem]
}
.gap-baseline {
@apply
gap-[1.1875rem] md:gap-[1.25rem]
}
.h-baseline {
@apply
h-[1.1875rem] md:h-[1.25rem]
}
.max-h-baseline {
@apply
max-h-[1.1875rem] md:max-h-[1.25rem]
}
.-mt-baseline {
@apply
-mt-[1.1875rem] md:-mt-[1.25rem]
}
.bg-baseline-grid {
@apply
bg-[repeating-linear-gradient(to_bottom,#eee,#eee_1px,transparent_1px,transparent_1.1875rem)]
md:bg-[repeating-linear-gradient(to_bottom,#eee,#eee_1px,transparent_1px,transparent_1.25rem)]
dark:bg-[repeating-linear-gradient(to_bottom,#222,#222_1px,transparent_1px,transparent_1.1875rem)]
dark:md:bg-[repeating-linear-gradient(to_bottom,#222,#222_1px,transparent_1px,transparent_1.25rem)]
}
}

View File

@ -1,98 +0,0 @@
const defaultTheme = require('tailwindcss/defaultTheme');
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx,mdx}',
],
darkMode: 'class',
theme: {
screens: {
'xs': '390px',
...defaultTheme.screens,
'3xl': '1640px',
},
fontSize: {
'xs': ['0.75rem', '1rem'], // 12px on 16px
'sm': ['0.84375rem', '1.1875rem'], // 13.5px on 19px [Default: mobile]
'base': ['0.875rem', '1.25rem'], // 14px on 20px [Default: desktop]
'lg': ['1rem', '1.25rem'], // 16px on 20px
'xl': ['1.125rem', '1.25rem'], // 18px on 20px
'2xl': ['1.25rem', '1.25rem'], // 20px on 20px
'3xl': ['1.5rem', '1.5rem'], // 24px on 24px
},
extend: {
fontFamily: {
'mono': ['var(--font-ibm-plex-mono)', ...defaultTheme.fontFamily.mono],
},
animation: {
'rotate-pulse':
'rotate-pulse 0.75s linear infinite normal both running',
'fade-in':
'fade-in 0.5s linear both running',
'fade-in-from-top':
'fade-in-from-top 0.25s ease-in-out',
'fade-in-from-bottom':
'fade-in-from-bottom 0.25s ease-in-out',
'hover-drift':
'hover-drift 8s linear infinite',
'hover-wobble':
'hover-wobble 6s linear infinite normal both running',
},
keyframes: {
'fade-in': {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
'fade-in-from-top': {
'0%': {
opacity: '0',
transform: 'translateY(-10px)',
},
'100%': {
opacity: '1',
transform: 'translateY(0)',
},
},
'fade-in-from-bottom': {
'0%': {
opacity: '0',
transform: 'translateY(10px)',
},
'100%': {
opacity: '1',
transform: 'translateY(0)',
},
},
'rotate-pulse': {
'0%': { transform: 'rotate(0deg) scale(1)' },
'50%': { transform: 'rotate(180deg) scale(0.8)' },
'100%': { transform: 'rotate(360deg) scale(1)' },
},
'hover-drift': {
'0%': { transform: 'translate(0, 0)' },
'20%': { transform: 'translate(1px, -2px)' },
'40%': { transform: 'translate(1px, 1.5px)' },
'60%': { transform: 'translate(-1px, 2px)' },
'80%': { transform: 'translate(-1.5px, -1.75px)' },
'100%': { transform: 'translate(0, 0)' },
},
'hover-wobble': {
'0%': { transform: 'rotate(0deg)' },
'20%': { transform: 'rotate(3.5deg)' },
'40%': { transform: 'rotate(-2deg)' },
'60%': { transform: 'rotate(2.5deg)' },
'80%': { transform: 'rotate(-2.5deg)' },
'100%': { transform: 'rotate(0deg)' },
},
},
},
},
future: {
hoverOnlyWhenSupported: true,
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/container-queries'),
],
};