diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index 9599632..a0070b9 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -1,6 +1,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; -import styles from "@styles/Button.module.css"; +import styles from "@styles/components/Button.module.css"; +import { cn } from "@utils/classNames"; interface ButtonProps { icon?: IconDefinition; @@ -25,36 +26,24 @@ export const Button: React.FC = ({ target, rel, }) => { - const buttonClassName = `${styles.button} ${styles[variant]} ${className || ""}`; - - const buttonContent = ( - <> - {icon && } + const ButtonComponent = ({ className }: { className: string }) => ( + ); if (href) { return ( - + ); } - return ( - - ); + return ; }; diff --git a/src/components/slideshow/Controls.tsx b/src/components/slideshow/Controls.tsx index 695fbc7..f9689e1 100644 --- a/src/components/slideshow/Controls.tsx +++ b/src/components/slideshow/Controls.tsx @@ -1,5 +1,5 @@ import { faXmark, faDownload } from "@fortawesome/free-solid-svg-icons"; -import styles from "@styles/Slideshow.module.css"; +import styles from "@styles/components/slideshow/Controls.module.css"; import { Button } from "@components/common/Button"; interface ControlsProps { diff --git a/src/components/slideshow/FullscreenImage.tsx b/src/components/slideshow/FullscreenImage.tsx index d6edf1a..1d40d67 100644 --- a/src/components/slideshow/FullscreenImage.tsx +++ b/src/components/slideshow/FullscreenImage.tsx @@ -1,5 +1,5 @@ -import styles from "@styles/Slideshow.module.css"; -import animations from "@styles/animations.module.css"; +import styles from "@styles/components/slideshow/FullscreenImage.module.css"; +import { cn } from "@utils/classNames"; import type { SlideImage } from "@types"; interface FullscreenImageProps { @@ -19,22 +19,16 @@ export const FullscreenImage: React.FC = ({ onImageError, onClick, }) => ( -
+
{image.alt} onImageError(image.id)} onLoad={(e) => e.currentTarget.classList.add(styles.loaded)} /> diff --git a/src/components/slideshow/Loading.tsx b/src/components/slideshow/Loading.tsx index 75be1d9..9368bbd 100644 --- a/src/components/slideshow/Loading.tsx +++ b/src/components/slideshow/Loading.tsx @@ -1,4 +1,4 @@ -import styles from "@styles/Slideshow.module.css"; +import styles from "@styles/components/slideshow/Loading.module.css"; export const Loading: React.FC = () => (
Loading images...
diff --git a/src/components/slideshow/Slideshow.tsx b/src/components/slideshow/Slideshow.tsx index cf97fdb..33a6de7 100644 --- a/src/components/slideshow/Slideshow.tsx +++ b/src/components/slideshow/Slideshow.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { preloadImages } from "@utils/imagePreloader"; import { useSlideshow } from "@hooks/useSlideshow"; -import styles from "@styles/Slideshow.module.css"; +import styles from "@styles/components/slideshow/Slideshow.module.css"; import type { SlideImage } from "@types"; import { getImagePath } from "@config/images"; import { Controls } from "@components/slideshow/Controls"; diff --git a/src/components/slideshow/Thumbnails.tsx b/src/components/slideshow/Thumbnails.tsx index 7060e86..d8cc368 100644 --- a/src/components/slideshow/Thumbnails.tsx +++ b/src/components/slideshow/Thumbnails.tsx @@ -1,4 +1,4 @@ -import styles from "@styles/Slideshow.module.css"; +import styles from "@styles/components/slideshow/Thumbnails.module.css"; import type { SlideImage } from "@types"; interface ThumbnailsProps { diff --git a/src/styles/Button.module.css b/src/styles/Button.module.css deleted file mode 100644 index 147405d..0000000 --- a/src/styles/Button.module.css +++ /dev/null @@ -1,65 +0,0 @@ -.button { - background: rgba(0, 0, 0, 0.5); - border: none; - width: 40px; - height: 40px; - border-radius: 50%; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.7; - transition: all 0.3s ease; - padding: 0; - color: white; -} - -.button:hover { - opacity: 1; - background: rgba(0, 0, 0, 0.8); -} - -.button svg { - width: 20px; - height: 20px; -} - -.close, -.download { - width: 40px; - height: 40px; -} - -.github { - background: none; -} - -.github svg { - width: 30px; - height: 30px; -} - -.link { - text-decoration: none; - position: fixed; /* Add this */ - top: 20px; /* Add this */ - right: 20px; /* Add this */ - z-index: 1002; /* Add this */ -} - -@media (max-width: 768px) { - .button { - width: 35px; - height: 35px; - } - - .github svg { - width: 25px; - height: 25px; - } - - .link { - top: 10px; - right: 10px; - } -} diff --git a/src/styles/animations.module.css b/src/styles/common/animations.module.css similarity index 100% rename from src/styles/animations.module.css rename to src/styles/common/animations.module.css diff --git a/src/styles/common/base.css b/src/styles/common/base.css new file mode 100644 index 0000000..483c1e6 --- /dev/null +++ b/src/styles/common/base.css @@ -0,0 +1,23 @@ +.noInteraction { + touch-action: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; + -webkit-user-select: none; + user-select: none; + overscroll-behavior: none; +} + +.image-base { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.button-base { + background: var(--color-overlay); + border: none; + border-radius: 50%; + cursor: pointer; + opacity: 0.7; + transition: var(--transition-default); +} diff --git a/src/styles/common/global.css b/src/styles/common/global.css new file mode 100644 index 0000000..07b7eb9 --- /dev/null +++ b/src/styles/common/global.css @@ -0,0 +1,20 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: #000; + overscroll-behavior-y: none; + overflow: hidden; +} + +img { + -webkit-user-drag: none; + user-select: none; +} diff --git a/src/styles/common/variables.css b/src/styles/common/variables.css new file mode 100644 index 0000000..dbd6f3d --- /dev/null +++ b/src/styles/common/variables.css @@ -0,0 +1,11 @@ +:root { + --primary-color: #717171; + --secondary-color: #bbb; + --background-dark: rgba(0, 0, 0, 0.5); + --background-darker: rgba(0, 0, 0, 0.8); + --transition-default: all 0.3s ease; + --border-radius-sm: 4px; + --border-radius-lg: 20px; + --spacing-sm: 10px; + --spacing-md: 20px; +} diff --git a/src/styles/components/Button.module.css b/src/styles/components/Button.module.css new file mode 100644 index 0000000..06c7e38 --- /dev/null +++ b/src/styles/components/Button.module.css @@ -0,0 +1,50 @@ +.button { + composes: button-base from "/src/styles/common/base.css"; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--button-size, 40px); + height: var(--button-size, 40px); + padding: 0; + color: var(--color-text); +} + +.button:hover { + opacity: 1; + background: var(--color-overlay-dark); +} + +.icon { + width: calc(var(--button-size, 40px) * 0.5); + height: calc(var(--button-size, 40px) * 0.5); +} + +/* Variants */ +.close, +.download { + --button-size: 40px; +} + +.github { + background: none; +} + +.link { + text-decoration: none; + position: fixed; + top: var(--spacing-md); + right: var(--spacing-md); + z-index: var(--z-controls); +} + +/* Media Queries */ +@media (max-width: var(--mobile-width)) { + .button { + --button-size: 35px; + } + + .link { + top: var(--spacing-sm); + right: var(--spacing-sm); + } +} diff --git a/src/styles/components/slideshow/FullscreenImage.module.css b/src/styles/components/slideshow/FullscreenImage.module.css index 6e3aa8b..4d4106a 100644 --- a/src/styles/components/slideshow/FullscreenImage.module.css +++ b/src/styles/components/slideshow/FullscreenImage.module.css @@ -1,33 +1,53 @@ +.slide { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + opacity: 1; + transition: opacity var(--transition-duration) ease; +} + +.hidden { + display: none; +} + +.fadeIn { + animation: fadeIn var(--transition-duration) ease; +} + .imageWrapper { + composes: noInteraction from "/src/styles/common/base.css"; position: relative; display: flex; justify-content: center; align-items: center; - transform-origin: center; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); width: 100%; height: 100%; cursor: pointer; - touch-action: none; - -webkit-touch-callout: none; - -webkit-tap-highlight-color: transparent; - -webkit-user-select: none; - user-select: none; - overscroll-behavior: none; + transform-origin: center; } .mainImage { + composes: image-base from "/src/styles/common/base.css"; opacity: 0; - transition: opacity 0.3s ease; - border-radius: 20px; - max-height: 100%; - max-width: 100%; + transition: opacity var(--transition-duration) ease; + border-radius: var(--border-radius-lg); +} + +.fullscreen { + cursor: zoom-in; } -.mainImage.loaded { +.loaded { opacity: 1; } -.fullscreen .mainImage { - border-radius: 8px; +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } } diff --git a/src/styles/components/slideshow/Loading.module.css b/src/styles/components/slideshow/Loading.module.css new file mode 100644 index 0000000..ab865ff --- /dev/null +++ b/src/styles/components/slideshow/Loading.module.css @@ -0,0 +1,16 @@ +.loading { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 1.2rem; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 8px; + backdrop-filter: blur(4px); +} diff --git a/src/styles/Slideshow.module.css b/src/styles/components/slideshow/Slideshow.module.css similarity index 100% rename from src/styles/Slideshow.module.css rename to src/styles/components/slideshow/Slideshow.module.css diff --git a/src/styles/index.css b/src/styles/index.css new file mode 100644 index 0000000..487e162 --- /dev/null +++ b/src/styles/index.css @@ -0,0 +1,3 @@ +@import "./common/variables.css"; +@import "./common/global.css"; +@import "./common/animations.module.css"; diff --git a/src/utils/classNames.ts b/src/utils/classNames.ts new file mode 100644 index 0000000..8f88396 --- /dev/null +++ b/src/utils/classNames.ts @@ -0,0 +1,3 @@ +export const cn = (...classes: (string | boolean | undefined)[]) => { + return classes.filter(Boolean).join(" "); +};