Added foundation for dark theme support
This commit is contained in:
parent
2b2a9c41e2
commit
a39313c655
|
@ -0,0 +1,29 @@
|
|||
function applyTheme() {
|
||||
if (!("theme" in localStorage)) {
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
localStorage.theme = "dark";
|
||||
} else {
|
||||
localStorage.theme = "light";
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.theme === "dark") {
|
||||
document.documentElement.classList.add("dark");
|
||||
document.querySelector("#themeButtonIcon").innerHTML = "light_mode";
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
document.querySelector("#themeButtonIcon").innerHTML = "dark_mode";
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if (localStorage.theme === "dark") {
|
||||
localStorage.theme = "light";
|
||||
} else {
|
||||
localStorage.theme = "dark";
|
||||
}
|
||||
applyTheme();
|
||||
}
|
||||
|
||||
applyTheme();
|
||||
document.querySelector("#themeButton").onclick = () => toggleTheme();
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from "redux/appLayoutSlice";
|
||||
import { RootState } from "redux/store";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import Script from "next/script";
|
||||
|
||||
type AppLayoutProps = {
|
||||
subPanel?: React.ReactNode;
|
||||
|
@ -44,7 +45,7 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||
const subPanelOpen = useSelector(
|
||||
(state: RootState) => state.appLayout.subPanelOpen
|
||||
);
|
||||
|
||||
|
||||
const sensibilitySwipe = 1.1;
|
||||
|
||||
const handlers = useSwipeable({
|
||||
|
@ -135,7 +136,7 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||
|
||||
{/* Background when navbar is opened */}
|
||||
<div
|
||||
className={`fixed bg-dark inset-0 transition-opacity duration-500
|
||||
className={`fixed bg-shade inset-0 transition-opacity duration-500
|
||||
${turnSubIntoContent ? "z-10" : ""}
|
||||
${
|
||||
(mainPanelOpen || subPanelOpen) && isMobile
|
||||
|
@ -188,7 +189,7 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||
|
||||
{/* Language selection background */}
|
||||
<div
|
||||
className={`fixed bg-dark inset-0 transition-all duration-500 z-20 grid place-content-center ${
|
||||
className={`fixed bg-shade inset-0 transition-all duration-500 z-20 grid place-content-center ${
|
||||
languagePanelOpen
|
||||
? "bg-opacity-50"
|
||||
: "bg-opacity-0 pointer-events-none touch-none"
|
||||
|
@ -198,7 +199,7 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||
}}
|
||||
>
|
||||
<div
|
||||
className={`p-10 bg-light rounded-lg shadow-2xl shadow-dark grid gap-4 place-items-center transition-transform ${
|
||||
className={`p-10 bg-light rounded-lg shadow-2xl shadow-shade grid gap-4 place-items-center transition-transform ${
|
||||
languagePanelOpen ? "scale-100" : "scale-0"
|
||||
}`}
|
||||
>
|
||||
|
@ -226,8 +227,10 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||
delayShow={300}
|
||||
delayHide={100}
|
||||
disable={!mainPanelReduced || isMobile || isCoarse}
|
||||
className="drop-shadow-dark-xl !opacity-100 !bg-light !rounded-lg after:!border-r-light text-left"
|
||||
className="drop-shadow-shade-xl !opacity-100 !bg-light !rounded-lg after:!border-r-light text-left !text-black"
|
||||
/>
|
||||
|
||||
<Script src="/js/toggleTheme.js" defer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import Link from "next/link";
|
|||
import { MouseEventHandler } from "react";
|
||||
|
||||
type ButtonProps = {
|
||||
id?: string;
|
||||
className?: string;
|
||||
href?: string;
|
||||
children: React.ReactChild | React.ReactChild[];
|
||||
|
@ -13,13 +14,14 @@ type ButtonProps = {
|
|||
export default function Button(props: ButtonProps): JSX.Element {
|
||||
const button = (
|
||||
<div
|
||||
id={props.id}
|
||||
onClick={props.onClick}
|
||||
className={`grid place-content-center place-items-center border-[1px] border-dark text-dark rounded-full px-4 pt-[0.4rem] pb-[0.5rem] transition-all ${
|
||||
props.className
|
||||
} ${
|
||||
props.active
|
||||
? "text-light bg-black drop-shadow-black-lg !border-black cursor-not-allowed"
|
||||
: "cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-dark-lg active:bg-black active:drop-shadow-black-lg active:border-black"
|
||||
: "cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-shade-lg active:bg-black active:drop-shadow-black-lg active:border-black"
|
||||
}`}
|
||||
>
|
||||
{props.children}
|
||||
|
|
|
@ -26,7 +26,7 @@ export default function ThumbnailHeader(
|
|||
return (
|
||||
<>
|
||||
<div className="grid place-items-center gap-12 mb-12">
|
||||
<div className="drop-shadow-dark-lg">
|
||||
<div className="drop-shadow-shade-lg">
|
||||
{content.thumbnail.data ? (
|
||||
<Img
|
||||
className=" rounded-xl"
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function InsetBox(props: InsetBoxProps): JSX.Element {
|
|||
return (
|
||||
<div
|
||||
id={props.id}
|
||||
className={`w-full shadow-inner-sm shadow-dark bg-mid rounded-xl p-8 ${props.className}`}
|
||||
className={`w-full shadow-inner-sm shadow-shade bg-mid rounded-xl p-8 ${props.className}`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@ export default function LibraryContentPreview(
|
|||
|
||||
return (
|
||||
<Link href={"/contents/" + item.slug} passHref>
|
||||
<div className="drop-shadow-dark-xl cursor-pointer grid items-end fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform">
|
||||
<div className="drop-shadow-shade-xl cursor-pointer grid items-end fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform">
|
||||
{item.thumbnail.data ? (
|
||||
<Img
|
||||
className="rounded-md coarse:rounded-b-none"
|
||||
|
@ -31,7 +31,7 @@ export default function LibraryContentPreview(
|
|||
) : (
|
||||
<div className="w-full aspect-[3/2] bg-light rounded-lg"></div>
|
||||
)}
|
||||
<div className="linearbg-1 fine:drop-shadow-dark-lg fine:absolute coarse:rounded-b-md bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
|
||||
<div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
|
||||
<div className="grid grid-flow-col gap-1 overflow-hidden place-content-start">
|
||||
{item.type ? (
|
||||
<Chip>
|
||||
|
|
|
@ -25,7 +25,7 @@ export default function LibraryItemsPreview(
|
|||
return (
|
||||
<Link href={"/library/" + item.slug} passHref>
|
||||
<div
|
||||
className={`drop-shadow-dark-xl cursor-pointer grid items-end hover:rounded-3xl fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform ${props.className}`}
|
||||
className={`drop-shadow-shade-xl cursor-pointer grid items-end hover:rounded-3xl fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform ${props.className}`}
|
||||
>
|
||||
{item.thumbnail.data ? (
|
||||
<Img
|
||||
|
@ -36,7 +36,7 @@ export default function LibraryItemsPreview(
|
|||
<div className="w-full aspect-[21/29.7] bg-light rounded-lg"></div>
|
||||
)}
|
||||
|
||||
<div className="linearbg-1 fine:drop-shadow-dark-lg fine:absolute place-items-start bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
|
||||
<div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute place-items-start bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
|
||||
{item.metadata && item.metadata.length > 0 ? (
|
||||
<div className="flex flex-row gap-1">
|
||||
<Chip>{prettyItemSubType(item.metadata[0])}</Chip>
|
||||
|
|
|
@ -16,10 +16,10 @@ type NavOptionProps = {
|
|||
export default function NavOption(props: NavOptionProps): JSX.Element {
|
||||
const router = useRouter();
|
||||
const isActive = router.asPath.startsWith(props.url);
|
||||
const divActive = "bg-mid shadow-inner-sm shadow-dark";
|
||||
const divActive = "bg-mid shadow-inner-sm shadow-shade";
|
||||
const border =
|
||||
"outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent]";
|
||||
const divCommon = `gap-x-5 w-full rounded-2xl cursor-pointer p-4 hover:bg-mid hover:shadow-inner-sm hover:shadow-dark hover:active:shadow-inner hover:active:shadow-dark transition-all ${
|
||||
const divCommon = `gap-x-5 w-full rounded-2xl cursor-pointer p-4 hover:bg-mid hover:shadow-inner-sm hover:shadow-shade hover:active:shadow-inner hover:active:shadow-shade transition-all ${
|
||||
props.border ? border : ""
|
||||
} ${isActive ? divActive : ""}`;
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
|||
{mainPanelReduced && isDesktop ? (
|
||||
<div className="grid place-items-center gap-4">
|
||||
<Link href="/" passHref>
|
||||
<div className="w-12 cursor-pointer transition-[filter] hover:colorize-dark">
|
||||
<div className="w-12 cursor-pointer transition-[filter] colorize-black hover:colorize-dark">
|
||||
<SVG
|
||||
src={"/icons/accords.svg"}
|
||||
alt={"Logo of Accord's Library"}
|
||||
|
@ -55,25 +55,34 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
|||
<div>
|
||||
<div className="grid place-items-center">
|
||||
<Link href="/" passHref>
|
||||
<div className="w-1/2 cursor-pointer transition-[filter] hover:colorize-dark">
|
||||
<div className="w-1/2 cursor-pointer transition-[filter] colorize-black hover:colorize-dark">
|
||||
<SVG
|
||||
src={"/icons/accords.svg"}
|
||||
alt={"Logo of Accord's Library"}
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
<div
|
||||
className="relative mt-5"
|
||||
onClick={() => dispatch(setLanguagePanelOpen(true))}
|
||||
>
|
||||
{router.locale ? (
|
||||
<Button className="absolute right-0 top-[-1.3em] text-xs !py-0.5 !px-2.5">
|
||||
|
||||
<h2 className="text-3xl">Accord’s Library</h2>
|
||||
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
<Button
|
||||
id="themeButton"
|
||||
className="right-0 top-[-1.3em] !py-0.5 !px-2.5"
|
||||
>
|
||||
<span id="themeButtonIcon" className="material-icons !text-sm">
|
||||
dark_mode
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{router.locale && (
|
||||
<Button
|
||||
onClick={() => dispatch(setLanguagePanelOpen(true))}
|
||||
className="right-0 top-[-1.3em] text-sm !py-0.5 !px-2.5"
|
||||
>
|
||||
{router.locale.toUpperCase()}
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<h2 className="text-3xl">Accord’s Library</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -183,7 +192,7 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
|||
)}
|
||||
</p>
|
||||
<a
|
||||
className="transition-[filter] hover:colorize-dark"
|
||||
className="transition-[filter] colorize-black hover:colorize-dark"
|
||||
href="https://creativecommons.org/licenses/by-sa/4.0/"
|
||||
>
|
||||
<div className="mt-4 mb-8 grid grid-flow-col place-content-center gap-1">
|
||||
|
@ -213,7 +222,7 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
|||
</p>
|
||||
<div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8">
|
||||
<a
|
||||
className="transition-[filter] hover:colorize-dark"
|
||||
className="transition-[filter] colorize-black hover:colorize-dark"
|
||||
href="https://github.com/Accords-Library"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
@ -221,7 +230,7 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
|||
<SVG className="w-10" src={"/icons/github-brands.svg"} alt={""} />
|
||||
</a>
|
||||
<a
|
||||
className="transition-[filter] hover:colorize-dark"
|
||||
className="transition-[filter] colorize-black hover:colorize-dark"
|
||||
href="/discord"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
|
|
@ -43,7 +43,7 @@ class MyDocument extends Document {
|
|||
<link rel="manifest" href="manifest.json" />
|
||||
<meta name="theme-color" content="#FFEDD8" />
|
||||
</Head>
|
||||
<body className="bg-light text-black">
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
|
|
@ -28,7 +28,7 @@ export default function ContentIndex(props: ContentIndexProps): JSX.Element {
|
|||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/contents"
|
||||
title={langui.library_content}
|
||||
title={"Contents"}
|
||||
langui={langui}
|
||||
/>
|
||||
<HorizontalLine />
|
||||
|
|
|
@ -54,7 +54,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
|||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.library_items}
|
||||
title={langui.main_library}
|
||||
langui={langui}
|
||||
/>
|
||||
<HorizontalLine />
|
||||
|
@ -116,7 +116,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
|||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<div className="grid place-items-center gap-12">
|
||||
<div className="drop-shadow-dark-xl w-full h-[50vh] mobile:h-[80vh] mb-16 relative">
|
||||
<div className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[80vh] mb-16 relative cursor-pointer">
|
||||
{item.thumbnail.data ? (
|
||||
<Img
|
||||
image={item.thumbnail.data.attributes}
|
||||
|
@ -173,7 +173,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
|||
key={galleryItem.id}
|
||||
className="relative aspect-square hover:scale-[1.02] transition-transform cursor-pointer"
|
||||
>
|
||||
<div className="bg-light absolute inset-0 rounded-lg shadow-md"></div>
|
||||
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div>
|
||||
<Img
|
||||
className="rounded-lg"
|
||||
image={galleryItem.attributes}
|
||||
|
@ -375,7 +375,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
|||
<div
|
||||
id={content.attributes.slug}
|
||||
key={content.id}
|
||||
className="grid gap-2 px-4 rounded-lg target:bg-mid target:shadow-inner-sm target:shadow-dark target:h-auto target:py-3 target:my-2 target:[--displaySubContentMenu:grid] [--displaySubContentMenu:none]"
|
||||
className="grid gap-2 px-4 rounded-lg target:bg-mid target:shadow-inner-sm target:shadow-shade target:h-auto target:py-3 target:my-2 target:[--displaySubContentMenu:grid] [--displaySubContentMenu:none]"
|
||||
>
|
||||
<div className="grid gap-4 place-items-center grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]">
|
||||
<a href={`#${content.attributes.slug}`}>
|
||||
|
|
|
@ -68,6 +68,6 @@
|
|||
}
|
||||
|
||||
.prose footer > div:target {
|
||||
@apply bg-mid shadow-inner-sm shadow-dark;
|
||||
@apply bg-mid shadow-inner-sm shadow-shade;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,43 @@
|
|||
const plugin = require("tailwindcss/plugin");
|
||||
|
||||
/* CONFIG */
|
||||
|
||||
const light = { r: 255, g: 237, b: 216 };
|
||||
const mid = { r: 240, g: 209, b: 179 };
|
||||
const dark = { r: 156, g: 102, b: 68 };
|
||||
const shade = { r: 156, g: 102, b: 68 };
|
||||
const black = { r: 27, g: 24, b: 17 };
|
||||
|
||||
const dark_light = { r: 38, g: 34, b: 30 };
|
||||
const dark_mid = { r: 57, g: 45, b: 34 };
|
||||
const dark_dark = { r: 192, g: 132, b: 94 };
|
||||
const dark_shade = { r: 12, g: 6, b: 4 };
|
||||
const dark_black = { r: 235, g: 234, b: 231 };
|
||||
|
||||
const breakDektop = { min: "60rem" };
|
||||
const breakMobile = { max: "60rem" };
|
||||
const breakThin = { max: "25rem" };
|
||||
|
||||
/* END CONFIG */
|
||||
|
||||
module.exports = {
|
||||
darkMode: "class",
|
||||
content: ["./src/**/*.{tsx,ts}"],
|
||||
theme: {
|
||||
colors: {
|
||||
light: "rgb(255, 237, 216)",
|
||||
mid: "rgb(240, 209, 179)",
|
||||
dark: "rgb(156, 102, 68)",
|
||||
black: "rgb(27, 24, 17)",
|
||||
light: `rgb(${light.r}, ${light.g}, ${light.b})`,
|
||||
mid: `rgb(${mid.r}, ${mid.g}, ${mid.b})`,
|
||||
dark: `rgb(${dark.r}, ${dark.g}, ${dark.b})`,
|
||||
shade: `rgb(${shade.r}, ${shade.g}, ${shade.b})`,
|
||||
black: `rgb(${black.r}, ${black.g}, ${black.b})`,
|
||||
|
||||
// Dark mode
|
||||
|
||||
"dark-light": `rgb(${dark_light.r}, ${dark_light.g}, ${dark_light.b})`,
|
||||
"dark-mid": `rgb(${dark_mid.r}, ${dark_mid.g}, ${dark_mid.b})`,
|
||||
"dark-dark": `rgb(${dark_dark.r}, ${dark_dark.g}, ${dark_dark.b})`,
|
||||
"dark-shade": `rgb(${dark_shade.r}, ${dark_shade.g}, ${dark_shade.b})`,
|
||||
"dark-black": `rgb(${dark_black.r}, ${dark_black.g}, ${dark_black.b})`,
|
||||
},
|
||||
fontFamily: {
|
||||
body: ["Zen Maru Gothic"],
|
||||
|
@ -15,14 +45,15 @@ module.exports = {
|
|||
monospace: ["monospace"],
|
||||
},
|
||||
screens: {
|
||||
desktop: { min: "60rem" },
|
||||
mobile: { max: "60rem" },
|
||||
thin: { max: "25rem" },
|
||||
desktop: breakDektop,
|
||||
mobile: breakMobile,
|
||||
thin: breakThin,
|
||||
coarse: { raw: "(pointer: coarse)" },
|
||||
fine: { raw: "(pointer: fine)" },
|
||||
},
|
||||
backgroundImage: {
|
||||
paper: "url('/paper.webp')",
|
||||
"dark-paper": "url('/paper.webp')",
|
||||
},
|
||||
extend: {
|
||||
boxShadow: {
|
||||
|
@ -47,20 +78,31 @@ module.exports = {
|
|||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
".colorize-light": {
|
||||
filter:
|
||||
"brightness(0) saturate(100%) invert(98%) sepia(3%) saturate(5426%) hue-rotate(303deg) brightness(108%) contrast(100%)",
|
||||
filter: getFilterRecipe(light),
|
||||
},
|
||||
".colorize-mid": {
|
||||
filter:
|
||||
"brightness(0) saturate(100%) invert(89%) sepia(16%) saturate(829%) hue-rotate(322deg) brightness(103%) contrast(88%)",
|
||||
filter: getFilterRecipe(mid),
|
||||
},
|
||||
".colorize-dark": {
|
||||
filter:
|
||||
"brightness(0) saturate(100%) invert(43%) sepia(5%) saturate(4120%) hue-rotate(339deg) brightness(98%) contrast(90%)",
|
||||
filter: getFilterRecipe(dark),
|
||||
},
|
||||
".colorize-black": {
|
||||
filter:
|
||||
"brightness(0) saturate(100%) invert(7%) sepia(13%) saturate(1156%) hue-rotate(4deg) brightness(103%) contrast(95%)",
|
||||
filter: getFilterRecipe(black),
|
||||
},
|
||||
|
||||
// Dark mode
|
||||
|
||||
".colorize-dark-light": {
|
||||
filter: getFilterRecipe(dark_light),
|
||||
},
|
||||
".colorize-dark-mid": {
|
||||
filter: getFilterRecipe(dark_mid),
|
||||
},
|
||||
".colorize-dark-dark": {
|
||||
filter: getFilterRecipe(dark_dark),
|
||||
},
|
||||
".colorize-dark-black": {
|
||||
filter: getFilterRecipe(dark_black),
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
@ -68,39 +110,371 @@ module.exports = {
|
|||
// Colored Dropshadow
|
||||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
".drop-shadow-dark-lg": {
|
||||
filter:
|
||||
"drop-shadow(0 10px 8px rgb(156 102 68 / 0.2)) drop-shadow(0 4px 3px rgb(156 102 68 / 0.4))",
|
||||
".drop-shadow-shade-md": {
|
||||
filter: `drop-shadow(0 4px 3px rgb(${shade.r} ${shade.g} ${shade.b} / 0.15)) drop-shadow(0 2px 2px rgb(${shade.r} ${shade.g} ${shade.b} / 0.2))`,
|
||||
},
|
||||
".drop-shadow-dark-xl": {
|
||||
filter:
|
||||
"drop-shadow(0 20px 13px rgb(156 102 68 / 0.25)) drop-shadow(0 8px 5px rgb(156 102 68 / 0.7))",
|
||||
".drop-shadow-shade-lg": {
|
||||
filter: `drop-shadow(0 10px 8px rgb(${shade.r} ${shade.g} ${shade.b} / 0.2)) drop-shadow(0 4px 3px rgb(${shade.r} ${shade.g} ${shade.b} / 0.4))`,
|
||||
},
|
||||
".drop-shadow-dark-2xl": {
|
||||
filter: "drop-shadow(0 25px 25px rgb(156 102 68 / 0.8))",
|
||||
".drop-shadow-shade-xl": {
|
||||
filter: `drop-shadow(0 20px 13px rgb(${shade.r} ${shade.g} ${shade.b} / 0.25)) drop-shadow(0 8px 5px rgb(${shade.r} ${shade.g} ${shade.b} / 0.7))`,
|
||||
},
|
||||
".drop-shadow-shade-2xl": {
|
||||
filter: `drop-shadow(0 25px 25px rgb(${shade.r} ${shade.g} ${shade.b} / 0.8))`,
|
||||
},
|
||||
|
||||
".drop-shadow-black-md": {
|
||||
filter: `drop-shadow(0 4px 3px rgb(${black.r} ${black.g} ${black.b} / 0.15)) drop-shadow(0 2px 2px rgb(${black.r} ${black.g} ${black.b} / 0.2))`,
|
||||
},
|
||||
".drop-shadow-black-lg": {
|
||||
filter:
|
||||
"drop-shadow(0 10px 8px rgb(27 24 17 / 0.2)) drop-shadow(0 4px 3px rgb(27 24 17 / 0.4))",
|
||||
filter: `drop-shadow(0 10px 8px rgb(${black.r} ${black.g} ${black.b} / 0.2)) drop-shadow(0 4px 3px rgb(${black.r} ${black.g} ${black.b} / 0.4))`,
|
||||
},
|
||||
".drop-shadow-black-xl": {
|
||||
filter:
|
||||
"drop-shadow(0 20px 13px rgb(27 24 17 / 0.25)) drop-shadow(0 8px 5px rgb(27 24 17 / 0.7))",
|
||||
filter: `drop-shadow(0 20px 13px rgb(${black.r} ${black.g} ${black.b} / 0.25)) drop-shadow(0 8px 5px rgb(${black.r} ${black.g} ${black.b} / 0.7))`,
|
||||
},
|
||||
".drop-shadow-black-2xl": {
|
||||
filter: "drop-shadow(0 25px 25px rgb(27 24 17 / 0.8))",
|
||||
filter: `drop-shadow(0 25px 25px rgb(${black.r} ${black.g} ${black.b} / 0.8))`,
|
||||
},
|
||||
|
||||
// Dark mode
|
||||
|
||||
".drop-shadow-dark-shade-md": {
|
||||
filter: `drop-shadow(0 4px 3px rgb(${dark_shade.r} ${dark_shade.g} ${dark_shade.b} / 0.15)) drop-shadow(0 2px 2px rgb(${dark_shade.r} ${dark_shade.g} ${dark_shade.b} / 0.2))`,
|
||||
},
|
||||
".drop-shadow-dark-shade-lg": {
|
||||
filter: `drop-shadow(0 10px 8px rgb(${dark_shade.r} ${dark_shade.g} ${dark_shade.b} / 0.2)) drop-shadow(0 4px 3px rgb(${dark_shade.r} ${dark_shade.g} ${dark_shade.b} / 0.4))`,
|
||||
},
|
||||
".drop-shadow-dark-shade-xl": {
|
||||
filter: `drop-shadow(0 20px 13px rgb(${dark_shade.r} ${dark_shade.g} ${dark_shade.b} / 0.25)) drop-shadow(0 8px 5px rgb(${dark_shade.r} ${dark_shade.g} ${dark_shade.b} / 0.7))`,
|
||||
},
|
||||
".drop-shadow-dark-shade-2xl": {
|
||||
filter: `drop-shadow(0 25px 25px rgb(${dark_shade.r} ${dark_shade.g} ${dark_shade.b} / 0.8))`,
|
||||
},
|
||||
|
||||
".drop-shadow-dark-black-md": {
|
||||
filter: `drop-shadow(0 4px 3px rgb(${dark_black.r} ${dark_black.g} ${dark_black.b} / 0.15)) drop-shadow(0 2px 2px rgb(${dark_black.r} ${dark_black.g} ${dark_black.b} / 0.2))`,
|
||||
},
|
||||
".drop-shadow-dark-black-lg": {
|
||||
filter: `drop-shadow(0 10px 8px rgb(${dark_black.r} ${dark_black.g} ${dark_black.b} / 0.2)) drop-shadow(0 4px 3px rgb(${dark_black.r} ${dark_black.g} ${dark_black.b} / 0.4))`,
|
||||
},
|
||||
".drop-shadow-dark-black-xl": {
|
||||
filter: `drop-shadow(0 20px 13px rgb(${dark_black.r} ${dark_black.g} ${dark_black.b} / 0.25)) drop-shadow(0 8px 5px rgb(${dark_black.r} ${dark_black.g} ${dark_black.b} / 0.7))`,
|
||||
},
|
||||
".drop-shadow-dark-black-2xl": {
|
||||
filter: `drop-shadow(0 25px 25px rgb(${dark_black.r} ${dark_black.g} ${dark_black.b} / 0.8))`,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
".linearbg-1": {
|
||||
".linearbg-obi": {
|
||||
background:
|
||||
"linear-gradient(to right, theme('colors.mid'), theme('colors.light') 3%, theme('colors.light') 97%, theme('colors.mid'))",
|
||||
},
|
||||
".linearbg-dark-obi": {
|
||||
background:
|
||||
"linear-gradient(to right, theme('colors.dark-mid'), theme('colors.dark-light') 3%, theme('colors.dark-light') 97%, theme('colors.dark-mid'))",
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
/*
|
||||
The following is taken from https://codepen.io/sosuke/pen/Pjoqqp
|
||||
Used for colorizing any element using filters.
|
||||
*/
|
||||
|
||||
function getFilterRecipe(rgb) {
|
||||
const color = new FilterColorTransform(rgb.r, rgb.g, rgb.b);
|
||||
const solver = new Solver(color);
|
||||
const result = solver.solve();
|
||||
return result;
|
||||
}
|
||||
|
||||
class FilterColorTransform {
|
||||
constructor(r, g, b) {
|
||||
this.set(r, g, b);
|
||||
}
|
||||
|
||||
set(r, g, b) {
|
||||
this.r = this.clamp(r);
|
||||
this.g = this.clamp(g);
|
||||
this.b = this.clamp(b);
|
||||
}
|
||||
|
||||
hueRotate(angle = 0) {
|
||||
angle = (angle / 180) * Math.PI;
|
||||
const sin = Math.sin(angle);
|
||||
const cos = Math.cos(angle);
|
||||
|
||||
this.multiply([
|
||||
0.213 + cos * 0.787 - sin * 0.213,
|
||||
0.715 - cos * 0.715 - sin * 0.715,
|
||||
0.072 - cos * 0.072 + sin * 0.928,
|
||||
0.213 - cos * 0.213 + sin * 0.143,
|
||||
0.715 + cos * 0.285 + sin * 0.14,
|
||||
0.072 - cos * 0.072 - sin * 0.283,
|
||||
0.213 - cos * 0.213 - sin * 0.787,
|
||||
0.715 - cos * 0.715 + sin * 0.715,
|
||||
0.072 + cos * 0.928 + sin * 0.072,
|
||||
]);
|
||||
}
|
||||
|
||||
grayscale(value = 1) {
|
||||
this.multiply([
|
||||
0.2126 + 0.7874 * (1 - value),
|
||||
0.7152 - 0.7152 * (1 - value),
|
||||
0.0722 - 0.0722 * (1 - value),
|
||||
0.2126 - 0.2126 * (1 - value),
|
||||
0.7152 + 0.2848 * (1 - value),
|
||||
0.0722 - 0.0722 * (1 - value),
|
||||
0.2126 - 0.2126 * (1 - value),
|
||||
0.7152 - 0.7152 * (1 - value),
|
||||
0.0722 + 0.9278 * (1 - value),
|
||||
]);
|
||||
}
|
||||
|
||||
sepia(value = 1) {
|
||||
this.multiply([
|
||||
0.393 + 0.607 * (1 - value),
|
||||
0.769 - 0.769 * (1 - value),
|
||||
0.189 - 0.189 * (1 - value),
|
||||
0.349 - 0.349 * (1 - value),
|
||||
0.686 + 0.314 * (1 - value),
|
||||
0.168 - 0.168 * (1 - value),
|
||||
0.272 - 0.272 * (1 - value),
|
||||
0.534 - 0.534 * (1 - value),
|
||||
0.131 + 0.869 * (1 - value),
|
||||
]);
|
||||
}
|
||||
|
||||
saturate(value = 1) {
|
||||
this.multiply([
|
||||
0.213 + 0.787 * value,
|
||||
0.715 - 0.715 * value,
|
||||
0.072 - 0.072 * value,
|
||||
0.213 - 0.213 * value,
|
||||
0.715 + 0.285 * value,
|
||||
0.072 - 0.072 * value,
|
||||
0.213 - 0.213 * value,
|
||||
0.715 - 0.715 * value,
|
||||
0.072 + 0.928 * value,
|
||||
]);
|
||||
}
|
||||
|
||||
multiply(matrix) {
|
||||
const newR = this.clamp(
|
||||
this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]
|
||||
);
|
||||
const newG = this.clamp(
|
||||
this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]
|
||||
);
|
||||
const newB = this.clamp(
|
||||
this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]
|
||||
);
|
||||
this.r = newR;
|
||||
this.g = newG;
|
||||
this.b = newB;
|
||||
}
|
||||
|
||||
brightness(value = 1) {
|
||||
this.linear(value);
|
||||
}
|
||||
contrast(value = 1) {
|
||||
this.linear(value, -(0.5 * value) + 0.5);
|
||||
}
|
||||
|
||||
linear(slope = 1, intercept = 0) {
|
||||
this.r = this.clamp(this.r * slope + intercept * 255);
|
||||
this.g = this.clamp(this.g * slope + intercept * 255);
|
||||
this.b = this.clamp(this.b * slope + intercept * 255);
|
||||
}
|
||||
|
||||
invert(value = 1) {
|
||||
this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255);
|
||||
this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255);
|
||||
this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255);
|
||||
}
|
||||
|
||||
hsl() {
|
||||
// Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA.
|
||||
const r = this.r / 255;
|
||||
const g = this.g / 255;
|
||||
const b = this.b / 255;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
let h,
|
||||
s,
|
||||
l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0;
|
||||
} else {
|
||||
const d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return {
|
||||
h: h * 100,
|
||||
s: s * 100,
|
||||
l: l * 100,
|
||||
};
|
||||
}
|
||||
|
||||
clamp(value) {
|
||||
if (value > 255) {
|
||||
value = 255;
|
||||
} else if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class Solver {
|
||||
constructor(target) {
|
||||
this.target = target;
|
||||
this.targetHSL = target.hsl();
|
||||
this.reusedColor = new FilterColorTransform(0, 0, 0);
|
||||
}
|
||||
|
||||
solve() {
|
||||
const result = this.solveNarrow(this.solveWide());
|
||||
return this.css(result.values);
|
||||
}
|
||||
|
||||
solveWide() {
|
||||
const A = 5;
|
||||
const c = 15;
|
||||
const a = [60, 180, 18000, 600, 1.2, 1.2];
|
||||
|
||||
let best = { loss: Infinity };
|
||||
for (let i = 0; best.loss > 25 && i < 3; i++) {
|
||||
const initial = [50, 20, 3750, 50, 100, 100];
|
||||
const result = this.spsa(A, a, c, initial, 1000);
|
||||
if (result.loss < best.loss) {
|
||||
best = result;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
solveNarrow(wide) {
|
||||
const A = wide.loss;
|
||||
const c = 2;
|
||||
const A1 = A + 1;
|
||||
const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
|
||||
return this.spsa(A, a, c, wide.values, 500);
|
||||
}
|
||||
|
||||
spsa(A, a, c, values, iters) {
|
||||
const alpha = 1;
|
||||
const gamma = 0.16666666666666666;
|
||||
|
||||
let best = null;
|
||||
let bestLoss = Infinity;
|
||||
const deltas = new Array(6);
|
||||
const highArgs = new Array(6);
|
||||
const lowArgs = new Array(6);
|
||||
|
||||
for (let k = 0; k < iters; k++) {
|
||||
const ck = c / Math.pow(k + 1, gamma);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
deltas[i] = Math.random() > 0.5 ? 1 : -1;
|
||||
highArgs[i] = values[i] + ck * deltas[i];
|
||||
lowArgs[i] = values[i] - ck * deltas[i];
|
||||
}
|
||||
|
||||
const lossDiff = this.loss(highArgs) - this.loss(lowArgs);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const g = (lossDiff / (2 * ck)) * deltas[i];
|
||||
const ak = a[i] / Math.pow(A + k + 1, alpha);
|
||||
values[i] = fix(values[i] - ak * g, i);
|
||||
}
|
||||
|
||||
const loss = this.loss(values);
|
||||
if (loss < bestLoss) {
|
||||
best = values.slice(0);
|
||||
bestLoss = loss;
|
||||
}
|
||||
}
|
||||
return { values: best, loss: bestLoss };
|
||||
|
||||
function fix(value, idx) {
|
||||
let max = 100;
|
||||
if (idx === 2 /* saturate */) {
|
||||
max = 7500;
|
||||
} else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) {
|
||||
max = 200;
|
||||
}
|
||||
|
||||
if (idx === 3 /* hue-rotate */) {
|
||||
if (value > max) {
|
||||
value %= max;
|
||||
} else if (value < 0) {
|
||||
value = max + (value % max);
|
||||
}
|
||||
} else if (value < 0) {
|
||||
value = 0;
|
||||
} else if (value > max) {
|
||||
value = max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
loss(filters) {
|
||||
// Argument is array of percentages.
|
||||
const color = this.reusedColor;
|
||||
color.set(0, 0, 0);
|
||||
|
||||
color.invert(filters[0] / 100);
|
||||
color.sepia(filters[1] / 100);
|
||||
color.saturate(filters[2] / 100);
|
||||
color.hueRotate(filters[3] * 3.6);
|
||||
color.brightness(filters[4] / 100);
|
||||
color.contrast(filters[5] / 100);
|
||||
|
||||
const colorHSL = color.hsl();
|
||||
return (
|
||||
Math.abs(color.r - this.target.r) +
|
||||
Math.abs(color.g - this.target.g) +
|
||||
Math.abs(color.b - this.target.b) +
|
||||
Math.abs(colorHSL.h - this.targetHSL.h) +
|
||||
Math.abs(colorHSL.s - this.targetHSL.s) +
|
||||
Math.abs(colorHSL.l - this.targetHSL.l)
|
||||
);
|
||||
}
|
||||
|
||||
css(filters) {
|
||||
function fmt(idx, multiplier = 1) {
|
||||
return Math.round(filters[idx] * multiplier);
|
||||
}
|
||||
return `
|
||||
brightness(0)
|
||||
saturate(100%)
|
||||
invert(${fmt(0)}%)
|
||||
sepia(${fmt(1)}%)
|
||||
saturate(${fmt(2)}%)
|
||||
hue-rotate(${fmt(3, 3.6)}deg)
|
||||
brightness(${fmt(4)}%)
|
||||
contrast(${fmt(5)}%);
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue