├── .env.example
├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── images
│ ├── bg_image.jpg
│ ├── bg_mobile.jpg
│ ├── hero_desc.png
│ ├── hero_title.png
│ └── members
│ │ ├── chaeyoung.jpg
│ │ ├── dahyun.jpg
│ │ ├── jeongyeon.jpg
│ │ ├── jihyo.jpg
│ │ ├── mina.jpg
│ │ ├── momo.jpg
│ │ ├── nayeon.jpg
│ │ ├── sana.jpg
│ │ └── tzuyu.jpg
├── logo.svg
└── site.webmanifest
├── src
├── components
│ ├── content
│ │ ├── contentItem.tsx
│ │ ├── index.ts
│ │ └── latestContent.tsx
│ ├── discography
│ │ ├── discographyItem.tsx
│ │ ├── discographySection.tsx
│ │ ├── index.ts
│ │ └── newestDiscographySection.tsx
│ ├── footer
│ │ └── index.tsx
│ ├── header
│ │ ├── index.tsx
│ │ ├── navData.ts
│ │ └── navLink.tsx
│ ├── layout
│ │ ├── container.tsx
│ │ ├── index.ts
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ └── section.tsx
│ ├── loadingScreen.tsx
│ ├── memberItem.tsx
│ ├── news
│ │ ├── index.ts
│ │ ├── latestNewsSection.tsx
│ │ ├── newsItem.tsx
│ │ └── newsSection.tsx
│ ├── scrollDownAnimation.tsx
│ └── verticalSocmedList.tsx
├── lib
│ ├── developersPage.json
│ ├── members.json
│ ├── models
│ │ ├── Contents.ts
│ │ ├── Discography.ts
│ │ ├── News.ts
│ │ └── index.ts
│ └── utils
│ │ ├── clsx.ts
│ │ ├── dbConnect.ts
│ │ ├── opacityAnimation.ts
│ │ └── useData.ts
├── pages
│ ├── 404.tsx
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── api
│ │ ├── discography
│ │ │ ├── [id].ts
│ │ │ └── index.ts
│ │ ├── members.ts
│ │ └── news
│ │ │ ├── [id].ts
│ │ │ └── index.ts
│ ├── developers.tsx
│ ├── discography
│ │ ├── [id].tsx
│ │ └── index.tsx
│ ├── index.tsx
│ ├── news
│ │ ├── [id].tsx
│ │ └── index.tsx
│ └── profile.tsx
├── styles
│ ├── footer.module.css
│ ├── globals.css
│ └── header.module.css
└── types
│ ├── components.ts
│ └── default.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | MONGO_URI=
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true,
6 | "jest": true
7 | },
8 | "parser": "@typescript-eslint/parser",
9 | "plugins": ["@typescript-eslint"],
10 | "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended", "prettier"],
11 | "rules": {
12 | "prefer-const": "error",
13 | "@typescript-eslint/no-unused-vars": "error",
14 | "@typescript-eslint/no-explicit-any": "error",
15 | "@typescript-eslint/no-empty-interface": [
16 | "error",
17 | {
18 | "allowSingleExtends": false
19 | }
20 | ],
21 | "quotes": "off",
22 | "@typescript-eslint/quotes": ["error"],
23 | "no-extra-semi": "off",
24 | "@typescript-eslint/no-extra-semi": ["error"]
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
36 |
37 | # typescript
38 | *.tsbuildinfo
39 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .next
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TWICE Website from ONCE
2 |
3 | This website was created by ONCE. You can use the api used for this website at `/developers`
4 |
5 | ## Environment
6 |
7 | ```env
8 | MONGO_URI=
9 | ```
10 |
11 | ## Run this website locally
12 |
13 | To run this project locally.
14 |
15 | ```bash
16 | # install dependencies
17 | npm install
18 |
19 | # serve with hot reload at localhost:3000
20 | npm run dev
21 |
22 | # build for production
23 | npm run build
24 | ```
25 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | skipLibCheck: true,
5 | images: {
6 | domains: ["i.ytimg.com", "lv2-cdn.azureedge.net", "s3-aop.plusmember.jp", "scontent.cdninstagram.com", "v.phinf.naver.net", "i0.wp.com"],
7 | },
8 | };
9 |
10 | module.exports = nextConfig;
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-starter-template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "lint:fix": "next lint --fix"
11 | },
12 | "dependencies": {
13 | "@tanem/react-nprogress": "^4.0.10",
14 | "dotenv": "^16.0.1",
15 | "mongoose": "^6.3.3",
16 | "next": "12.1.0",
17 | "react": "17.0.2",
18 | "react-dom": "17.0.2",
19 | "react-icons": "^4.3.1",
20 | "swr": "^1.3.0"
21 | },
22 | "devDependencies": {
23 | "@types/node": "^15.14.1",
24 | "@types/react": "17.0.40",
25 | "@typescript-eslint/eslint-plugin": "^5.15.0",
26 | "@typescript-eslint/parser": "^5.15.0",
27 | "eslint": "8.11.0",
28 | "eslint-config-next": "12.1.0",
29 | "eslint-config-prettier": "^8.5.0",
30 | "prettier": "^2.6.0",
31 | "typescript": "^4.6.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/bg_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/bg_image.jpg
--------------------------------------------------------------------------------
/public/images/bg_mobile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/bg_mobile.jpg
--------------------------------------------------------------------------------
/public/images/hero_desc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/hero_desc.png
--------------------------------------------------------------------------------
/public/images/hero_title.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/hero_title.png
--------------------------------------------------------------------------------
/public/images/members/chaeyoung.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/chaeyoung.jpg
--------------------------------------------------------------------------------
/public/images/members/dahyun.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/dahyun.jpg
--------------------------------------------------------------------------------
/public/images/members/jeongyeon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/jeongyeon.jpg
--------------------------------------------------------------------------------
/public/images/members/jihyo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/jihyo.jpg
--------------------------------------------------------------------------------
/public/images/members/mina.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/mina.jpg
--------------------------------------------------------------------------------
/public/images/members/momo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/momo.jpg
--------------------------------------------------------------------------------
/public/images/members/nayeon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/nayeon.jpg
--------------------------------------------------------------------------------
/public/images/members/sana.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/sana.jpg
--------------------------------------------------------------------------------
/public/images/members/tzuyu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhafitf/twice/69074b68cd2a6c5e00cb1fed1b2e9f7f8c5096b8/public/images/members/tzuyu.jpg
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/src/components/content/contentItem.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { ContentItemProps } from "~types/components";
3 |
4 | export default function ContentItem({ href, thumb, title, tag }: ContentItemProps) {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
{tag}
12 |
13 | {title}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/content/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ContentItem } from "./contentItem";
2 | export { default as LatestContent } from "./latestContent";
3 |
--------------------------------------------------------------------------------
/src/components/content/latestContent.tsx:
--------------------------------------------------------------------------------
1 | import ContentItem from "./contentItem";
2 | import { ContentItemProps } from "~types/components";
3 |
4 | interface IContentListProps {
5 | data: ContentItemProps[];
6 | }
7 |
8 | export default function LatestContent({ data }: IContentListProps) {
9 | const renderItems = () => {
10 | if (data) {
11 | return data.map((item: ContentItemProps, index: number) => {
12 | return ;
13 | });
14 | }
15 | };
16 |
17 | return
{renderItems()}
;
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/discography/discographyItem.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { DiscographyType } from "~types/components";
3 | import Image from "next/image";
4 |
5 | interface DiscographyItemProps {
6 | items?: DiscographyType;
7 | isSkeleton?: boolean;
8 | }
9 |
10 | export default function DiscographyItem({ items, isSkeleton }: DiscographyItemProps) {
11 | const renderItem = () => {
12 | if (items && !isSkeleton) {
13 | const { _id, type, name, releaseDate, coverArt } = items;
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
{type}
22 |
{name}
23 |
{releaseDate} Release
24 |
25 |
26 |
27 | );
28 | }
29 | return (
30 |
33 | );
34 | };
35 | return <>{renderItem()}>;
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/discography/discographySection.tsx:
--------------------------------------------------------------------------------
1 | import useData from "~lib/utils/useData";
2 | import DiscographyItem from "./discographyItem";
3 | import { DiscographyType } from "~types/components";
4 |
5 | export default function DiscographySection() {
6 | const { data } = useData("/api/discography");
7 | const renderItems = () => {
8 | if (data) {
9 | return data.map((item: DiscographyType) => {
10 | return ;
11 | });
12 | }
13 |
14 | return Array.from({ length: 6 }).map((_, index) => {
15 | return ;
16 | });
17 | };
18 | return {renderItems()}
;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/discography/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DiscographyItem } from "./discographyItem";
2 | export { default as DiscographySection } from "./discographySection";
3 | export { default as NewestDiscographySection } from "./newestDiscographySection";
4 |
--------------------------------------------------------------------------------
/src/components/discography/newestDiscographySection.tsx:
--------------------------------------------------------------------------------
1 | import useData from "~lib/utils/useData";
2 | import DiscographyItem from "./discographyItem";
3 | import { DiscographyType } from "~types/components";
4 |
5 | export default function NewestDiscographySection() {
6 | const { data } = useData("/api/discography");
7 | const renderItems = () => {
8 | if (data) {
9 | return data.slice(0, 6).map((item: DiscographyType) => {
10 | return ;
11 | });
12 | }
13 |
14 | return Array.from({ length: 6 }).map((_, index) => {
15 | return ;
16 | });
17 | };
18 | return {renderItems()}
;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/footer/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-no-target-blank */
2 | import styles from "~styles/footer.module.css";
3 | import Link from "next/link";
4 | import Image from "next/image";
5 | import { Container } from "../layout";
6 |
7 | export default function Footer() {
8 | return (
9 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/header/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from "~styles/header.module.css";
2 | import Link from "next/link";
3 | import Image from "next/image";
4 | import NavLink from "./navLink";
5 | import { left, right } from "./navData";
6 | import { useEffect, useState } from "react";
7 | import clsx from "~lib/utils/clsx";
8 | import { useRouter } from "next/router";
9 | import { addOpacity } from "~lib/utils/opacityAnimation";
10 |
11 | export default function Header() {
12 | const [isFixed, setIsFixed] = useState(false);
13 | const [isOpen, setIsOpen] = useState(false);
14 |
15 | const router = useRouter();
16 | const burgerStyleNotHome = router.pathname === "/" ? {} : { background: "#434445" };
17 |
18 | useEffect(() => {
19 | const handleScroll = () => {
20 | const scrollTop = window.pageYOffset;
21 | if (scrollTop > 70) {
22 | setIsFixed(true);
23 | } else {
24 | setIsFixed(false);
25 | }
26 | };
27 | window.addEventListener("scroll", handleScroll);
28 | return () => {
29 | window.removeEventListener("scroll", handleScroll);
30 | };
31 | }, []);
32 |
33 | useEffect(() => {
34 | isOpen ? document.body.classList.add("overflow-hidden") : document.body.classList.remove("overflow-hidden");
35 | }, [isOpen]);
36 |
37 | let opacity = 0;
38 |
39 | const handleClickNav = () => {
40 | isOpen
41 | ? (opacity = 0)
42 | : addOpacity({
43 | opacity: opacity,
44 | DOMByID: "nav",
45 | time: 10,
46 | });
47 | };
48 |
49 | return (
50 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/src/components/header/navData.ts:
--------------------------------------------------------------------------------
1 | export const left = [
2 | { href: "/", text: "Home" },
3 | { href: "/news", text: "News" },
4 | ];
5 |
6 | export const right = [
7 | { href: "/profile", text: "Profile" },
8 | { href: "/discography", text: "Discography" },
9 | ];
--------------------------------------------------------------------------------
/src/components/header/navLink.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { NavLinkProps } from "~types/components";
3 |
4 | const NavLink = ({ className, href, text }: NavLinkProps) => {
5 | return (
6 |
7 |
8 | {text}
9 |
10 |
11 | );
12 | };
13 |
14 | export default NavLink;
15 |
--------------------------------------------------------------------------------
/src/components/layout/container.tsx:
--------------------------------------------------------------------------------
1 | import { ContainerProps } from "~types/components";
2 | import clsx from "~lib/utils/clsx";
3 |
4 | export default function Container({ children, className, style, isLarge, isSmall }: ContainerProps) {
5 | return (
6 | <>
7 |
8 | {children}
9 |
10 | >
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/layout/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Layout } from "./layout";
2 | export { default as Container } from "./container";
3 | export { default as Section } from "./section";
4 |
--------------------------------------------------------------------------------
/src/components/layout/layout.tsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import { LayoutProps } from "~types/components";
3 | import Footer from "../footer";
4 | import Header from "../header";
5 |
6 | export default function Layout(props: LayoutProps) {
7 | const { children, title, metaDescription, metaImage, style } = props;
8 | const image = metaImage || "/images/bg_image.jpg";
9 | return (
10 | <>
11 |
12 | {title}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {children}
27 |
28 |
29 | >
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/layout/loading.tsx:
--------------------------------------------------------------------------------
1 | import { useNProgress } from "@tanem/react-nprogress";
2 |
3 | const Loading: React.FC<{ isRouteChanging: boolean }> = ({ isRouteChanging }) => {
4 | const { animationDuration, isFinished, progress } = useNProgress({
5 | isAnimating: isRouteChanging,
6 | });
7 |
8 | return (
9 | <>
10 |
38 |
43 | >
44 | );
45 | };
46 |
47 | export default Loading;
48 |
--------------------------------------------------------------------------------
/src/components/layout/section.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import clsx from "~lib/utils/clsx";
3 | import { SectionProps } from "~types/components";
4 |
5 | export default function Section({ title, children, className, isHomePage, withLoadMore }: SectionProps) {
6 | const href = title.toLowerCase().replace(/\s/g, "-");
7 | return (
8 | <>
9 |
10 |
11 |
{title}
12 | {withLoadMore && (
13 |
14 |
15 | Load More
16 |
17 |
18 | )}
19 |
20 | {children}
21 | {withLoadMore && (
22 |
27 | )}
28 |
29 | >
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/loadingScreen.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | interface LoadingScreenProps {
4 | isLoading: boolean;
5 | }
6 |
7 | export default function LoadingScreen({ isLoading }: LoadingScreenProps) {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/memberItem.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { MemberItemProps } from "~/src/types/components";
3 |
4 | interface Props extends MemberItemProps {
5 | onClick?: () => void;
6 | }
7 |
8 | export default function MemberItem({ onClick, image, name, nickname }: Props) {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
{nickname}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/news/index.ts:
--------------------------------------------------------------------------------
1 | export { default as NewsItem } from "./newsItem";
2 | export { default as LatestNewsSection } from "./latestNewsSection";
3 | export { default as NewsSection } from "./newsSection";
4 |
--------------------------------------------------------------------------------
/src/components/news/latestNewsSection.tsx:
--------------------------------------------------------------------------------
1 | import useData from "~lib/utils/useData";
2 | import NewsItem from "./newsItem";
3 | import { NewsType } from "~types/components";
4 |
5 | export default function LatestNewsSection() {
6 | const { data } = useData("/api/news");
7 | const renderItems = () => {
8 | if (data) {
9 | return data.slice(0, 6).map((item: NewsType) => {
10 | return ;
11 | });
12 | }
13 |
14 | return Array.from({ length: 6 }).map((_, index) => {
15 | return ;
16 | });
17 | };
18 | return {renderItems()}
;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/news/newsItem.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { NewsType } from "~types/components";
3 |
4 | interface NewsItemProps {
5 | items?: NewsType;
6 | isSkeleton?: boolean;
7 | }
8 |
9 | export default function NewsItem({ items, isSkeleton }: NewsItemProps) {
10 | const renderItem = () => {
11 | if (items && !isSkeleton) {
12 | const { _id, title, date } = items;
13 | return (
14 |
15 |
16 | {date}
17 | {title}
18 |
19 |
20 | );
21 | }
22 | return (
23 |
27 | );
28 | };
29 | return <>{renderItem()}>;
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/news/newsSection.tsx:
--------------------------------------------------------------------------------
1 | import useData from "~lib/utils/useData";
2 | import NewsItem from "./newsItem";
3 | import { NewsType } from "~types/components";
4 |
5 | export default function LatestNewsSection() {
6 | const { data } = useData("/api/news");
7 | const renderItems = () => {
8 | if (data) {
9 | return data.map((item: NewsType) => {
10 | return ;
11 | });
12 | }
13 |
14 | return Array.from({ length: 6 }).map((_, index) => {
15 | return ;
16 | });
17 | };
18 | return {renderItems()}
;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/scrollDownAnimation.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import clsx from "~lib/utils/clsx";
3 |
4 | export default function ScrollDownAnimation() {
5 | const [isFixed, setIsFixed] = useState(false);
6 |
7 | useEffect(() => {
8 | const handleScroll = () => {
9 | const scrollTop = window.pageYOffset;
10 | if (scrollTop > 70) {
11 | setIsFixed(true);
12 | } else {
13 | setIsFixed(false);
14 | }
15 | };
16 | window.addEventListener("scroll", handleScroll);
17 | return () => {
18 | window.removeEventListener("scroll", handleScroll);
19 | };
20 | }, []);
21 |
22 | return (
23 | <>
24 |
25 | >
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/verticalSocmedList.tsx:
--------------------------------------------------------------------------------
1 | import { AiOutlineInstagram, AiOutlineTwitter } from "react-icons/ai";
2 | import { BsYoutube } from "react-icons/bs";
3 | import { FaTiktok } from "react-icons/fa";
4 |
5 | interface LinksProps {
6 | href: string;
7 | icon: JSX.Element;
8 | ariaLabel: string;
9 | }
10 |
11 | const twiceKorea: LinksProps[] = [
12 | { href: "https://www.instagram.com/twicetagram/", icon: , ariaLabel: "TWICE Korea's Instagram" },
13 | { href: "https://twitter.com/jypetwice", icon: , ariaLabel: "TWICE Korea's Twitter" },
14 | { href: "https://www.youtube.com/c/TWICE", icon: , ariaLabel: "TWICE Korea's Youtube" },
15 | { href: "https://www.tiktok.com/@twice_tiktok_official", icon: , ariaLabel: "TWICE Korea's Tiktok" },
16 | ];
17 |
18 | const twiceJapan: LinksProps[] = [
19 | { href: "https://www.instagram.com/jypetwice_japan/", icon: , ariaLabel: "TWICE Japan's Instagram" },
20 | { href: "https://twitter.com/JYPETWICE_JAPAN", icon: , ariaLabel: "TWICE Japan's Twitter" },
21 | { href: "https://www.youtube.com/channel/UCCRb6nYKaT8tzLA8CwDdUtw", icon: , ariaLabel: "TWICE Japan's Youtube" },
22 | { href: "https://www.tiktok.com/@twice_tiktok_officialjp", icon: , ariaLabel: "TWICE Japan's TikTok" },
23 | ];
24 |
25 | const mappedList = (arrayElement: LinksProps[]) => {
26 | return arrayElement.map((item: LinksProps, index: number) => {
27 | return (
28 |
29 |
30 | {item.icon}
31 |
32 |
33 | );
34 | });
35 | };
36 |
37 | export default function VerticalSocmedList() {
38 | return (
39 | <>
40 |
41 |
42 |
TWICE Korea
43 |
44 |
45 |
46 |
TWICE Japan
47 |
48 | >
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/src/lib/developersPage.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Discography",
4 | "description": "Discography from TWICE",
5 | "apiReference": [
6 | {
7 | "name": "List All Discography",
8 | "method": "get",
9 | "endpoint": "/api/discography",
10 | "response": [
11 | {
12 | "_id": "number",
13 | "type": "string",
14 | "country": "string",
15 | "name": "string",
16 | "releaseDate": "string",
17 | "coverArt": "string",
18 | "content": "string",
19 | "tracks": ["string", "string"],
20 | "spotifyLinks": "string",
21 | "video": ["string", "string"]
22 | }
23 | ]
24 | },
25 | {
26 | "name": "Discography Details",
27 | "method": "get",
28 | "endpoint": "/api/discography/:id",
29 | "params": [
30 | {
31 | "name": "id",
32 | "type": "number",
33 | "description": "Discography id"
34 | }
35 | ],
36 | "response": {
37 | "_id": "number",
38 | "type": "string",
39 | "country": "string",
40 | "name": "string",
41 | "releaseDate": "string",
42 | "coverArt": "string",
43 | "content": "string",
44 | "tracks": ["string", "string"],
45 | "spotifyLinks": "string",
46 | "video": ["string", "string"]
47 | }
48 | }
49 | ]
50 | },
51 | {
52 | "name": "Members",
53 | "description": "TWICE member list.",
54 | "apiReference": [
55 | {
56 | "name": "List of Members",
57 | "method": "get",
58 | "endpoint": "/api/members",
59 | "response": {
60 | "name": "string",
61 | "nickname": "string",
62 | "born": "string",
63 | "bloodType": "string",
64 | "image": "string",
65 | "instagram": "string"
66 | }
67 | }
68 | ]
69 | },
70 | {
71 | "name": "News",
72 | "description": "News list from TWICE",
73 | "apiReference": [
74 | {
75 | "name": "List of News",
76 | "method": "get",
77 | "endpoint": "/api/news",
78 | "response": [
79 | {
80 | "_id": "number",
81 | "title": "string",
82 | "date": "string",
83 | "htmlContent": "string"
84 | }
85 | ]
86 | },
87 | {
88 | "name": "News Details",
89 | "method": "get",
90 | "endpoint": "/api/news/:id",
91 | "params": [
92 | {
93 | "name": "id",
94 | "type": "number",
95 | "description": "News id"
96 | }
97 | ],
98 | "response": {
99 | "_id": "number",
100 | "title": "string",
101 | "date": "string",
102 | "htmlContent": "string"
103 | }
104 | }
105 | ]
106 | }
107 | ]
108 |
--------------------------------------------------------------------------------
/src/lib/members.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Im Nayeon",
4 | "nickname": "Nayeon",
5 | "born": "1995-09-22",
6 | "bloodType": "A",
7 | "image": "/images/members/nayeon.jpg",
8 | "instagram": "https://www.instagram.com/nayeonyny/"
9 | },
10 | {
11 | "name": "Yoo Jeongyeon",
12 | "nickname": "Jeongyeon",
13 | "born": "1996-11-01",
14 | "bloodType": "O",
15 | "image": "/images/members/jeongyeon.jpg",
16 | "instagram": "https://www.instagram.com/jy_piece/"
17 | },
18 | {
19 | "name": "Momo Hirai",
20 | "nickname": "Momo",
21 | "born": "1996-11-09",
22 | "bloodType": "A",
23 | "image": "/images/members/momo.jpg",
24 | "instagram": "https://www.instagram.com/momo/"
25 | },
26 | {
27 | "name": "Sana Minatozaki",
28 | "nickname": "Sana",
29 | "born": "1996-12-29",
30 | "bloodType": "B",
31 | "image": "/images/members/sana.jpg",
32 | "instagram": "https://www.instagram.com/m.by__sana/"
33 | },
34 | {
35 | "name": "Park Jihyo",
36 | "nickname": "Jihyo",
37 | "born": "1997-02-01",
38 | "bloodType": "O",
39 | "image": "/images/members/jihyo.jpg",
40 | "instagram": "https://www.instagram.com/_zyozyo/"
41 | },
42 | {
43 | "name": "Mina Sharon Myoi",
44 | "nickname": "Mina",
45 | "born": "1997-03-24",
46 | "bloodType": "A",
47 | "image": "/images/members/mina.jpg",
48 | "instagram": "https://www.instagram.com/mina_sr_my/"
49 | },
50 | {
51 | "name": "Kim Dahyun",
52 | "nickname": "Dahyun",
53 | "born": "1998-05-28",
54 | "bloodType": "O",
55 | "image": "/images/members/dahyun.jpg",
56 | "instagram": "https://www.instagram.com/dahhyunnee/"
57 | },
58 | {
59 | "name": "Son Chaeyoung",
60 | "nickname": "Chaeyoung",
61 | "born": "1999-04-23",
62 | "bloodType": "B",
63 | "image": "/images/members/chaeyoung.jpg",
64 | "instagram": "https://www.instagram.com/chaeyo.0/"
65 | },
66 | {
67 | "name": "Chou Tzuyu",
68 | "nickname": "Tzuyu",
69 | "born": "1999-06-14",
70 | "bloodType": "A",
71 | "image": "/images/members/tzuyu.jpg",
72 | "instagram": "https://www.instagram.com/thinkaboutzu/"
73 | }
74 | ]
75 |
--------------------------------------------------------------------------------
/src/lib/models/Contents.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models, Model } from "mongoose";
2 | import { ContentItemProps } from "~types/components";
3 |
4 | const ContentSchema = new Schema({
5 | tag: String,
6 | title: String,
7 | href: String,
8 | thumb: String,
9 | });
10 |
11 | const Contents = (models.Contents as Model) || model("Contents", ContentSchema, "Contents");
12 | export default Contents;
13 |
--------------------------------------------------------------------------------
/src/lib/models/Discography.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const DiscographySchema = new Schema(
4 | {
5 | _id: Number,
6 | type: String,
7 | country: String,
8 | name: String,
9 | releaseDate: String,
10 | coverArt: String,
11 | tracks: Array,
12 | spotifyLink: String,
13 | },
14 | { _id: false }
15 | );
16 |
17 | const Discography = models.Discography || model("Discography", DiscographySchema, "Discography");
18 | export default Discography;
19 |
--------------------------------------------------------------------------------
/src/lib/models/News.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const NewsSchema = new Schema(
4 | {
5 | _id: Number,
6 | title: String,
7 | date: String,
8 | htmlContent: String,
9 | },
10 | { _id: false }
11 | );
12 |
13 | const News = models.News || model("News", NewsSchema, "News");
14 | export default News;
15 |
--------------------------------------------------------------------------------
/src/lib/models/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Discography } from "./Discography";
2 | export { default as News } from "./News";
3 | export { default as Contents } from "./Contents";
4 |
--------------------------------------------------------------------------------
/src/lib/utils/clsx.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
2 | export default function clsx(...classes: any) {
3 | return classes.filter(Boolean).join(" ");
4 | }
5 |
--------------------------------------------------------------------------------
/src/lib/utils/dbConnect.ts:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const MONGODB_URI = `${process.env.MONGO_URI}`;
4 |
5 | if (!MONGODB_URI) {
6 | throw new Error("Please define the MONGODB_URI environment variable inside .env.local");
7 | }
8 |
9 | /**
10 | * Global is used here to maintain a cached connection across hot reloads
11 | * in development. This prevents connections growing exponentially
12 | * during API Route usage.
13 | */
14 |
15 | interface CustomNodeJsGlobal extends NodeJS.Global {
16 | mongoose: {
17 | conn: typeof mongoose | null;
18 | promise: Promise | null;
19 | };
20 | }
21 |
22 | declare const global: CustomNodeJsGlobal;
23 |
24 | let cached = global.mongoose;
25 |
26 | if (!cached) {
27 | cached = global.mongoose = { conn: null, promise: null };
28 | }
29 |
30 | export async function dbConnect() {
31 | if (cached.conn) {
32 | return cached.conn;
33 | }
34 |
35 | if (!cached.promise) {
36 | cached.promise = mongoose.connect(MONGODB_URI);
37 | }
38 | cached.conn = await cached.promise;
39 | return cached.conn;
40 | }
41 |
--------------------------------------------------------------------------------
/src/lib/utils/opacityAnimation.ts:
--------------------------------------------------------------------------------
1 | import { OpacityTransition } from "~types/components";
2 |
3 | export const addOpacity = async ({ opacity, DOMByID, time }: OpacityTransition) => {
4 | if (opacity < 1) {
5 | opacity += 0.2;
6 | setTimeout(function () {
7 | addOpacity({ opacity, DOMByID, time });
8 | }, time);
9 | }
10 | const DOMElement = document.getElementById(DOMByID);
11 | DOMElement && (DOMElement.style.opacity = `${opacity}`);
12 | };
13 |
--------------------------------------------------------------------------------
/src/lib/utils/useData.ts:
--------------------------------------------------------------------------------
1 | import useSWR from "swr";
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
4 | export default function useData(endpoint: any) {
5 | const fetcher = async () => {
6 | const response = await fetch(endpoint);
7 | const data = await response.json();
8 | return data;
9 | };
10 |
11 | const { data, error } = useSWR(endpoint, fetcher);
12 |
13 | return {
14 | data,
15 | isLoading: !data && !error,
16 | isError: error,
17 | } as const;
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { Layout } from "~components/layout";
3 |
4 | export default function Custom404() {
5 | return (
6 |
7 |
32 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "~styles/globals.css";
2 | import type { AppProps } from "next/app";
3 | import Loading from "~components/layout/loading";
4 | import { useRouter } from "next/router";
5 | import { useEffect, useState } from "react";
6 |
7 | const App: React.FC = ({ Component, pageProps }) => {
8 | const router = useRouter();
9 |
10 | const [state, setState] = useState({
11 | isRouteChanging: false,
12 | loadingKey: 0,
13 | });
14 |
15 | useEffect(() => {
16 | const handleRouteChangeStart = () => {
17 | setState((prevState) => ({
18 | ...prevState,
19 | isRouteChanging: true,
20 | loadingKey: prevState.loadingKey ^ 1,
21 | }));
22 | };
23 |
24 | const handleRouteChangeEnd = () => {
25 | setState((prevState) => ({
26 | ...prevState,
27 | isRouteChanging: false,
28 | }));
29 | };
30 | router.events.on("routeChangeStart", handleRouteChangeStart);
31 | router.events.on("routeChangeComplete", handleRouteChangeEnd);
32 | router.events.on("routeChangeError", handleRouteChangeEnd);
33 |
34 | return () => {
35 | router.events.off("routeChangeStart", handleRouteChangeStart);
36 | router.events.off("routeChangeComplete", handleRouteChangeEnd);
37 | router.events.off("routeChangeError", handleRouteChangeEnd);
38 | };
39 | }, [router.events]);
40 |
41 | return (
42 | <>
43 |
44 |
45 | >
46 | );
47 | };
48 |
49 | export default App;
50 |
--------------------------------------------------------------------------------
/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { DocumentContext, Head, Html, Main, NextScript } from "next/document";
2 |
3 | class MyDocument extends Document {
4 | static async getInitialProps(ctx: DocumentContext) {
5 | const initialProps = await Document.getInitialProps(ctx);
6 | return initialProps;
7 | }
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | export default MyDocument;
31 |
--------------------------------------------------------------------------------
/src/pages/api/discography/[id].ts:
--------------------------------------------------------------------------------
1 | import { NextApiHandler } from "next";
2 | import { dbConnect } from "~lib/utils/dbConnect";
3 | import { Discography } from "~lib/models";
4 |
5 | const handler: NextApiHandler = async (req, res) => {
6 | if (req.method === "GET") {
7 | try {
8 | const id = req.query.id;
9 |
10 | await dbConnect();
11 | const filter = { _id: id };
12 | const discographyDB = await Discography.findOne(filter);
13 | return res.json(discographyDB);
14 | } catch (err: unknown) {
15 | return res.status(400).json({ message: "An error occured" });
16 | }
17 | }
18 |
19 | return res.status(404).json({ message: "Not found" });
20 | };
21 |
22 | export default handler;
23 |
--------------------------------------------------------------------------------
/src/pages/api/discography/index.ts:
--------------------------------------------------------------------------------
1 | import { NextApiHandler } from "next";
2 | import { dbConnect } from "~lib/utils/dbConnect";
3 | import { Discography } from "~lib/models";
4 |
5 | const handler: NextApiHandler = async (req, res) => {
6 | if (req.method === "GET") {
7 | try {
8 | await dbConnect();
9 | const discographyDB = await Discography.find().sort({ _id: -1 });
10 | return res.json(discographyDB);
11 | } catch (err: unknown) {
12 | return res.status(400).json({ message: "An error occured" });
13 | }
14 | }
15 |
16 | return res.status(404).json({ message: "Not found" });
17 | };
18 |
19 | export default handler;
20 |
--------------------------------------------------------------------------------
/src/pages/api/members.ts:
--------------------------------------------------------------------------------
1 | import { NextApiHandler } from "next";
2 | import members from "~lib/members.json";
3 |
4 | const handler: NextApiHandler = async (req, res) => {
5 | if (req.method === "GET") {
6 | try {
7 | return res.status(200).json(members);
8 | } catch (err: unknown) {
9 | return res.status(400).json({ message: "An error occured" });
10 | }
11 | }
12 |
13 | return res.status(404).json({ message: "Not found" });
14 | };
15 |
16 | export default handler;
17 |
--------------------------------------------------------------------------------
/src/pages/api/news/[id].ts:
--------------------------------------------------------------------------------
1 | import { NextApiHandler } from "next";
2 | import { dbConnect } from "~lib/utils/dbConnect";
3 | import { News } from "~lib/models";
4 |
5 | const handler: NextApiHandler = async (req, res) => {
6 | if (req.method === "GET") {
7 | try {
8 | const id = req.query.id;
9 |
10 | await dbConnect();
11 | const filter = { _id: id };
12 | const newsDB = await News.findOne(filter);
13 | return res.json(newsDB);
14 | } catch (err: unknown) {
15 | return res.status(400).json({ message: "An error occured" });
16 | }
17 | }
18 |
19 | return res.status(404).json({ message: "Not found" });
20 | };
21 |
22 | export default handler;
23 |
--------------------------------------------------------------------------------
/src/pages/api/news/index.ts:
--------------------------------------------------------------------------------
1 | import { NextApiHandler } from "next";
2 | import { dbConnect } from "~lib/utils/dbConnect";
3 | import { News } from "~lib/models";
4 |
5 | const handler: NextApiHandler = async (req, res) => {
6 | if (req.method === "GET") {
7 | try {
8 | await dbConnect();
9 | const newsDB = await News.find().sort({ _id: -1 });
10 | return res.json(newsDB);
11 | } catch (err: unknown) {
12 | return res.status(400).json({ message: "An error occured" });
13 | }
14 | }
15 |
16 | return res.status(404).json({ message: "Not found" });
17 | };
18 |
19 | export default handler;
20 |
--------------------------------------------------------------------------------
/src/pages/developers.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { Container, Layout, Section } from "~components/layout";
3 | import developersPage from "~lib/developersPage.json";
4 |
5 | interface Props {
6 | name: string;
7 | description: string;
8 | apiReference: any;
9 | }
10 |
11 | const Items = ({ name, description, apiReference }: Props) => {
12 | return (
13 |
14 |
{name}
15 |
{description}
16 | {apiReference?.map((item: any, index: number) => {
17 | return (
18 |
19 |
{item.name}
20 |
21 | {item.method}
22 | {item.endpoint}
23 |
24 | {item.params && (
25 |
26 |
Params
27 |
28 |
29 |
30 | Name
31 | Type
32 | Description
33 |
34 |
35 |
36 | {item.params.map((param: any, index: number) => {
37 | return (
38 |
39 | {param.name}
40 | {param.type}
41 | {param.description}
42 |
43 | );
44 | })}
45 |
46 |
47 |
48 | )}
49 |
50 |
Response
51 |
52 | {JSON.stringify(item.response, null, 4).replace(/"/g, "")}
53 |
54 |
55 |
56 | );
57 | })}
58 |
59 | );
60 | };
61 |
62 | const Developer = () => {
63 | return (
64 | <>
65 |
66 |
67 |
68 |
69 | All media are obtained from{" "}
70 |
71 | JYPE
72 |
73 | {" & "}
74 |
75 | Sony Music
76 |
77 | .
78 |
79 |
80 | {developersPage.map((item, index) => {
81 | return ;
82 | })}
83 |
84 |
85 |
86 |
87 | >
88 | );
89 | };
90 |
91 | export default Developer;
92 |
--------------------------------------------------------------------------------
/src/pages/discography/[id].tsx:
--------------------------------------------------------------------------------
1 | import { DiscographyType } from "~types/components";
2 | import { Container, Layout } from "~components/layout";
3 | import Image from "next/image";
4 | import useData from "~lib/utils/useData";
5 | import { useRouter } from "next/router";
6 | import Custom404 from "../404";
7 | import LoadingScreen from "~components/loadingScreen";
8 |
9 | const DiscographyByID = () => {
10 | const router = useRouter();
11 | const { id } = router.query;
12 | const endpoint = id ? `/api/discography/${id}` : null;
13 | const { data, isLoading, isError } = useData(endpoint);
14 |
15 | const renderItems = () => {
16 | try {
17 | if (isLoading) {
18 | return ;
19 | } else if (data) {
20 | const { coverArt, name, releaseDate, type, tracks, video, content, spotifyLink }: DiscographyType = data;
21 | const spotifyID = spotifyLink.split("/").pop();
22 | const youtubeID: string[] = [];
23 |
24 | if (video) {
25 | for (const item of video) {
26 | const idFromLink = item.split("=").pop();
27 | youtubeID.push(`${idFromLink}`);
28 | }
29 | }
30 |
31 | const removedHTMLtag = content.replace(/<[^>]*>?/gm, "");
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {spotifyLink && (
43 |
50 | )}
51 |
52 |
53 |
54 |
{name}
55 |
56 |
{type}
57 |
{releaseDate} Release
58 |
59 |
60 | {tracks.length > 0 && (
61 |
62 |
Tracks Lists
63 |
64 | {tracks.map((track, index: number) => {
65 | const trackNumber = index + 1;
66 | return (
67 |
68 |
{trackNumber.toString().padStart(2, "0")}.
69 |
{track}
70 |
71 | );
72 | })}
73 |
74 |
75 | )}
76 |
77 |
78 |
79 |
Video
80 | {youtubeID.map((idYoutube, index) => {
81 | return (
82 |
83 | VIDEO
92 |
93 | );
94 | })}
95 |
96 |
97 |
98 | );
99 | } else if (isError) {
100 | throw new Error("Error");
101 | }
102 | } catch (error) {
103 | return ;
104 | }
105 | };
106 |
107 | return <>{renderItems()}>;
108 | };
109 |
110 | export default DiscographyByID;
111 |
--------------------------------------------------------------------------------
/src/pages/discography/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import { Layout, Container, Section } from "~components/layout";
3 | import { DiscographySection } from "~components/discography";
4 |
5 | const Discography: NextPage = () => {
6 | return (
7 | <>
8 |
9 |
10 |
13 |
14 |
15 | >
16 | );
17 | };
18 |
19 | export default Discography;
20 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import { Layout, Container, Section } from "~components/layout";
3 | import VerticalSocmedList from "~components/verticalSocmedList";
4 | import ScrollDownAnimation from "~components/scrollDownAnimation";
5 | import Image from "next/image";
6 | import Link from "next/link";
7 | import { useState } from "react";
8 | import LoadingScreen from "~components/loadingScreen";
9 | import { LatestContent } from "~components/content";
10 | import { NewestDiscographySection } from "~components/discography";
11 | import { LatestNewsSection } from "~components/news";
12 | import Script from "next/script";
13 | import { GetServerSideProps } from "next";
14 | import { dbConnect } from "~lib/utils/dbConnect";
15 | import Contents from "~/src/lib/models/Contents";
16 | import { ContentItemProps } from "~types/components";
17 | import { useEffect } from "react";
18 |
19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
20 | const Home: NextPage = (data: any) => {
21 | const [isLoading, setIsLoading] = useState(true);
22 | const [isLoader, setIsLoader] = useState(true);
23 | const metaDescription =
24 | "TWICE is a South Korean girl group formed by JYP Entertainment. The group is composed of nine members: Nayeon, Jeongyeon, Momo, Sana, Jihyo, Mina, Dahyun, Chaeyoung, and Tzuyu. Twice was formed under the television program Sixteen (2015) and debuted on October 20, 2015, with the extended play (EP) The Story Begins.";
25 |
26 | useEffect(() => {
27 | if (!isLoader) {
28 | setTimeout(function () {
29 | setIsLoading(false);
30 | }, 2000);
31 | }
32 | }, [isLoading, isLoader]);
33 |
34 | return (
35 | <>
36 | {isLoading && }
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | setIsLoader(false)}
54 | />
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
67 |
68 |
69 |
72 |
73 |
74 |
77 |
78 |
79 |
80 | About TWICE
81 |
82 | TWICE (Korean: 트와이스; RR: Teuwaiseu; Japanese: トゥワイス, Hepburn: To~uwaisu; commonly stylized in all caps as TWICE) is a South Korean girl group formed by JYP
83 | Entertainment. The group is composed of nine members: Nayeon, Jeongyeon, Momo, Sana, Jihyo, Mina, Dahyun, Chaeyoung, and Tzuyu. Twice was formed under the television
84 | program Sixteen (2015) and debuted on October 20, 2015, with the extended play (EP) The Story Begins.
85 | The group debuted in Japan on June 28, 2017, under Warner Music Japan, with the release of a compilation album titled #Twice. The album charted at number 2 on
86 | the Oricon Albums Chart with the highest first-week album sales by a K-pop artist in Japan in two years. It was followed by the release of TWICE's first original
87 | Japanese maxi single titled "One More Time" in October. Twice became the first Korean girl group to earn a platinum certification from the Recording Industry
88 | Association of Japan (RIAJ) for both an album and CD single in the same year. Twice ranked third in the Top Artist category of Billboard Japan's 2017 Year-end
89 | Rankings, and in 2019, they became the first Korean girl group to embark on a Japanese dome tour.
90 |
91 |
96 |
97 |
108 |
109 | >
110 | );
111 | };
112 |
113 | export default Home;
114 |
115 | export const getServerSideProps: GetServerSideProps = async () => {
116 | await dbConnect();
117 | const data = (await Contents.find({}).sort({ _id: -1 }).limit(3)) as ContentItemProps[];
118 |
119 | const contents = JSON.parse(JSON.stringify(data));
120 | return { props: { contents } };
121 | };
122 |
--------------------------------------------------------------------------------
/src/pages/news/[id].tsx:
--------------------------------------------------------------------------------
1 | import { NewsType } from "~types/components";
2 | import { Container, Layout } from "~components/layout";
3 | import useData from "~lib/utils/useData";
4 | import { useRouter } from "next/router";
5 | import Custom404 from "../404";
6 | import LoadingScreen from "~components/loadingScreen";
7 |
8 | const NewsByID = () => {
9 | const router = useRouter();
10 | const { id } = router.query;
11 | const endpoint = id ? `/api/news/${id}` : null;
12 | const { data, isLoading, isError } = useData(endpoint);
13 |
14 | const renderItems = () => {
15 | try {
16 | if (isLoading) {
17 | return ;
18 | } else if (data) {
19 | const { title, date, htmlContent }: NewsType = data;
20 | const removedHTMLtag = htmlContent.replace(/<[^>]*>?/gm, "");
21 |
22 | return (
23 |
24 |
25 | {title}
26 | {date}
27 |
28 |
29 |
30 | );
31 | } else if (isError) {
32 | throw new Error("Error");
33 | }
34 | } catch (error) {
35 | return ;
36 | }
37 | };
38 |
39 | return <>{renderItems()}>;
40 | };
41 |
42 | export default NewsByID;
43 |
--------------------------------------------------------------------------------
/src/pages/news/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import { Layout, Container, Section } from "~components/layout";
3 | import { NewsSection } from "~components/news";
4 |
5 | const News: NextPage = () => {
6 | return (
7 | <>
8 |
9 |
10 |
13 |
14 |
15 | >
16 | );
17 | };
18 |
19 | export default News;
20 |
--------------------------------------------------------------------------------
/src/pages/profile.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import { useState, useEffect } from "react";
3 | import { Layout, Container, Section } from "~components/layout";
4 | import MemberItem from "~components/memberItem";
5 | import members from "~lib/members.json";
6 | import { AiOutlineClose } from "react-icons/ai";
7 | import { MdOutlineArrowBackIosNew, MdOutlineArrowForwardIos } from "react-icons/md";
8 | import { addOpacity } from "~/src/lib/utils/opacityAnimation";
9 |
10 | let imageOpacityValue,
11 | contentOpacityValue,
12 | // eslint-disable-next-line prefer-const
13 | modalOpacityValue = 0;
14 | const Profile: NextPage = () => {
15 | const [count, setCount] = useState(0);
16 | const [isModal, setIsModal] = useState(false);
17 |
18 | const handleNextClick = () => {
19 | if (count >= 8) {
20 | setCount(0);
21 | } else {
22 | setCount(count + 1);
23 | }
24 |
25 | imageOpacity();
26 | setTimeout(() => {
27 | contentOpacity();
28 | }, 500);
29 | };
30 |
31 | const handlePrevClick = async () => {
32 | if (count <= 0) {
33 | setCount(8);
34 | } else {
35 | setCount(count - 1);
36 | }
37 |
38 | imageOpacity();
39 | setTimeout(() => {
40 | contentOpacity();
41 | }, 500);
42 | };
43 |
44 | const imageOpacity = () => {
45 | imageOpacityValue = 0;
46 | addOpacity({
47 | opacity: imageOpacityValue,
48 | DOMByID: "member_modal_image",
49 | time: 60,
50 | });
51 | const modalDOM = document.getElementById("member_modal_content");
52 | modalDOM && (modalDOM.style.opacity = "0");
53 | };
54 |
55 | const contentOpacity = async () => {
56 | contentOpacityValue = 0;
57 | addOpacity({
58 | opacity: contentOpacityValue,
59 | DOMByID: "member_modal_content",
60 | time: 50,
61 | });
62 | };
63 |
64 | const removeOpacity = async (opacity: number) => {
65 | if (opacity > 0) {
66 | opacity -= 0.3;
67 | setTimeout(function () {
68 | removeOpacity(opacity);
69 | }, 10);
70 | }
71 | const modalDOM = document.getElementById("member_modal");
72 | modalDOM && (modalDOM.style.opacity = `${opacity}`);
73 | };
74 |
75 | const handleOpenModalClick = async () => {
76 | await setIsModal(true);
77 | addOpacity({
78 | opacity: modalOpacityValue,
79 | DOMByID: "member_modal",
80 | time: 40,
81 | });
82 | };
83 |
84 | const handleCloseModalClick = async () => {
85 | await removeOpacity(modalOpacityValue);
86 | setIsModal(false);
87 | };
88 |
89 | useEffect(() => {
90 | isModal ? document.body.classList.add("overflow-hidden") : document.body.classList.remove("overflow-hidden");
91 | }, [isModal]);
92 |
93 | const memberByCount = members[count];
94 | const intagramID = memberByCount.instagram.split("/")[3];
95 |
96 | const metaDescription =
97 | "TWICE (Korean: 트와이스; RR: Teuwaiseu; Japanese: トゥワイス, Hepburn: To~uwaisu; commonly stylized in all caps as TWICE) is a South Korean girl group formed by JYP Entertainment. The group is composed of nine members: Nayeon, Jeongyeon, Momo, Sana, Jihyo, Mina, Dahyun, Chaeyoung, and Tzuyu. Twice was formed under the television program Sixteen (2015) and debuted on October 20, 2015, with the extended play (EP) The Story Begins.";
98 |
99 | return (
100 | <>
101 |
102 |
103 |
104 |
105 | {members.map((member, index) => {
106 | return (
107 | {
111 | setCount(index);
112 | handleOpenModalClick();
113 | }}
114 | />
115 | );
116 | })}
117 |
118 |
119 |
120 | {isModal && (
121 |
122 |
123 |
{
125 | handleCloseModalClick();
126 | }}
127 | />
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | {memberByCount.name}
137 | {memberByCount.nickname}
138 |
139 |
Born: {memberByCount.born}
140 |
Blood Type: {memberByCount.bloodType}
141 |
147 |
148 |
149 | )}
150 |
151 | >
152 | );
153 | };
154 |
155 | export default Profile;
156 |
--------------------------------------------------------------------------------
/src/styles/footer.module.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | padding: 2rem 0 0;
3 | background: #182227;
4 | z-index: 3;
5 | color: #fafafa;
6 | }
7 |
8 | .footer a:hover {
9 | text-decoration: underline;
10 | }
11 |
12 | .container {
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | gap: 1rem;
17 | }
18 |
19 | .menu {
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 | flex-direction: column;
24 | gap: 2rem;
25 | width: 100%;
26 | margin: 0 20px;
27 | }
28 |
29 | .menu_list {
30 | width: 100%;
31 | text-align: center;
32 | }
33 |
34 | .menu_list span {
35 | font-size: 1.2rem;
36 | font-weight: 500;
37 | }
38 |
39 | .menu_list ul li {
40 | font-size: var(--f-sm);
41 | }
42 |
43 | .link {
44 | width: 100%;
45 | }
46 |
47 | .official_site {
48 | display: flex;
49 | }
50 |
51 | .credit {
52 | text-align: center;
53 | padding: 1.5rem 1rem 1rem;
54 | color: #999999;
55 | font-size: 14px;
56 | }
57 |
58 | .credit a {
59 | color: #999999;
60 | }
61 |
62 | @media screen and (max-width: 767px) {
63 | .official_site {
64 | gap: 12px;
65 | flex-direction: column;
66 | }
67 |
68 | .official_site a {
69 | text-align: center;
70 | background-color: #32383a;
71 | width: 100%;
72 | padding: 12px 0;
73 | border-radius: 10px;
74 | }
75 |
76 | .other {
77 | display: none;
78 | }
79 | }
80 |
81 | @media screen and (min-width: 767px) {
82 | .link {
83 | display: flex;
84 | gap: 1rem;
85 | justify-content: center;
86 | }
87 |
88 | .official_site {
89 | gap: 1rem;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap");
2 |
3 | :root {
4 | --font: "Noto Sans", sans-serif;
5 | --main-color: #f0c478;
6 | --sec-color: #a2cdd6;
7 | --fd-lg: 3rem;
8 | --fd-md: 2.25rem;
9 | --fd-sm: 1.875rem;
10 | --fd-xs: 1.5rem;
11 | --f-lg: 1.25rem;
12 | --f-md: 1.125rem;
13 | --f-reg: 1rem;
14 | --f-sm: 0.875rem;
15 | --f-xs: 0.75rem;
16 | }
17 |
18 | html,
19 | body {
20 | background: #fafafa;
21 | padding: 0;
22 | margin: 0;
23 | height: 100%;
24 | font-size: 1rem;
25 | line-height: 1.5rem;
26 | font-family: var(--font);
27 | }
28 |
29 | a {
30 | color: var(--main-color);
31 | text-decoration: none;
32 | }
33 |
34 | * {
35 | box-sizing: border-box;
36 | }
37 |
38 | ul,
39 | li {
40 | text-decoration: none;
41 | list-style: none;
42 | margin: 0;
43 | padding: 0;
44 | }
45 |
46 | html,
47 | body,
48 | div,
49 | span,
50 | applet,
51 | object,
52 | iframe,
53 | h1,
54 | h2,
55 | h3,
56 | h4,
57 | h5,
58 | h6,
59 | p,
60 | blockquote,
61 | pre,
62 | a,
63 | abbr,
64 | acronym,
65 | address,
66 | big,
67 | cite,
68 | code,
69 | del,
70 | dfn,
71 | em,
72 | img,
73 | ins,
74 | kbd,
75 | q,
76 | s,
77 | samp,
78 | small,
79 | strike,
80 | strong,
81 | sub,
82 | sup,
83 | tt,
84 | var,
85 | b,
86 | u,
87 | i,
88 | center,
89 | dl,
90 | dt,
91 | dd,
92 | ol,
93 | ul,
94 | li,
95 | fieldset,
96 | form,
97 | label,
98 | legend,
99 | table,
100 | caption,
101 | tbody,
102 | tfoot,
103 | thead,
104 | tr,
105 | th,
106 | td,
107 | article,
108 | aside,
109 | canvas,
110 | details,
111 | embed,
112 | figure,
113 | figcaption,
114 | footer,
115 | header,
116 | hgroup,
117 | menu,
118 | nav,
119 | output,
120 | ruby,
121 | section,
122 | summary,
123 | time,
124 | mark,
125 | audio,
126 | video {
127 | margin: 0;
128 | padding: 0;
129 | border: 0;
130 | font-size: 100%;
131 | font-family: var(--font);
132 | font-weight: normal;
133 | }
134 |
135 | #__next {
136 | height: 100%;
137 | }
138 |
139 | #content {
140 | width: 100%;
141 | height: 100%;
142 | }
143 |
144 | #layout_content {
145 | display: flex;
146 | flex-direction: column;
147 | height: 100%;
148 | width: 100%;
149 | transition: opacity 1s ease-in-out;
150 | }
151 |
152 | #main {
153 | flex: 1;
154 | }
155 |
156 | img {
157 | width: 100%;
158 | height: 100%;
159 | }
160 |
161 | .overflow-hidden {
162 | overflow: hidden;
163 | -webkit-overflow-scrolling: touch;
164 | }
165 |
166 | .inner {
167 | position: relative;
168 | }
169 |
170 | #header {
171 | z-index: 5;
172 | }
173 |
174 | .hidden {
175 | opacity: 0;
176 | }
177 |
178 | #header .inner {
179 | margin: 0 20px;
180 | display: flex;
181 | align-items: center;
182 | justify-content: space-between;
183 | height: 40px;
184 | }
185 |
186 | .hero_container {
187 | height: 100vh;
188 | width: 100%;
189 | position: relative;
190 | animation-fill-mode: forwards;
191 | transition: all 0.6s;
192 | }
193 |
194 | .hero_container::after {
195 | content: "";
196 | position: absolute;
197 | top: 0;
198 | bottom: 0;
199 | left: 0;
200 | right: 0;
201 | z-index: 2;
202 | }
203 |
204 | .heroImg,
205 | .heroBG {
206 | width: 100%;
207 | height: 100%;
208 | background-size: cover;
209 | background-position: center center;
210 | background-repeat: no-repeat;
211 | position: fixed;
212 | }
213 |
214 | .heroBG {
215 | top: 0;
216 | left: 0;
217 | z-index: 1;
218 | background-color: #2d2d2d;
219 | opacity: 0.4;
220 | }
221 |
222 | .scroll {
223 | position: fixed;
224 | bottom: 0;
225 | left: 50%;
226 | width: 2px;
227 | height: 70px;
228 | z-index: 1;
229 | }
230 |
231 | .scroll::after {
232 | content: "";
233 | width: 100%;
234 | position: absolute;
235 | height: 100%;
236 | display: block;
237 | top: 0px;
238 | background: var(--main-color);
239 | -webkit-animation: scroll 1.2s linear 0s infinite;
240 | animation: scroll 1.2s linear 0s infinite;
241 | }
242 |
243 | @-webkit-keyframes scroll {
244 | 0% {
245 | height: 0%;
246 | top: 0px;
247 | bottom: auto;
248 | }
249 | 46% {
250 | height: 100%;
251 | top: 0px;
252 | bottom: auto;
253 | }
254 | 50% {
255 | height: 100%;
256 | bottom: 0px;
257 | top: auto;
258 | }
259 | 54% {
260 | height: 100%;
261 | bottom: 0px;
262 | top: auto;
263 | }
264 | 100% {
265 | height: 0%;
266 | bottom: 0px;
267 | top: auto;
268 | }
269 | }
270 |
271 | @keyframes scroll {
272 | 0% {
273 | height: 0%;
274 | top: 0px;
275 | bottom: auto;
276 | }
277 | 46% {
278 | height: 100%;
279 | top: 0px;
280 | bottom: auto;
281 | }
282 | 50% {
283 | height: 100%;
284 | bottom: 0px;
285 | top: auto;
286 | }
287 | 54% {
288 | height: 100%;
289 | bottom: 0px;
290 | top: auto;
291 | }
292 | 100% {
293 | height: 0%;
294 | bottom: 0px;
295 | top: auto;
296 | }
297 | }
298 |
299 | .hero_title {
300 | position: absolute;
301 | width: 100%;
302 | bottom: 20%;
303 | left: 50%;
304 | transform: translateX(-50%);
305 | color: var(--main-color);
306 | z-index: 2;
307 | text-align: center;
308 | transition: all 0.3s ease 0s;
309 | }
310 |
311 | .hero_title h1 {
312 | width: 80%;
313 | margin: auto;
314 | }
315 |
316 | .hero_title div {
317 | width: 70%;
318 | margin: auto;
319 | max-width: 500px;
320 | }
321 |
322 | .socmed_list {
323 | opacity: 0;
324 | transition: all 0.3s ease 0s;
325 | }
326 |
327 | .content {
328 | position: relative;
329 | background: #fafafa;
330 | z-index: 3;
331 | padding: 3rem 0 0.5rem 0;
332 | }
333 |
334 | .container,
335 | .largeContainer,
336 | .smallContainer {
337 | margin: 0 20px;
338 | }
339 |
340 | .section_content {
341 | padding-bottom: 3rem;
342 | }
343 |
344 | .top_section {
345 | position: relative;
346 | margin-bottom: 1.5rem;
347 | padding-bottom: 12px;
348 | }
349 |
350 | .top_section span {
351 | display: none;
352 | }
353 |
354 | .top_section::after {
355 | content: "";
356 | position: absolute;
357 | height: 2px;
358 | bottom: 0;
359 | }
360 |
361 | .title_section {
362 | position: relative;
363 | text-align: center;
364 | font-size: var(--fd-xs);
365 | font-weight: 700;
366 | }
367 |
368 | .content_block {
369 | display: grid;
370 | grid-template-columns: 1fr;
371 | gap: 1.5rem;
372 | }
373 |
374 | .content_block a {
375 | color: #111827;
376 | transition: all 0.3s 0s ease;
377 | }
378 |
379 | .content_block a:hover {
380 | transform: translateY(-5px);
381 | color: var(--main-color);
382 | }
383 |
384 | .content_block a:hover .content_tag {
385 | background-color: var(--main-color);
386 | color: #111827;
387 | }
388 |
389 | .content_item {
390 | position: relative;
391 | z-index: 1;
392 | }
393 |
394 | .content_thumb {
395 | position: relative;
396 | padding-top: 56.25%;
397 | }
398 |
399 | .content_tag {
400 | transition: all 0.3s 0s ease;
401 | position: absolute;
402 | bottom: 12px;
403 | left: 12px;
404 | background-color: var(--sec-color);
405 | width: 80px;
406 | border-radius: 5px;
407 | text-align: center;
408 | padding: 5px 0;
409 | font-size: 12px;
410 | text-transform: uppercase;
411 | height: 30px;
412 | line-height: 20px;
413 | font-weight: 600;
414 | }
415 |
416 | .content_title {
417 | font-weight: 600;
418 | padding-top: 12px;
419 | }
420 |
421 | .loadMore {
422 | display: flex;
423 | justify-content: center;
424 | align-items: center;
425 | margin-top: 1.5rem;
426 | }
427 |
428 | .loadMore a {
429 | color: #fafafa;
430 | background-color: var(--main-color);
431 | padding: 5px 12px;
432 | border-radius: 5px;
433 | font-weight: 600;
434 | }
435 |
436 | .loadMore a:hover {
437 | transition: all 0.3s ease 0s;
438 | transform: translateY(-5px);
439 | }
440 |
441 | .news_block {
442 | display: grid;
443 | grid-template-columns: 1fr;
444 | gap: 12px;
445 | }
446 |
447 | .news_item {
448 | border: solid 1px #111827;
449 | border-radius: 5px;
450 | padding: 12px;
451 | display: flex;
452 | flex-direction: column;
453 | gap: 6px;
454 | }
455 |
456 | .news_date {
457 | font-weight: 600;
458 | }
459 |
460 | .news_item {
461 | color: #111827;
462 | transition: all 0.2s 0s ease;
463 | }
464 |
465 | .news_item:hover {
466 | background-color: var(--main-color);
467 | border-color: var(--main-color);
468 | }
469 |
470 | .news_item.skeleton {
471 | background-color: #e9e9e9;
472 | border: none;
473 | min-height: 40px;
474 | position: relative;
475 | overflow: hidden;
476 | }
477 |
478 | .news_item.skeleton::after {
479 | content: "";
480 | position: absolute;
481 | width: 100%;
482 | height: 100%;
483 | display: block;
484 | top: 0px;
485 | background: #e5e4e4;
486 | transform: translateX(-100%);
487 | -webkit-animation: loadingSkeleton 1s infinite;
488 | animation: loadingSkeleton 1s infinite;
489 | }
490 |
491 | @keyframes loadingSkeleton {
492 | 100% {
493 | transform: translateX(100%);
494 | }
495 | }
496 |
497 | .discography_block {
498 | display: grid;
499 | grid-template-columns: 1fr;
500 | gap: 12px;
501 | }
502 |
503 | .discography_item {
504 | position: relative;
505 | overflow: hidden;
506 | color: #111827;
507 | }
508 |
509 | .discography_category {
510 | background-color: rgb(235, 235, 235);
511 | border-radius: 25px;
512 | padding: 5px 10px;
513 | text-transform: uppercase;
514 | font-size: var(--f-xs);
515 | margin-bottom: 6px;
516 | }
517 |
518 | .discography_title {
519 | margin: 6px 0;
520 | font-weight: normal;
521 | }
522 |
523 | .discography_releaseDate {
524 | font-weight: normal;
525 | font-size: var(--f-sm);
526 | color: rgb(180, 180, 180);
527 | }
528 |
529 | .about {
530 | position: relative;
531 | color: #fafafa;
532 | z-index: 2;
533 | padding: 5rem 0;
534 | }
535 |
536 | .about_title {
537 | position: relative;
538 | text-align: center;
539 | font-size: var(--fd-sm);
540 | font-weight: 700;
541 | padding-bottom: 1rem;
542 | }
543 |
544 | .about_title::selection,
545 | .about_desc::selection {
546 | color: #111827;
547 | background: var(--main-color);
548 | }
549 |
550 | .about_button {
551 | display: flex;
552 | justify-content: center;
553 | align-items: center;
554 | padding-top: 1rem;
555 | }
556 |
557 | .about_button a {
558 | background-color: var(--main-color);
559 | color: #252b2e;
560 | border-radius: 5px;
561 | padding: 5px 12px;
562 | transition: all 0.3s ease 0s;
563 | }
564 |
565 | .about_button a:hover {
566 | transform: translateY(-5px);
567 | }
568 |
569 | .twitter-section {
570 | position: relative;
571 | z-index: 2;
572 | background-color: #252b2e;
573 | padding: 3rem 0;
574 | }
575 |
576 | .twitter-section .container {
577 | display: flex;
578 | gap: 2rem;
579 | flex-direction: column;
580 | }
581 |
582 | .loadingScreen {
583 | width: 100%;
584 | height: 100vh;
585 | position: fixed;
586 | z-index: 999;
587 | background: #182227;
588 | align-items: center;
589 | justify-content: center;
590 | flex-direction: column;
591 | }
592 |
593 | @keyframes fadeLoading {
594 | 0% {
595 | opacity: 0.08;
596 | filter: blur(5px);
597 | }
598 | 100% {
599 | opacity: 1;
600 | }
601 | }
602 |
603 | .loadingScreen h1,
604 | .loadingScreen h2 {
605 | animation: fadeLoading 1.2s infinite 0s ease-in-out;
606 | animation-direction: alternate;
607 | }
608 |
609 | .loadingScreen h2 {
610 | width: 70%;
611 | max-width: 500px;
612 | }
613 |
614 | .news-title {
615 | font-size: var(--fd-xs);
616 | line-height: 2.5rem;
617 | }
618 |
619 | .news-date {
620 | font-size: var(--f-sm);
621 | color: #6c6c6c;
622 | padding: 1rem 0 2rem;
623 | }
624 |
625 | article a:hover {
626 | text-decoration: underline;
627 | color: #434445;
628 | }
629 |
630 | .discography_detail {
631 | font-size: var(--f-sm);
632 | }
633 |
634 | .discography_flex {
635 | display: flex;
636 | gap: 20px;
637 | }
638 |
639 | .discography_flex_cover {
640 | display: flex;
641 | flex-direction: column;
642 | gap: 1rem;
643 | }
644 |
645 | .discography_content a {
646 | color: cornflowerblue;
647 | }
648 |
649 | .discography_content a:hover {
650 | color: var(--main-color);
651 | }
652 |
653 | .discography_content .pre {
654 | background: rgb(151 206 226);
655 | padding: 10px;
656 | color: #fafafa;
657 | border-radius: 10px;
658 | font-weight: 600;
659 | transition: all 0.3s ease;
660 | display: flex;
661 | justify-content: center;
662 | align-items: center;
663 | }
664 |
665 | .discography_content .pre:hover {
666 | color: #182227;
667 | background: #eaf199;
668 | filter: drop-shadow(1px 1px 1px #cfcaca);
669 | }
670 |
671 | .discography_detail h2 {
672 | font-weight: 700;
673 | text-transform: uppercase;
674 | font-size: var(--f-reg);
675 | }
676 |
677 | .discography_tracks {
678 | padding-top: 1.5rem;
679 | }
680 |
681 | .discography_tracks h2 {
682 | padding-bottom: 6px;
683 | }
684 |
685 | .discography_tracks_item {
686 | display: flex;
687 | align-items: center;
688 | }
689 |
690 | .discography_tracks_item_number {
691 | padding-right: 0.25rem;
692 | }
693 |
694 | .discography_video {
695 | padding-top: 1.5rem;
696 | display: flex;
697 | flex-direction: column;
698 | gap: 1rem;
699 | }
700 |
701 | .videoWrapper {
702 | float: none;
703 | clear: both;
704 | width: 100%;
705 | position: relative;
706 | padding-bottom: 56.25%;
707 | padding-top: 25px;
708 | height: 0;
709 | }
710 | .videoWrapper iframe {
711 | position: absolute;
712 | top: 0;
713 | left: 0;
714 | width: 100%;
715 | height: 100%;
716 | }
717 |
718 | .discography_meta {
719 | padding: 1rem 0 2rem;
720 | display: flex;
721 | gap: 10px;
722 | align-items: center;
723 | }
724 |
725 | .discography_type {
726 | font-size: var(--f-xs);
727 | background-color: #c4c4c4;
728 | border-radius: 10px;
729 | padding: 5px 10px;
730 | text-transform: uppercase;
731 | }
732 |
733 | .discography_date {
734 | font-size: var(--f-sm);
735 | color: #6c6c6c;
736 | }
737 |
738 | .discography_info_content .discography_title {
739 | font-size: var(--fd-sm);
740 | font-weight: 700;
741 | margin: 0;
742 | color: #182227;
743 | }
744 |
745 | .discography_thumb img {
746 | object-fit: cover;
747 | }
748 |
749 | .member_block {
750 | display: grid;
751 | grid-template-columns: 1fr;
752 | gap: 12px;
753 | }
754 |
755 | .member_item {
756 | /* width: 100%;
757 | height: 450px; */
758 | position: relative;
759 | overflow: hidden;
760 | transition: all 0.2s 0s ease;
761 | }
762 |
763 | .member_item:hover {
764 | cursor: pointer;
765 | }
766 |
767 | .member_pict {
768 | height: 100%;
769 | position: relative;
770 | display: flex;
771 | }
772 |
773 | .member_info {
774 | position: absolute;
775 | bottom: 50%;
776 | transform: translate(0, 50%);
777 | width: 100%;
778 | text-align: center;
779 | opacity: 0;
780 | transition: all 0.2s 0s ease;
781 | }
782 |
783 | .member_modal {
784 | position: fixed;
785 | top: 0;
786 | left: 0;
787 | z-index: 50;
788 | width: 100%;
789 | height: 100vh;
790 | transition: all 0.3s 0s ease;
791 | background-color: #182227;
792 | }
793 |
794 | .member_modal_closeBtn {
795 | display: flex;
796 | margin: 1rem 20px;
797 | font-size: 2.5rem;
798 | justify-content: flex-end;
799 | z-index: 52;
800 | position: relative;
801 | color: #fafafa;
802 | }
803 |
804 | .member_modal_closeBtn svg:hover {
805 | cursor: pointer;
806 | }
807 |
808 | .member_modal_navigation {
809 | position: fixed;
810 | top: 0;
811 | z-index: 1;
812 | display: flex;
813 | width: 100%;
814 | height: 100%;
815 | justify-content: space-between;
816 | font-size: 3rem;
817 | align-items: center;
818 | }
819 |
820 | .member_modal_navigation svg {
821 | cursor: pointer;
822 | color: #fafafa;
823 | }
824 |
825 | .member_modal_image {
826 | width: 100%;
827 | height: 100vh;
828 | position: absolute;
829 | top: 0;
830 | object-fit: cover;
831 | object-position: center center;
832 | transition: opacity 0.3s 0s ease;
833 | }
834 |
835 | .member_modal_content {
836 | position: fixed;
837 | bottom: 0;
838 | background: var(--main-color);
839 | width: 100%;
840 | padding: 1rem;
841 | z-index: 2;
842 | }
843 |
844 | .member_modal_name span:first-child {
845 | text-transform: uppercase;
846 | font-size: var(--fd-xs);
847 | font-weight: 700;
848 | line-height: 2rem;
849 | margin-right: 0.4em;
850 | }
851 |
852 | .member_modal_name span {
853 | font-size: var(--f-sm);
854 | line-height: 1.7rem;
855 | }
856 |
857 | .member_modal_name {
858 | display: flex;
859 | align-items: flex-end;
860 | }
861 |
862 | .member_modal_socmed a {
863 | color: #111827;
864 | }
865 |
866 | .member_modal_socmed a:hover {
867 | cursor: pointer;
868 | }
869 |
870 | .apiReference {
871 | display: flex;
872 | flex-direction: column;
873 | gap: 1rem;
874 | font-size: var(--f-sm);
875 | }
876 |
877 | .dev_title {
878 | font-size: var(--f-lg);
879 | font-weight: 600;
880 | margin-bottom: 0.5rem;
881 | border-bottom: 1px solid #dddbdb;
882 | padding-bottom: 0.3em;
883 | }
884 |
885 | .dev_desc {
886 | margin-bottom: 1rem;
887 | }
888 |
889 | .dev_ref {
890 | padding-bottom: 1.5rem;
891 | display: flex;
892 | flex-direction: column;
893 | gap: 0.5rem;
894 | }
895 |
896 | .dev_ref h3 {
897 | font-size: var(--f-reg);
898 | font-weight: 600;
899 | }
900 |
901 | .dev_endpoint span {
902 | background: var(--sec-color);
903 | padding: 5px;
904 | }
905 |
906 | .dev_endpoint {
907 | display: flex;
908 | flex-direction: row;
909 | }
910 |
911 | .dev_endpoint span:first-child {
912 | font-size: var(--f-sm);
913 | font-weight: 700;
914 | line-height: 1.5rem;
915 | padding-right: 0.4em;
916 | text-transform: uppercase;
917 | }
918 |
919 | .dev_endpoint span:last-child {
920 | font-size: var(--f-sm);
921 | line-height: 1.5rem;
922 | }
923 |
924 | .dev_ref h4 {
925 | font-weight: 600;
926 | margin-bottom: 0.5rem;
927 | }
928 |
929 | .dev_params_table {
930 | display: block;
931 | width: 100%;
932 | overflow: auto;
933 | word-break: normal;
934 | word-break: keep-all;
935 | border-spacing: 0;
936 | border-collapse: collapse;
937 | }
938 |
939 | .dev_params_table tr {
940 | background-color: #fff;
941 | border-top: 1px solid #ccc;
942 | }
943 |
944 | .dev_params_table th {
945 | font-weight: 600;
946 | }
947 |
948 | .dev_params_table th,
949 | .dev_params_table td {
950 | padding: 6px 13px;
951 | border: 1px solid #ddd;
952 | }
953 |
954 | .dev_response pre {
955 | padding: 1rem;
956 | background: #eee;
957 | line-height: 1.5rem;
958 | font-size: 14px;
959 | overflow-x: auto;
960 | }
961 |
962 | @media screen and (min-width: 420px) {
963 | .member_block {
964 | grid-template-columns: repeat(2, 1fr);
965 | }
966 | }
967 |
968 | @media screen and (max-width: 767px) {
969 | .top_section::after {
970 | background-color: var(--sec-color);
971 | left: 50%;
972 | width: 50%;
973 | transform: translateX(-50%);
974 | }
975 |
976 | .heroImg {
977 | height: 100vh;
978 | background-image: url(https://twicejapan.com/static/twice/official/feature/4th_album/images/bg_main_sp_8ae2896befb2fe356873535f7eea715f.jpg);
979 | }
980 |
981 | .discography_flex {
982 | flex-direction: column;
983 | }
984 |
985 | .spotify_playlist {
986 | display: none;
987 | }
988 | }
989 |
990 | @media screen and (min-width: 768px) {
991 | .heroImg {
992 | background-image: url(/images/bg_image.jpg);
993 | }
994 |
995 | #header .inner {
996 | height: 50px;
997 | }
998 |
999 | .top_section::before {
1000 | content: "";
1001 | position: absolute;
1002 | width: 100%;
1003 | background-color: rgba(172, 172, 172, 0.2);
1004 | left: 0;
1005 | bottom: 0;
1006 | height: 2px;
1007 | }
1008 |
1009 | .top_section::after {
1010 | width: 170px;
1011 | background-color: var(--sec-color);
1012 | bottom: 0;
1013 | height: 2px;
1014 | z-index: 2;
1015 | }
1016 |
1017 | .content_block {
1018 | grid-template-columns: 1fr 1fr;
1019 | }
1020 |
1021 | .top_section {
1022 | display: flex;
1023 | justify-content: space-between;
1024 | align-items: center;
1025 | }
1026 |
1027 | .top_section span {
1028 | display: block;
1029 | }
1030 |
1031 | .top_section span a:hover {
1032 | color: var(--main-color);
1033 | }
1034 |
1035 | .top_section a {
1036 | color: #111827;
1037 | }
1038 |
1039 | .loadMore {
1040 | display: none;
1041 | }
1042 |
1043 | .news_item {
1044 | flex-direction: row;
1045 | gap: 2rem;
1046 | }
1047 |
1048 | .discography_block {
1049 | grid-template-columns: 1fr 1fr;
1050 | gap: 1rem;
1051 | }
1052 |
1053 | .news-title {
1054 | font-size: var(--fd-sm);
1055 | line-height: 3rem;
1056 | }
1057 |
1058 | .discography_flex_cover {
1059 | width: 50%;
1060 | }
1061 |
1062 | .discography_info_content {
1063 | width: 50%;
1064 | }
1065 |
1066 | .member_block {
1067 | grid-template-columns: repeat(auto-fit, minmax(284px, 1fr));
1068 | }
1069 |
1070 | .member_info h2 {
1071 | font-size: var(--fd-xs);
1072 | font-weight: 700;
1073 | line-height: 2rem;
1074 | }
1075 |
1076 | .member_item::after,
1077 | .member_item::before {
1078 | background: #182227;
1079 | width: 200%;
1080 | height: 200%;
1081 | position: absolute;
1082 | content: "";
1083 | opacity: 0.5;
1084 | transition: all 0.55s ease-in-out;
1085 | z-index: 1;
1086 | }
1087 |
1088 | .member_item::before {
1089 | right: 0;
1090 | bottom: 0;
1091 | transform: skew(-45deg) translateX(200%);
1092 | }
1093 |
1094 | .member_item::after {
1095 | top: 0;
1096 | left: 0;
1097 | transform: skew(-45deg) translateX(-200%);
1098 | }
1099 |
1100 | .member_item:hover::before {
1101 | transform: skew(-45deg) translateX(50%);
1102 | }
1103 |
1104 | .member_item:hover::after {
1105 | transform: skew(-45deg) translateX(-50%);
1106 | }
1107 |
1108 | .member_item:hover .member_info {
1109 | opacity: 1;
1110 | transition-delay: 0.5s;
1111 | z-index: 2;
1112 | color: #fafafa;
1113 | letter-spacing: 2px;
1114 | }
1115 | }
1116 |
1117 | @media screen and (min-width: 768px) and (max-width: 1023px) {
1118 | .content_item:last-child {
1119 | display: none;
1120 | }
1121 | }
1122 |
1123 | @media screen and (max-width: 860px) {
1124 | .discography_thumb img {
1125 | width: 25%;
1126 | float: left;
1127 | }
1128 |
1129 | .discography_item {
1130 | display: flex;
1131 | flex-direction: row;
1132 | gap: 5%;
1133 | }
1134 |
1135 | .discography_thumb {
1136 | width: 25%;
1137 | }
1138 |
1139 | .discography_item.skeleton {
1140 | padding-top: 10%;
1141 | background-color: #e9e9e9;
1142 | }
1143 |
1144 | .discography_item.skeleton::after {
1145 | content: "";
1146 | position: absolute;
1147 | width: 100%;
1148 | height: 100%;
1149 | display: block;
1150 | top: 0px;
1151 | background: #e5e4e4;
1152 | transform: translateX(-100%);
1153 | -webkit-animation: loadingSkeleton 0.8s infinite;
1154 | animation: loadingSkeleton 0.8s infinite;
1155 | }
1156 | }
1157 |
1158 | @media screen and (min-width: 860px) {
1159 | .smallContainer {
1160 | max-width: 820px;
1161 | margin: auto;
1162 | }
1163 |
1164 | .hero_container {
1165 | height: 100vh;
1166 | }
1167 |
1168 | .socmed_list {
1169 | opacity: 1;
1170 | z-index: 3;
1171 | position: fixed;
1172 | top: 50%;
1173 | transform: translateY(-50%);
1174 | color: var(--main-color);
1175 | }
1176 |
1177 | .socmed_list.right {
1178 | right: 40px;
1179 | }
1180 |
1181 | .socmed_list.left {
1182 | left: 40px;
1183 | }
1184 |
1185 | .socmed_list ul {
1186 | display: flex;
1187 | align-items: center;
1188 | justify-content: center;
1189 | flex-direction: column;
1190 | gap: 1.5rem;
1191 | }
1192 |
1193 | .socmed_list ul li a {
1194 | font-size: 20px;
1195 | color: var(--main-color);
1196 | }
1197 |
1198 | .socmed_list ul li {
1199 | transition: all 0.3s ease 0s;
1200 | }
1201 |
1202 | .socmed_list ul li:hover {
1203 | transform: translateY(-5px);
1204 | }
1205 |
1206 | .socmed_list ul:after {
1207 | content: "";
1208 | border: solid var(--main-color);
1209 | border-width: 0 1px 0 0;
1210 | width: 1px;
1211 | height: 2em;
1212 | margin: 0 auto 1.5em;
1213 | display: block;
1214 | }
1215 |
1216 | .socmed_list p {
1217 | margin: auto;
1218 | letter-spacing: 2px;
1219 | line-height: 20px;
1220 | font-size: var(--f-sm);
1221 | text-transform: uppercase;
1222 | font-weight: 500;
1223 | -webkit-writing-mode: vertical-rl;
1224 | -ms-writing-mode: tb-rl;
1225 | -o-writing-mode: vertical-rl;
1226 | writing-mode: vertical-rl;
1227 | }
1228 |
1229 | .discography_block {
1230 | grid-template-columns: 1fr 1fr 1fr;
1231 | gap: 1.5rem;
1232 | }
1233 |
1234 | .discography_thumb.skeleton {
1235 | padding-top: 100%;
1236 | background-color: #e9e9e9;
1237 | }
1238 |
1239 | .discography_thumb.skeleton::after {
1240 | content: "";
1241 | position: absolute;
1242 | width: 100%;
1243 | height: 100%;
1244 | display: block;
1245 | top: 0px;
1246 | background: #e5e4e4;
1247 | transform: translateX(-100%);
1248 | -webkit-animation: loadingSkeleton 0.8s infinite;
1249 | animation: loadingSkeleton 0.8s infinite;
1250 | }
1251 |
1252 | .discography_item::after,
1253 | .discography_item::before {
1254 | background: #182227;
1255 | width: 200%;
1256 | height: 200%;
1257 | position: absolute;
1258 | content: "";
1259 | opacity: 0.5;
1260 | transition: all 0.55s ease-in-out;
1261 | z-index: 1;
1262 | }
1263 |
1264 | .discography_item::before {
1265 | right: 0;
1266 | bottom: 0;
1267 | transform: skew(-45deg) translateX(200%);
1268 | }
1269 |
1270 | .discography_item::after {
1271 | top: 0;
1272 | left: 0;
1273 | transform: skew(-45deg) translateX(-200%);
1274 | }
1275 |
1276 | .discography_item:hover::before {
1277 | transform: skew(-45deg) translateX(50%);
1278 | }
1279 |
1280 | .discography_item:hover::after {
1281 | transform: skew(-45deg) translateX(-50%);
1282 | }
1283 |
1284 | .discography_info {
1285 | position: absolute;
1286 | letter-spacing: 0.05em;
1287 | right: 10px;
1288 | left: 10px;
1289 | bottom: 10px;
1290 | padding: 10px;
1291 | z-index: 2;
1292 | opacity: 0;
1293 | transition: all 0.5s ease;
1294 | }
1295 |
1296 | .discography_item:hover .discography_info {
1297 | opacity: 1;
1298 | }
1299 |
1300 | .discography_title {
1301 | font-weight: 600;
1302 | font-size: var(--f-lg);
1303 | color: #fafafa;
1304 | }
1305 |
1306 | .discography_releaseDate {
1307 | color: rgb(189, 189, 189);
1308 | }
1309 |
1310 | .twitter-section .container {
1311 | flex-direction: row;
1312 | }
1313 | }
1314 |
1315 | @media screen and (min-width: 1024px) {
1316 | .section_content {
1317 | padding-bottom: 2.5rem;
1318 | }
1319 |
1320 | .title_section,
1321 | .about_title {
1322 | font-size: var(--fd-md);
1323 | line-height: 3rem;
1324 | }
1325 |
1326 | .content_block {
1327 | grid-template-columns: 1fr 1fr 1fr;
1328 | }
1329 |
1330 | .content_title {
1331 | line-height: 1.5rem;
1332 | font-size: var(--f-md);
1333 | }
1334 |
1335 | .member_modal_image {
1336 | width: auto;
1337 | right: 0;
1338 | }
1339 |
1340 | .member_modal_content {
1341 | color: #fafafa;
1342 | width: 50%;
1343 | background: none;
1344 | height: 100%;
1345 | display: flex;
1346 | flex-direction: column;
1347 | justify-content: center;
1348 | left: 14%;
1349 | }
1350 |
1351 | .member_modal_name {
1352 | flex-direction: column;
1353 | align-items: flex-start;
1354 | gap: 1.5rem;
1355 | padding-bottom: 2rem;
1356 | }
1357 |
1358 | .member_modal_name span:first-child {
1359 | font-size: var(--fd-lg);
1360 | }
1361 |
1362 | .member_modal_name span:last-child {
1363 | font-size: var(--fd-xs);
1364 | font-weight: normal;
1365 | opacity: 0.5;
1366 | }
1367 |
1368 | .member_modal_content div {
1369 | font-size: var(--f-md);
1370 | line-height: 2rem;
1371 | }
1372 |
1373 | .member_modal_socmed a {
1374 | color: #fafafa;
1375 | }
1376 |
1377 | .member_modal_socmed a:hover {
1378 | color: var(--main-color);
1379 | }
1380 | }
1381 |
1382 | @media screen and (min-width: 1165px) {
1383 | .container {
1384 | max-width: 1125px;
1385 | margin: auto;
1386 | }
1387 | }
1388 |
1389 | @media screen and (min-width: 1540px) {
1390 | .largeContainer {
1391 | max-width: 1500px;
1392 | margin: auto;
1393 | }
1394 | }
1395 |
--------------------------------------------------------------------------------
/src/styles/header.module.css:
--------------------------------------------------------------------------------
1 | .header {
2 | padding: 6px 0;
3 | position: fixed;
4 | width: 100%;
5 | top: 0;
6 | text-transform: uppercase;
7 | font-weight: 500;
8 | transition: all 0.3s ease 0s;
9 | }
10 |
11 | .header a {
12 | color: var(--main-color);
13 | }
14 |
15 | .header a:hover {
16 | color: #434445;
17 | }
18 |
19 | .header.fixed a {
20 | color: #434445;
21 | }
22 |
23 | .header.fixed a:hover {
24 | color: var(--main-color);
25 | }
26 |
27 | .logo {
28 | height: 50px;
29 | width: 50px;
30 | transition: all 0.3s ease 0s;
31 | }
32 |
33 | .fixed {
34 | background: #fafafa;
35 | box-shadow: 0px 0px 2px -1px rgb(0 0 0 / 90%);
36 | }
37 |
38 | .navButton span {
39 | position: relative;
40 | z-index: 99;
41 | width: 30px;
42 | height: 2px;
43 | background-color: #fafafa;
44 | display: block;
45 | margin-block: 8px;
46 | border-radius: 4px;
47 | transition: transform 0.3s;
48 | }
49 |
50 | .fixed .navButton span {
51 | background: #434445;
52 | }
53 |
54 | .active .top {
55 | transform: translateY(10px) rotate(45deg);
56 | background: #434445;
57 | }
58 |
59 | .active .middle {
60 | opacity: 0;
61 | }
62 |
63 | .active .bottom {
64 | transform: translateY(-10px) rotate(-45deg);
65 | background: #434445;
66 | }
67 |
68 | .active .navButton a {
69 | transform: translateX(10px);
70 | }
71 |
72 | @media screen and (max-width: 767px) {
73 | .navbar {
74 | width: 100%;
75 | height: 100%;
76 | position: fixed;
77 | background: #fafafa;
78 | top: 0;
79 | left: 0;
80 | flex-direction: column;
81 | align-items: center;
82 | justify-content: center;
83 | gap: 1rem;
84 | opacity: var(--opacity);
85 | transition: opacity 0.3s;
86 | }
87 |
88 | .navbar a {
89 | color: #434445;
90 | }
91 |
92 | .left,
93 | .right {
94 | display: flex;
95 | align-items: center;
96 | justify-content: center;
97 | flex-direction: column;
98 | gap: 1rem;
99 | }
100 |
101 | .left li,
102 | .right li {
103 | padding: 0 1rem;
104 | }
105 | }
106 |
107 | @media screen and (min-width: 768px) {
108 | .navButton {
109 | display: none;
110 | }
111 |
112 | .logo {
113 | transform: translate(-50%, -50%);
114 | position: absolute;
115 | top: 50%;
116 | left: 50%;
117 | }
118 |
119 | .navbar {
120 | display: block !important;
121 | text-align: center;
122 | }
123 |
124 | .left {
125 | top: 0;
126 | right: 50%;
127 | margin-right: 60px;
128 | position: absolute;
129 | }
130 |
131 | .right {
132 | top: 0;
133 | left: 50%;
134 | margin-left: 60px;
135 | position: absolute;
136 | }
137 |
138 | .navbar ul {
139 | display: flex;
140 | height: 100%;
141 | align-items: center;
142 | gap: 2rem;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/types/components.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { ChildrenProps } from "./default";
3 |
4 | export interface OpacityTransition {
5 | opacity: number;
6 | DOMByID: string;
7 | time: number;
8 | }
9 |
10 | export interface LayoutProps extends ChildrenProps {
11 | title: string;
12 | metaDescription: string;
13 | metaImage?: string;
14 | style?: CSSProperties;
15 | }
16 |
17 | export interface ContainerProps extends ChildrenProps {
18 | className?: string;
19 | style?: CSSProperties;
20 | isLarge?: boolean;
21 | isSmall?: boolean;
22 | }
23 |
24 | export interface SectionProps extends ChildrenProps {
25 | title: string;
26 | className?: string;
27 | isHomePage?: boolean;
28 | withLoadMore?: boolean;
29 | }
30 |
31 | export interface NavLinkProps {
32 | className?: string;
33 | href: string;
34 | text: string;
35 | }
36 |
37 | export interface MemberItemProps {
38 | image: string;
39 | name: string;
40 | nickname: string;
41 | born?: string;
42 | bloodType?: string;
43 | instagram?: string;
44 | }
45 |
46 | export interface DiscographyType {
47 | _id: number;
48 | type: string;
49 | country: string;
50 | name: string;
51 | releaseDate: string;
52 | coverArt: string;
53 | content: string;
54 | tracks: string[];
55 | spotifyLink: string;
56 | video: string[];
57 | }
58 |
59 | export interface NewsType {
60 | _id: number;
61 | title: string;
62 | date: string;
63 | htmlContent: string;
64 | }
65 |
66 | export interface ContentItemProps {
67 | thumb: string;
68 | title: string;
69 | tag: string;
70 | href: string;
71 | }
72 |
--------------------------------------------------------------------------------
/src/types/default.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | export interface ChildrenProps {
4 | children: ReactNode;
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "baseUrl": ".",
18 | "paths": {
19 | "~/*": ["*"],
20 | "~components/*": ["src/components/*"],
21 | "~lib/*": ["src/lib/*"],
22 | "~styles/*": ["src/styles/*"],
23 | "~types/*": ["src/types/*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------