Added foundation for dark theme support
This commit is contained in:
		
							parent
							
								
									2b2a9c41e2
								
							
						
					
					
						commit
						a39313c655
					
				
							
								
								
									
										29
									
								
								public/js/toggleTheme.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								public/js/toggleTheme.js
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint