├── .babelrc ├── .github └── workflows │ ├── lighthouse.yml │ └── test.yml ├── .gitignore ├── .node-version ├── .prettierignore ├── .prettierrc.json ├── .storybook ├── main.js └── preview.js ├── README.md ├── README_EN.md ├── components ├── atomics │ ├── common │ │ ├── CloseButton │ │ │ ├── CloseButton.stories.tsx │ │ │ ├── CloseButton.tsx │ │ │ └── __tests__ │ │ │ │ ├── CloseButton.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ └── CloseButton.test.tsx.snap │ │ ├── CrossMark │ │ │ ├── CrossMark.stories.tsx │ │ │ └── CrossMark.tsx │ │ ├── H2.tsx │ │ ├── HamburgerMenu │ │ │ ├── HamburgerMenu.tsx │ │ │ └── __tests__ │ │ │ │ └── HamburgerMenu.test.tsx │ │ ├── Logo.tsx │ │ ├── Ring │ │ │ ├── Ring.stories.tsx │ │ │ └── Ring.tsx │ │ ├── SidewaysLines │ │ │ ├── SidewaysLines.stories.tsx │ │ │ └── SidewaysLines.tsx │ │ ├── Triangle │ │ │ ├── Triangle.stories.tsx │ │ │ └── Triangle.tsx │ │ └── YoutubeEmbedder │ │ │ ├── YoutubeEmbedder.stories.tsx │ │ │ └── YoutubeEmbedder.tsx │ ├── gatya │ │ └── SingleMagicCircle.tsx │ ├── home │ │ ├── BigSlideText.tsx │ │ └── SlideText.tsx │ └── info │ │ ├── EmbedBox │ │ ├── EmbedBox.stories.tsx │ │ └── EmbedBox.tsx │ │ ├── ThumbnailLink │ │ ├── ThumbnailLink.stories.tsx │ │ └── ThumbnailLink.tsx │ │ └── VTuberIcon │ │ ├── VTuberIcon.stories.tsx │ │ └── VTuberIcon.tsx ├── molecules │ ├── common │ │ └── ListRow │ │ │ ├── ListRow.tsx │ │ │ └── __tests__ │ │ │ ├── ListRow.test.tsx │ │ │ └── __snapshots__ │ │ │ └── ListRow.test.tsx.snap │ └── info │ │ └── VTuberIconAndName │ │ ├── VTuberIconAndName.stories.tsx │ │ └── VTuberIconAndName.tsx ├── organisms │ ├── common │ │ └── NavBar │ │ │ ├── ExpandCircle.tsx │ │ │ ├── NavBar.tsx │ │ │ ├── NavBarCrossMarks.tsx │ │ │ ├── NavBarLeft │ │ │ ├── CenterCircle │ │ │ │ ├── CenterCircle.stories.tsx │ │ │ │ ├── CenterCircle.tsx │ │ │ │ ├── OutsideRotateLine.tsx │ │ │ │ └── animationBaseDelay.ts │ │ │ ├── CloseAnimation.tsx │ │ │ ├── NavBarLeft.tsx │ │ │ └── ThreeSlashes │ │ │ │ ├── ThreeSlashes.stories.tsx │ │ │ │ └── ThreeSlashes.tsx │ │ │ ├── NavBarMenu │ │ │ └── NavBarMenu.tsx │ │ │ ├── NavBarRings.tsx │ │ │ ├── NavBarSidewaysLines.tsx │ │ │ ├── NavBarTop │ │ │ ├── NavBarTitle.tsx │ │ │ └── NavBarTop.tsx │ │ │ └── NavBarTriangles.tsx │ ├── gatya │ │ ├── AngeCard │ │ │ ├── AngeCard.tsx │ │ │ ├── AngeDescription.tsx │ │ │ ├── AngeImg.tsx │ │ │ ├── AngeName.tsx │ │ │ ├── SSRText.tsx │ │ │ ├── WhiteBG.tsx │ │ │ └── constants.ts │ │ ├── AngeTriangle.tsx │ │ ├── BlackTransition │ │ │ ├── BlackCircle.tsx │ │ │ └── BlackTransition.tsx │ │ ├── Flash │ │ │ ├── CircleFlash.tsx │ │ │ ├── Flash.tsx │ │ │ ├── HideScreenCircleFlash.tsx │ │ │ ├── LineFlash.tsx │ │ │ └── WhiteCircle.tsx │ │ ├── MagicCircle │ │ │ ├── MagicCircle.tsx │ │ │ └── SummonText.tsx │ │ ├── OmataseMattaText.tsx │ │ ├── ShowAngeCard.tsx │ │ ├── SmallMagicCircleMap │ │ │ ├── SmallMagicCircle.tsx │ │ │ └── SmallMagicCircleMap.tsx │ │ └── description │ │ │ └── DecriptionMain.tsx │ ├── home │ │ ├── CircleSlide │ │ │ ├── CircleMain.tsx │ │ │ ├── CircleSlide.tsx │ │ │ └── SlideController │ │ │ │ ├── SlideContent │ │ │ │ ├── NormalSlideContent │ │ │ │ │ ├── NormalOneSlideContent.tsx │ │ │ │ │ └── NormalSlideContent.tsx │ │ │ │ ├── SlideContent.tsx │ │ │ │ ├── SplitedSlideContent │ │ │ │ │ └── SplitedSlideContent.tsx │ │ │ │ ├── contentDatas │ │ │ │ │ ├── contentDataType.ts │ │ │ │ │ ├── meritAndDemeritData.tsx │ │ │ │ │ ├── sanbakaData.tsx │ │ │ │ │ └── streamingStyle.tsx │ │ │ │ └── slideAnimation.ts │ │ │ │ └── SlideController.tsx │ │ ├── HomeAnge │ │ │ ├── HomeAnge.tsx │ │ │ └── SpeechBubble.tsx │ │ ├── HomeBG.tsx │ │ └── OpenerFromStartAnimation.tsx │ ├── info │ │ ├── AngeDescriptionArea │ │ │ ├── AngeDescription.tsx │ │ │ ├── AngeDescriptionArea.tsx │ │ │ ├── AngeIcon.tsx │ │ │ ├── AngeProfile.tsx │ │ │ └── Background.tsx │ │ ├── AngeLinksArea │ │ │ └── AngeLinksArea.tsx │ │ ├── EmbedContentArea │ │ │ └── EmbedContentArea.tsx │ │ └── WhatIsSanbakaArea │ │ │ └── WhatIsSanbakaArea.tsx │ ├── license │ │ └── LicenseBlock.tsx │ └── startAnimation │ │ ├── Anime1 │ │ ├── Anime1.tsx │ │ ├── CenterCircles.tsx │ │ ├── CenterText.tsx │ │ ├── LeftBottomBlackCircle.tsx │ │ ├── LeftTopCircles.tsx │ │ ├── RightBottomCircle.tsx │ │ └── RightTopCircles.tsx │ │ ├── Anime2 │ │ ├── Anime2.tsx │ │ ├── BGBorderText.tsx │ │ ├── BottomWave.tsx │ │ ├── CenterWave.tsx │ │ ├── CloseAnime2.tsx │ │ ├── ExpandCircle.tsx │ │ ├── WaveBG.tsx │ │ └── WaveScreen.tsx │ │ ├── Anime3 │ │ ├── AngeImage.tsx │ │ ├── AngeName.tsx │ │ ├── Anime3.tsx │ │ ├── BGBorderText.tsx │ │ ├── CloseAnime3.tsx │ │ └── OpenAnime3.tsx │ │ ├── Loading │ │ ├── Loading.tsx │ │ ├── LoadingText.tsx │ │ └── ThreeSquares.tsx │ │ └── LoadingToAnime1 │ │ ├── AnimateSquare.tsx │ │ ├── BackgroundAnimation.tsx │ │ ├── LoadingToAnime1.tsx │ │ └── animationOrder.ts └── templates │ ├── CSR.tsx │ └── PageWrapper.tsx ├── constants ├── breakpoints.ts ├── colors.ts ├── cssProps.ts ├── gatya │ ├── animation_order.ts │ ├── omataseMattaSetting.ts │ └── zindex.ts ├── home │ └── zindex.ts └── zindex.ts ├── hooks ├── useAnimationRestarter.tsx ├── useDidMount.ts └── useIntersectionObserver.ts ├── index.d.ts ├── jest.config.js ├── lighthouserc.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── gatya.tsx ├── gatya │ └── description.tsx ├── index.tsx ├── info.tsx ├── license.tsx ├── ogp.tsx └── startanimation.tsx ├── public ├── favicons │ ├── android-chrome-192x192.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── imgs │ ├── ange-basic.png │ ├── ange-doctor.png │ ├── common │ │ ├── github-logo.png │ │ └── nijisanji-logo.png │ ├── gatya │ │ ├── ange-hey.png │ │ ├── ange-light-dress.png │ │ ├── fourth_in.png │ │ ├── most_in.png │ │ ├── second_in.png │ │ └── third_in.png │ ├── info │ │ ├── mashmellow-logo.png │ │ ├── nijisanji-wiki.png │ │ ├── rectangle.png │ │ └── twitter-logo.png │ ├── inui-toko.png │ ├── ogp.png │ └── rize-heruesta.png ├── load-adobe-fonts.js └── svgs │ ├── common │ ├── ange_triangle_sharp.svg │ ├── black_triangle.svg │ ├── one_black_circle.svg │ ├── one_yellow_circle.svg │ ├── red-homeIcon.svg │ ├── red-infoIcon.svg │ ├── red-licenseIcon.svg │ ├── red-presentIcon.svg │ ├── red_triangle.svg │ ├── two_yellow_two_red_circle.svg │ ├── white-homeIcon.svg │ ├── white-infoIcon.svg │ ├── white-licenseIcon.svg │ ├── white-presentIcon.svg │ ├── white_triangle.svg │ └── yellow_triangle.svg │ ├── gatya │ ├── ange_triangle.svg │ ├── flash_line.svg │ ├── fourth_in.svg │ ├── most_in.svg │ ├── red_circle.svg │ ├── second_in.svg │ ├── summon.svg │ ├── third_in.svg │ └── white_circle.svg │ ├── home │ ├── logo.svg │ └── red-logo.svg │ └── startanimation │ ├── black-wave.svg │ ├── red-wave.svg │ ├── wave.svg │ └── yellow-wave.svg ├── redux ├── modules │ ├── isStartSummonAnimation.ts │ ├── sizes.ts │ ├── sounds.ts │ ├── startAnimation.ts │ └── themes.ts └── store.ts ├── renovate.json ├── stories ├── Button.stories.tsx ├── Button.tsx ├── Header.stories.tsx ├── Header.tsx ├── Introduction.stories.mdx ├── Page.stories.tsx ├── Page.tsx ├── assets │ ├── code-brackets.svg │ ├── colors.svg │ ├── comments.svg │ ├── direction.svg │ ├── flow.svg │ ├── plugin.svg │ ├── repo.svg │ └── stackalt.svg ├── button.css ├── header.css └── page.css ├── styles ├── commonAnimation.ts └── global.scss ├── systems ├── getRandomInt.ts └── sizeTypeJudge.ts ├── tsconfig.json ├── typing ├── AnimationProps.ts ├── Color.ts ├── LocationXY.ts └── SizeType.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [["styled-components", { "ssr": true }]] 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/lighthouse.yml: -------------------------------------------------------------------------------- 1 | name: light house CI 2 | on: [push] 3 | jobs: 4 | lhci: 5 | name: Lighthouse 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | ref: ${{ github.event.pull_request.head.sha }} 11 | - name: Use Node.js 14.x 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: 14.x 15 | - name: yarn install, build 16 | run: | 17 | yarn install 18 | yarn build 19 | - name: run Lighthouse CI 20 | run: | 21 | npm install -g @lhci/cli@0.6.x 22 | lhci autorun 23 | env: 24 | LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} 25 | - name: Slack Notification on SUCCESS 26 | if: success() 27 | uses: tokorom/action-slack-incoming-webhook@main 28 | env: 29 | INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 30 | with: 31 | text: Successfully run lighthouse 32 | attachments: | 33 | [ 34 | { 35 | "color": "good", 36 | "author_name": "${{ github.actor }}", 37 | "author_icon": "${{ github.event.sender.avatar_url }}", 38 | "fields": [ 39 | { 40 | "title": "コミットメッセージ", 41 | "value": "${{ github.event.head_commit.message }}" 42 | }, 43 | { 44 | "title": "GitHub Actions URL", 45 | "value": "${{ github.event.repository.url }}/actions/runs/${{ github.run_id }}" 46 | }, 47 | { 48 | "title": "差分表示 URL", 49 | "value": "${{ github.event.compare }}" 50 | } 51 | ] 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: test CI 5 | 6 | on: 7 | push: 8 | branches: [dev] 9 | pull_request: 10 | branches: [dev] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: yarn install 27 | - run: yarn test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | yarn-error.log 4 | .DS_Store -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 15.9.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | #my def 24 | .gitignore 25 | /.next 26 | /.github 27 | /public -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf" 3 | } 4 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: [ 3 | "../stories/**/*.stories.mdx", 4 | "../stories/**/*.stories.@(js|jsx|ts|tsx)", 5 | "../components/**/*.stories.tsx", 6 | ], 7 | addons: ["@storybook/addon-links", "@storybook/addon-essentials"], 8 | webpackFinal: async (config) => { 9 | const rules = config.module.rules; 10 | const fileLoaderRule = rules.find((rule) => rule.test.test(".svg")); 11 | fileLoaderRule.exclude = /\.svg$/; 12 | 13 | rules.push({ 14 | test: /\.svg$/, 15 | enforce: "pre", 16 | loader: require.resolve("@svgr/webpack"), 17 | }); 18 | return config; 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: "^on[A-Z].*" }, 3 | }; 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # アンジュカトリーナ非公式ファンサイト「アンゲ・カワイーナ」:balance_scale: 2 | 3 | [ENGLISH README](./README_EN.md) 4 | 5 | にじさんじライバー アンジュカトリーナさんの非公式ファンサイトです。 6 | 7 | https://ange-kawaina.umashiba.dev/ 8 | 9 | アンジュカトリーナ非公式ファンサイト「アンゲ・カワイーナ」サムネイル 10 | 11 | # こだわり 12 | 13 | 「錬金術師っぽさ」みたいなのを意識してアンジュカトリーナさんのよく使うカラーなどを取り入れながらデザインしました! 14 | 15 | また、CSS アニメーションを使い柔らかいけど触っていて気持ちいいサイトになるよう意識して作りました。 16 | 特にスタートアニメーション、サイドバーアニメーション、ガチャのアニメーションはこだわって作ったのでぜひ PC で開いて見てみてください! 17 | 18 | https://user-images.githubusercontent.com/49422601/185646059-12fb82af-5c1f-4ba7-a29a-c2c4a5f75935.mov 19 | 20 | # LICENSE 21 | 22 | このファンサイトは以下のにじさんじ二次創作ガイドラインに従い作成させていただきました。 23 | 二次創作を許可していただきありがとうございます。 24 | https://event.nijisanji.app/guidelines/ 25 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Ange Katrina unofficial fan site 【Ange Kawaina】 :balance_scale: 2 | 3 | This is unofficial fan site of NIJISANJI streamer Ange Katrina 4 | 5 | https://ange-kawaina.umashiba.dev/ 6 | 7 | Ange Katrina unofficial fan [site Ange Kawaina] thumbnail 8 | 9 | # good point of this fan site. 10 | 11 | - I made the fan site in colors that Ange might use. 12 | - This fan site was designed with the image of an alchemist in mind 13 | - I use CSS animation in this fan site for interesting experience. 14 | 15 | https://user-images.githubusercontent.com/49422601/185646059-12fb82af-5c1f-4ba7-a29a-c2c4a5f75935.mov 16 | 17 | # LICENSE 18 | 19 | The fan site was created regarding "NIJISANJI fan fiction guideline". Thank you for permitting fan fiction. 20 | https://event.nijisanji.app/guidelines/ 21 | -------------------------------------------------------------------------------- /components/atomics/common/CloseButton/CloseButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react/types-6-0"; 2 | import CloseButton, { CloseButtonProps } from "./CloseButton"; 3 | 4 | export default { 5 | title: "common/CloseButton", 6 | component: CloseButton, 7 | args: { 8 | displayAnimationDelay: 500, 9 | disableAnimationDelay: 500, 10 | runDisplayAnimation: true, 11 | runCloseAnimation: false, 12 | }, 13 | } as Meta; 14 | 15 | const Template: Story = (args) => ; 16 | 17 | export const NormalClose = Template.bind({}); 18 | -------------------------------------------------------------------------------- /components/atomics/common/CloseButton/__tests__/CloseButton.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CloseButton from "../CloseButton"; 3 | import renderer from "react-test-renderer"; 4 | 5 | test("sample", () => { 6 | const component = renderer.create(); 7 | const tree = component.toJSON() as renderer.ReactTestRendererJSON; 8 | expect(tree).toMatchSnapshot(); 9 | }); 10 | -------------------------------------------------------------------------------- /components/atomics/common/CloseButton/__tests__/__snapshots__/CloseButton.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`sample 1`] = ` 4 |
7 | 23 |
24 | `; 25 | -------------------------------------------------------------------------------- /components/atomics/common/CrossMark/CrossMark.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react/types-6-0"; 2 | import styled from "styled-components"; 3 | import { 4 | ANGE_BLACK, 5 | ANGE_RED, 6 | ANGE_YELLOW, 7 | } from "../../../../constants/colors"; 8 | import CrossMark, { CrossMarkProps } from "./CrossMark"; 9 | 10 | const Decorator = styled.div` 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | width: 140px; 15 | height: 140px; 16 | border: 2px dotted blue; 17 | `; 18 | 19 | export default { 20 | title: "common/CrossMark", 21 | component: CrossMark, 22 | decorators: [ 23 | (Story) => ( 24 | 25 | 26 | 27 | ), 28 | ], 29 | } as Meta; 30 | 31 | const Template: Story = (args) => ( 32 | 33 | ); 34 | 35 | export const YellowCross = Template.bind({}); 36 | 37 | YellowCross.args = { 38 | color: ANGE_YELLOW, 39 | }; 40 | 41 | export const BlackCross = Template.bind({}); 42 | 43 | BlackCross.args = { 44 | color: ANGE_BLACK, 45 | }; 46 | 47 | export const RedCross = Template.bind({}); 48 | 49 | RedCross.args = { 50 | color: ANGE_RED, 51 | }; 52 | 53 | export const Cross60px = Template.bind({}); 54 | 55 | Cross60px.args = { 56 | color: ANGE_RED, 57 | widthHeight: "60px", 58 | }; 59 | 60 | export const ExpandRotateAnimationCross = Template.bind({}); 61 | 62 | ExpandRotateAnimationCross.args = { 63 | color: ANGE_RED, 64 | animation: "expandRotate", 65 | }; 66 | 67 | export const SlideAnimationCross = Template.bind({}); 68 | 69 | SlideAnimationCross.args = { 70 | color: ANGE_YELLOW, 71 | animation: "slideLine", 72 | }; 73 | -------------------------------------------------------------------------------- /components/atomics/common/H2.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { TA_F1_BLOCK_LINE } from "../../../constants/cssProps"; 4 | import { ANGE_BLACK } from "../../../constants/colors"; 5 | 6 | interface Props { 7 | text: string; 8 | color?: string; 9 | } 10 | 11 | const Text = styled.h2<{ color: string }>` 12 | ${TA_F1_BLOCK_LINE} 13 | font-size: 24px; 14 | color: ${({ color }) => color}; 15 | `; 16 | 17 | const H2: React.FC = ({ text, color = ANGE_BLACK }: Props) => { 18 | return {text}; 19 | }; 20 | 21 | export default H2; 22 | -------------------------------------------------------------------------------- /components/atomics/common/HamburgerMenu/HamburgerMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { ANGE_WHITE, ANGE_LIVE_BACK_COLOR } from "../../../../constants/colors"; 4 | import { 5 | sm_breakpoint, 6 | tablet_breakpoint, 7 | } from "../../../../constants/breakpoints"; 8 | 9 | interface Props { 10 | onClickFC?: () => void; 11 | } 12 | 13 | const Circle = styled.div` 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | background-color: ${ANGE_WHITE}; 19 | border-radius: 50%; 20 | position: fixed; 21 | cursor: pointer; 22 | width: 45px; 23 | height: 45px; 24 | top: 8px; 25 | right: 8px; 26 | 27 | @media (min-width: ${sm_breakpoint}px) { 28 | width: 70px; 29 | height: 70px; 30 | top: 12px; 31 | right: 12px; 32 | } 33 | @media (min-width: ${tablet_breakpoint}px) { 34 | width: 80px; 35 | height: 80px; 36 | top: 16px; 37 | right: 16px; 38 | } 39 | `; 40 | 41 | const Line = styled.div` 42 | display: flex; 43 | background-color: ${ANGE_LIVE_BACK_COLOR}; 44 | width: 65%; 45 | height: 4px; 46 | margin: 3px 0 3px 0; 47 | border-radius: 2px; 48 | @media (min-width: ${sm_breakpoint}px) { 49 | width: 60%; 50 | height: 6px; 51 | margin: 4px 0 4px 0; 52 | border-radius: 3px; 53 | } 54 | @media (min-width: ${tablet_breakpoint}px) { 55 | height: 8px; 56 | margin: 4px 0 4px 0; 57 | border-radius: 4px; 58 | } 59 | `; 60 | 61 | const HamburgerMenu: React.FC = (props: Props) => { 62 | return ( 63 | 64 | 65 | 66 | 67 | 68 | ); 69 | }; 70 | 71 | export default HamburgerMenu; 72 | -------------------------------------------------------------------------------- /components/atomics/common/HamburgerMenu/__tests__/HamburgerMenu.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { unmountComponentAtNode, render } from "react-dom"; 3 | import { act } from "react-dom/test-utils"; 4 | import HamburgerMenu from "../HamburgerMenu"; 5 | 6 | describe("functional test", () => { 7 | let container: HTMLElement | null = null; 8 | beforeEach(() => { 9 | // setup a DOM element as a render target 10 | container = document.createElement("div"); 11 | // container *must* be attached to document so events work correctly. 12 | document.body.appendChild(container); 13 | }); 14 | 15 | afterEach(() => { 16 | // cleanup on exiting 17 | if (container !== null) { 18 | unmountComponentAtNode(container); 19 | container.remove(); 20 | container = null; 21 | } 22 | }); 23 | 24 | test("test: run onClickFC when clicked", () => { 25 | let isRun = false; 26 | const mockFn = jest.fn(() => (isRun = true)); 27 | act(() => { 28 | render(, container); 29 | }); 30 | 31 | const menu = document.querySelector("[data-testid=hamburger-menu]"); 32 | expect(isRun).toBe(false); 33 | if (menu !== null) { 34 | act(() => { 35 | menu.dispatchEvent(new MouseEvent("click", { bubbles: true })); 36 | }); 37 | } 38 | expect(isRun).toBe(true); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /components/atomics/common/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import WhiteLogoImg from "../../../public/svgs/home/logo.svg"; 3 | import RedLogoImg from "../../../public/svgs/home/red-logo.svg"; 4 | import styled from "styled-components"; 5 | import { sm_breakpoint } from "../../../constants/breakpoints"; 6 | 7 | const Wrapper = styled.div` 8 | position: relative; 9 | top: 10px; 10 | left: 10px; 11 | width: 150px; 12 | max-height: 100px; 13 | @media (min-width: ${sm_breakpoint}px) { 14 | width: 300px; 15 | } 16 | `; 17 | 18 | interface Props { 19 | bgColor: "red" | "white"; 20 | } 21 | 22 | const Logo: React.VFC = ({ bgColor = "white" }) => { 23 | const LogoImg = bgColor === "white" ? WhiteLogoImg : RedLogoImg; 24 | 25 | return ( 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default Logo; 33 | -------------------------------------------------------------------------------- /components/atomics/common/Ring/Ring.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react/types-6-0"; 2 | import { 3 | ANGE_BLACK, 4 | ANGE_RED, 5 | ANGE_YELLOW, 6 | } from "../../../../constants/colors"; 7 | import Ring, { RingProps } from "./Ring"; 8 | 9 | export default { 10 | title: "common/Ring", 11 | component: Ring, 12 | } as Meta; 13 | 14 | const Template: Story = (args) => ( 15 | 16 | ); 17 | 18 | export const YellowRing = Template.bind({}); 19 | 20 | YellowRing.args = { 21 | color: ANGE_YELLOW, 22 | }; 23 | 24 | export const BlackRing = Template.bind({}); 25 | 26 | BlackRing.args = { 27 | color: ANGE_BLACK, 28 | }; 29 | 30 | export const RedRing = Template.bind({}); 31 | 32 | RedRing.args = { 33 | color: ANGE_RED, 34 | }; 35 | 36 | export const Ring60px = Template.bind({}); 37 | 38 | Ring60px.args = { 39 | widthHeight: "60px", 40 | color: ANGE_YELLOW, 41 | }; 42 | 43 | export const ExpandRing = Template.bind({}); 44 | 45 | ExpandRing.args = { 46 | animation: "expand", 47 | color: ANGE_RED, 48 | }; 49 | 50 | export const Expand60pxRing = Template.bind({}); 51 | 52 | Expand60pxRing.args = { 53 | animation: "expand", 54 | color: ANGE_RED, 55 | widthHeight: "60px", 56 | }; 57 | -------------------------------------------------------------------------------- /components/atomics/common/SidewaysLines/SidewaysLines.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react/types-6-0"; 2 | import styled from "styled-components"; 3 | import SidewaysLines, { SidewaysLinesProps } from "./SidewaysLines"; 4 | 5 | const Decorator = styled.div` 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | margin: 36px; 10 | `; 11 | 12 | export default { 13 | title: "common/SidewaysLines", 14 | component: SidewaysLines, 15 | decorators: [ 16 | (Story) => ( 17 | 18 | 19 | 20 | ), 21 | ], 22 | } as Meta; 23 | 24 | const Template: Story = (args) => ( 25 | 26 | ); 27 | 28 | export const LeftRightCenter = Template.bind({}); 29 | 30 | LeftRightCenter.args = { 31 | pattern: "leftRightCenter", 32 | animation: "none", 33 | }; 34 | 35 | export const RightCenterLeft = Template.bind({}); 36 | 37 | RightCenterLeft.args = { 38 | pattern: "rightCenterLeft", 39 | animation: "none", 40 | }; 41 | 42 | export const CenterLeftRight = Template.bind({}); 43 | 44 | CenterLeftRight.args = { 45 | pattern: "centerLeftRight", 46 | animation: "none", 47 | }; 48 | 49 | export const SlideFadeinLeftRightCenter = Template.bind({}); 50 | 51 | SlideFadeinLeftRightCenter.args = { 52 | pattern: "centerLeftRight", 53 | animation: "slideFadein", 54 | animationDelayMs: 400, 55 | }; 56 | 57 | export const SlideFadeinRightCenterLeft = Template.bind({}); 58 | 59 | SlideFadeinRightCenterLeft.args = { 60 | pattern: "rightCenterLeft", 61 | animation: "slideFadein", 62 | animationDelayMs: 400, 63 | }; 64 | 65 | export const SlideFadeinCenterLeftRight = Template.bind({}); 66 | 67 | SlideFadeinCenterLeftRight.args = { 68 | pattern: "centerLeftRight", 69 | animation: "slideFadein", 70 | animationDelayMs: 400, 71 | }; 72 | -------------------------------------------------------------------------------- /components/atomics/common/Triangle/Triangle.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react/types-6-0"; 2 | import styled from "styled-components"; 3 | import Triangle, { TriangleProps } from "./Triangle"; 4 | 5 | const Decorator = styled.div` 6 | position: relative; 7 | top: 0; 8 | left: 0; 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | width: 140px; 13 | height: 140px; 14 | background-color: skyblue; 15 | `; 16 | 17 | export default { 18 | title: "common/Triangle", 19 | component: Triangle, 20 | decorators: [ 21 | (Story) => ( 22 | 23 | 24 | 25 | ), 26 | ], 27 | } as Meta; 28 | 29 | const Template: Story = (args) => ; 30 | 31 | export const YellowTriangle = Template.bind({}); 32 | 33 | YellowTriangle.args = { 34 | color: "yellow", 35 | }; 36 | 37 | export const WhiteTriangle = Template.bind({}); 38 | 39 | WhiteTriangle.args = { 40 | color: "white", 41 | }; 42 | 43 | export const BlackTriangle = Template.bind({}); 44 | 45 | BlackTriangle.args = { 46 | color: "black", 47 | }; 48 | 49 | export const RedTriangle = Template.bind({}); 50 | 51 | RedTriangle.args = { 52 | color: "red", 53 | }; 54 | 55 | export const AngeTriangle = Template.bind({}); 56 | 57 | AngeTriangle.args = { 58 | color: "ange", 59 | }; 60 | 61 | export const ExpandTriangle = Template.bind({}); 62 | 63 | ExpandTriangle.args = { 64 | color: "black", 65 | animation: "boundExpand", 66 | animationDelayMs: 200, 67 | }; 68 | 69 | export const ExpandAngeTriangle = Template.bind({}); 70 | 71 | ExpandAngeTriangle.args = { 72 | color: "ange", 73 | animation: "boundExpand", 74 | animationDelayMs: 200, 75 | }; 76 | 77 | export const AbsolutePositionTriangle = Template.bind({}); 78 | 79 | AbsolutePositionTriangle.args = { 80 | color: "black", 81 | top: 0, 82 | left: 0, 83 | }; 84 | 85 | export const RotateTriangle = Template.bind({}); 86 | 87 | RotateTriangle.args = { 88 | color: "black", 89 | rotate: "45deg", 90 | }; 91 | 92 | export const Triangle60px = Template.bind({}); 93 | 94 | Triangle60px.args = { 95 | color: "black", 96 | width: "60px", 97 | }; 98 | -------------------------------------------------------------------------------- /components/atomics/common/Triangle/Triangle.tsx: -------------------------------------------------------------------------------- 1 | import YellowTriangleImg from "../../../../public/svgs/common/yellow_triangle.svg"; 2 | import WhiteTriangleImg from "../../../../public/svgs/common/white_triangle.svg"; 3 | import AngeTriangleImg from "../../../../public/svgs/common/ange_triangle_sharp.svg"; 4 | import BlackTriangleImg from "../../../../public/svgs/common/black_triangle.svg"; 5 | import RedTriangleImg from "../../../../public/svgs/common/red_triangle.svg"; 6 | import styled, { css } from "styled-components"; 7 | import { multiBoundExpand } from "../../../../styles/commonAnimation"; 8 | 9 | export interface TriangleProps { 10 | color: "yellow" | "white" | "ange" | "black" | "red"; 11 | animation?: "none" | "boundExpand"; 12 | animationDelayMs?: number; 13 | width?: string; 14 | rotate?: string; 15 | top?: string | number; 16 | left?: string | number; 17 | bottom?: string | number; 18 | right?: string | number; 19 | } 20 | 21 | const Wrapper = styled.div>>` 22 | width: ${({ width }) => width}; 23 | position: absolute; 24 | top: ${({ top }) => top}; 25 | left: ${({ left }) => left}; 26 | bottom: ${({ bottom }) => bottom}; 27 | right: ${({ right }) => right}; 28 | ${({ animation, animationDelayMs }) => 29 | animation === "boundExpand" && 30 | css` 31 | animation: ${multiBoundExpand()} 1500ms both ease-in-out 32 | ${animationDelayMs}ms; 33 | `} 34 | `; 35 | 36 | const RotateWrapper = styled.div>>` 37 | transform: rotate(${({ rotate }) => rotate}); 38 | `; 39 | 40 | const Triangle: React.FC = ({ 41 | color, 42 | animation = "none", 43 | animationDelayMs = 100, 44 | width = "100px", 45 | rotate = "0deg", 46 | top = "auto", 47 | left = "auto", 48 | bottom = "auto", 49 | right = "auto", 50 | }: TriangleProps) => { 51 | return ( 52 | 61 | 62 | {color === "yellow" ? ( 63 | 64 | ) : color === "white" ? ( 65 | 66 | ) : color === "black" ? ( 67 | 68 | ) : color === "red" ? ( 69 | 70 | ) : ( 71 | 72 | )} 73 | 74 | 75 | ); 76 | }; 77 | 78 | export default Triangle; 79 | -------------------------------------------------------------------------------- /components/atomics/common/YoutubeEmbedder/YoutubeEmbedder.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | import YoutubeEmbedder, { YouTubeEmbedderProps } from "./YoutubeEmbedder"; 3 | import styled from "styled-components"; 4 | import { ANGE_WHITE } from "../../../../constants/colors"; 5 | import { tablet_breakpoint } from "../../../../constants/breakpoints"; 6 | 7 | const Wrapper = styled.div` 8 | width: 100%; 9 | background-color: ${ANGE_WHITE}; 10 | padding: 24px 0; 11 | 12 | @media (min-width: ${tablet_breakpoint}px) { 13 | width: 800px; 14 | } 15 | `; 16 | 17 | export default { 18 | title: "common/YoutubeEmbedder", 19 | component: YoutubeEmbedder, 20 | } as Meta; 21 | 22 | const Template: Story = () => ( 23 | 24 | 25 | 34 | 35 | 36 | ); 37 | 38 | export const NormalYoutubeEmbedder = Template.bind({}); 39 | -------------------------------------------------------------------------------- /components/atomics/common/YoutubeEmbedder/YoutubeEmbedder.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useRef, useState, useEffect } from "react"; 2 | import styled from "styled-components"; 3 | import useDidMount from "../../../../hooks/useDidMount"; 4 | 5 | export interface YouTubeEmbedderProps { 6 | children: ReactNode; 7 | } 8 | 9 | const videoRatio = 0.5625; // 315 / 560 youtube video ratio 10 | 11 | const Wrapper = styled.div<{ height: string }>` 12 | width: 100%; 13 | height: ${({ height }) => height}; 14 | `; 15 | 16 | const useYouTubeHeight = () => { 17 | const [height, changeHeight] = useState(0); 18 | const ref = useRef(null); 19 | 20 | const calcAndChangeHeight = () => { 21 | ref.current && changeHeight(ref.current.clientWidth * videoRatio); 22 | }; 23 | 24 | useDidMount(() => { 25 | calcAndChangeHeight(); 26 | 27 | ref.current && 28 | (function () { 29 | const iframes = ref.current.getElementsByTagName("iframe"); 30 | iframes[0].setAttribute("width", "100%"); 31 | iframes[0].setAttribute("height", "100%"); 32 | })(); 33 | }); 34 | 35 | useEffect(() => { 36 | window.addEventListener("resize", calcAndChangeHeight); 37 | return function cleanup() { 38 | window.removeEventListener("resize", calcAndChangeHeight); 39 | }; 40 | }, []); 41 | 42 | return [ref, height] as [React.RefObject, number]; 43 | }; 44 | 45 | const YoutubeEmbedder: React.VFC = ({ children }) => { 46 | const [ref, height] = useYouTubeHeight(); 47 | 48 | return ( 49 | 50 | {children} 51 | 52 | ); 53 | }; 54 | 55 | export default YoutubeEmbedder; 56 | -------------------------------------------------------------------------------- /components/atomics/home/BigSlideText.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import styled from "styled-components"; 3 | import { TA_F1_BLOCK_LINE } from "../../../constants/cssProps"; 4 | import { 5 | ANGE_LIVE_BACK_COLOR, 6 | ANGE_WHITE, 7 | ANGE_RED, 8 | } from "../../../constants/colors"; 9 | import { 10 | sm_breakpoint, 11 | tablet_breakpoint, 12 | } from "../../../constants/breakpoints"; 13 | 14 | const TextMain = styled.div<{ color: Props["color"] }>` 15 | ${TA_F1_BLOCK_LINE} 16 | font-size: 1.2rem; 17 | line-height: 1.8; 18 | @media (min-width: ${sm_breakpoint}px) { 19 | font-size: 2.2rem; 20 | } 21 | 22 | @media (min-width: ${tablet_breakpoint}px) { 23 | font-size: 2.5rem; 24 | } 25 | 26 | color: ${({ color }) => 27 | color === "bgRed" 28 | ? ANGE_LIVE_BACK_COLOR 29 | : color === "angeRed" 30 | ? ANGE_RED 31 | : color === "white" 32 | ? ANGE_WHITE 33 | : null}; 34 | `; 35 | 36 | interface Props { 37 | color: "bgRed" | "angeRed" | "white"; 38 | children: ReactNode; 39 | } 40 | 41 | const BigSlideText: React.FC = (props: Props) => { 42 | return {props.children}; 43 | }; 44 | 45 | export default BigSlideText; 46 | -------------------------------------------------------------------------------- /components/atomics/home/SlideText.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import styled from "styled-components"; 3 | import { 4 | ANGE_LIVE_BACK_COLOR, 5 | ANGE_RED, 6 | ANGE_WHITE, 7 | } from "../../../constants/colors"; 8 | import { TA_F1_BLOCK_LINE } from "../../../constants/cssProps"; 9 | import { 10 | sm_breakpoint, 11 | tablet_breakpoint, 12 | } from "../../../constants/breakpoints"; 13 | 14 | interface Props { 15 | color: "bgRed" | "angeRed" | "white"; 16 | children: ReactNode; 17 | } 18 | 19 | const TextMain = styled.div` 20 | ${TA_F1_BLOCK_LINE} 21 | font-size: 0.8rem; 22 | line-height: 2; 23 | @media (min-width: ${sm_breakpoint}px) { 24 | font-size: 1rem; 25 | } 26 | 27 | @media (min-width: ${tablet_breakpoint}px) { 28 | font-size: 1.6rem; 29 | } 30 | 31 | color: ${({ color }) => 32 | color === "bgRed" 33 | ? ANGE_LIVE_BACK_COLOR 34 | : color === "angeRed" 35 | ? ANGE_RED 36 | : color === "white" 37 | ? ANGE_WHITE 38 | : null}; 39 | `; 40 | 41 | const SlideText: React.FC = (props: Props) => ( 42 | {props.children} 43 | ); 44 | 45 | export default SlideText; 46 | -------------------------------------------------------------------------------- /components/atomics/info/EmbedBox/EmbedBox.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | import EmbedBox, { EmbededBoxProps } from "./EmbedBox"; 3 | import styled from "styled-components"; 4 | import { ANGE_WHITE } from "../../../../constants/colors"; 5 | import { 6 | sm_breakpoint, 7 | tablet_breakpoint, 8 | } from "../../../../constants/breakpoints"; 9 | import YoutubeEmbedder from "../../common/YoutubeEmbedder/YoutubeEmbedder"; 10 | 11 | const Wrapper = styled.div` 12 | box-sizing: border-box; 13 | width: 100%; 14 | background-color: ${ANGE_WHITE}; 15 | padding: 24px 0; 16 | height: 100%; 17 | 18 | @media (min-width: ${tablet_breakpoint}px) { 19 | width: 1200px; 20 | } 21 | `; 22 | 23 | const SampleEmbedElement: React.VFC = () => { 24 | return ( 25 | 32 | ); 33 | }; 34 | 35 | const YouTubeWrapper = styled.div` 36 | width: 100%; 37 | display: flex; 38 | flex-direction: column; 39 | 40 | > * { 41 | margin: 4px; 42 | } 43 | 44 | @media (min-width: ${sm_breakpoint}px) { 45 | flex-direction: row; 46 | } 47 | `; 48 | 49 | export default { 50 | title: "info/EmbedBox", 51 | component: EmbedBox, 52 | args: { 53 | title: "サンプルタイトル", 54 | }, 55 | } as Meta; 56 | 57 | const Template: Story = (args) => ( 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | ); 71 | 72 | export const NormalEmbedArea = Template.bind({}); 73 | -------------------------------------------------------------------------------- /components/atomics/info/EmbedBox/EmbedBox.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { sm_breakpoint } from "../../../../constants/breakpoints"; 4 | import { 5 | ANGE_LIVE_BACK_COLOR, 6 | GREY_SHADOW_COLOR, 7 | } from "../../../../constants/colors"; 8 | import { BUNKYU_MIDASHI_GO_STD } from "../../../../constants/cssProps"; 9 | import useIntersectionObserver from "../../../../hooks/useIntersectionObserver"; 10 | import { fadein } from "../../../../styles/commonAnimation"; 11 | 12 | export interface EmbededBoxProps { 13 | children: ReactNode; 14 | title: string; 15 | } 16 | 17 | const Wrapper = styled.section<{ isStartAnimation: boolean }>` 18 | display: flex; 19 | flex-direction: column; 20 | justify-content: center; 21 | align-items: center; 22 | box-sizing: border-box; 23 | width: 100%; 24 | border: 4px solid ${ANGE_LIVE_BACK_COLOR}; 25 | border-radius: 24px; 26 | overflow-x: hidden; 27 | padding: 16px 16px 32px 16px; 28 | box-shadow: 0 10px 20px ${GREY_SHADOW_COLOR}; 29 | opacity: 0; 30 | 31 | ${({ isStartAnimation }) => 32 | isStartAnimation && 33 | css` 34 | animation: ${fadein(1)} ease-in 400ms forwards; 35 | `} 36 | 37 | @media (min-width: ${sm_breakpoint}px) { 38 | padding: 16px 16px 64px 16px; 39 | } 40 | `; 41 | 42 | const Title = styled.h1` 43 | width: 100%; 44 | font-size: 1.5rem; 45 | color: ${ANGE_LIVE_BACK_COLOR}; 46 | ${BUNKYU_MIDASHI_GO_STD} 47 | margin: 16px 0; 48 | `; 49 | 50 | const EmbedContent = styled.div` 51 | display: flex; 52 | justify-content: center; 53 | align-items: center; 54 | width: 100%; 55 | margin: 24px; 56 | `; 57 | 58 | const EmbedBox: React.VFC = ({ title, children }) => { 59 | const [ref, isStartAnimation] = useIntersectionObserver({}); 60 | 61 | return ( 62 | 63 | {title} 64 | {children} 65 | 66 | ); 67 | }; 68 | 69 | export default EmbedBox; 70 | -------------------------------------------------------------------------------- /components/atomics/info/ThumbnailLink/ThumbnailLink.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | import ThumbnailLink from "./ThumbnailLink"; 3 | import { ThumbnailLinkProps } from "./ThumbnailLink"; 4 | import MashmellowLogoImg from "../../../../public/imgs/info/mashmellow-logo.png"; 5 | import styled from "styled-components"; 6 | 7 | const MashmellowThumbnail = styled.div` 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | background-color: #ffffff; 12 | width: 100%; 13 | height: 100%; 14 | `; 15 | 16 | const MashmellowImg = styled.img` 17 | object-fit: contain; 18 | height: 60%; 19 | width: 100%; 20 | overflow: hidden; 21 | `; 22 | 23 | export default { 24 | title: "info/ThumbnailLink", 25 | component: ThumbnailLink, 26 | args: { 27 | description: "アンジュのマシュマロ", 28 | link: "https://marshmallow-qa.com/ange_katrina_", 29 | }, 30 | } as Meta; 31 | 32 | const Template: Story = (args) => ( 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | 40 | export const NormalThumbnailLink = Template.bind({}); 41 | -------------------------------------------------------------------------------- /components/atomics/info/ThumbnailLink/ThumbnailLink.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { ReactNode } from "react"; 4 | import { 5 | ANGE_RED, 6 | ANGE_WHITE, 7 | RED_SHADOW_COLOR, 8 | } from "../../../../constants/colors"; 9 | import { BUNKYU_MIDASHI_GO_STD } from "../../../../constants/cssProps"; 10 | import { fadein } from "../../../../styles/commonAnimation"; 11 | import useIntersectionObserver from "../../../../hooks/useIntersectionObserver"; 12 | 13 | export interface ThumbnailLinkProps { 14 | description: string; 15 | children: ReactNode; 16 | link: string; 17 | } 18 | 19 | const Wrapper = styled.a<{ isStartAnimation: boolean }>` 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | background-color: ${ANGE_WHITE}; 24 | color: ${ANGE_RED}; 25 | box-shadow: 0 5px 20px ${RED_SHADOW_COLOR}; 26 | border-radius: 20px; 27 | width: 240px; 28 | height: 200px; 29 | overflow: hidden; 30 | transition: transform 300ms, box-shadow 300ms; 31 | opacity: 0; 32 | 33 | ${({ isStartAnimation }) => 34 | isStartAnimation && 35 | css` 36 | animation: ${fadein(1)} 400ms ease-in forwards; 37 | `} 38 | 39 | :hover { 40 | box-shadow: 0 5px 25px ${RED_SHADOW_COLOR}; 41 | transform: scale(1.1); 42 | } 43 | `; 44 | 45 | const ThumnailArea = styled.div` 46 | display: flex; 47 | justify-content: center; 48 | align-items: center; 49 | width: 100%; 50 | height: 140px; 51 | border-radius: 20px 20px 0 0; 52 | overflow: hidden; 53 | `; 54 | 55 | const Description = styled.p` 56 | width: 100%; 57 | height: 60px; 58 | display: flex; 59 | align-items: center; 60 | justify-content: center; 61 | font-size: 1rem; 62 | ${BUNKYU_MIDASHI_GO_STD} 63 | margin: 0; 64 | `; 65 | 66 | const ThumbnailLink: React.VFC = ({ 67 | description, 68 | link, 69 | children, 70 | }) => { 71 | const [ref, isStartAnimation] = useIntersectionObserver( 72 | {} 73 | ); 74 | 75 | return ( 76 | 83 | {children} 84 | {description} 85 | 86 | ); 87 | }; 88 | 89 | export default ThumbnailLink; 90 | -------------------------------------------------------------------------------- /components/atomics/info/VTuberIcon/VTuberIcon.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | import VTuberIcon, { VTuberIconProps } from "./VTuberIcon"; 3 | import angeImg from "../../../../public/imgs/ange-basic.png"; 4 | 5 | export default { 6 | title: "info/VTuberIcon", 7 | component: VTuberIcon, 8 | args: { 9 | imgPath: angeImg, 10 | imgAlt: "アンジュ画像", 11 | }, 12 | } as Meta; 13 | 14 | const Template: Story = (args) => ; 15 | 16 | export const NormalVTuberIcon = Template.bind({}); 17 | -------------------------------------------------------------------------------- /components/atomics/info/VTuberIcon/VTuberIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { ANGE_LIVE_BACK_COLOR } from "../../../../constants/colors"; 4 | 5 | export interface VTuberIconProps { 6 | imgPath: string; 7 | imgAlt: string; 8 | } 9 | 10 | const Wrapper = styled.div` 11 | display: flex; 12 | justify-content: center; 13 | align-items: start; 14 | width: 200px; 15 | height: 200px; 16 | border: 2px ${ANGE_LIVE_BACK_COLOR} solid; 17 | border-radius: 50%; 18 | overflow: hidden; 19 | `; 20 | 21 | const VTuberImg = styled.img` 22 | width: 80%; 23 | margin-top: 10%; 24 | `; 25 | 26 | const VTuberIcon: React.VFC = ({ imgAlt, imgPath }) => { 27 | return ( 28 | 29 | 30 | 31 | ); 32 | }; 33 | 34 | export default VTuberIcon; 35 | -------------------------------------------------------------------------------- /components/molecules/common/ListRow/ListRow.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import styled from "styled-components"; 3 | import H2 from "../../../atomics/common/H2"; 4 | 5 | interface Props { 6 | mainText: string; 7 | IconSvg?: React.StatelessComponent>; 8 | HoveredSvg?: React.StatelessComponent>; 9 | bgColor: string; 10 | textColor: string; 11 | cursor?: "default" | "pointer"; 12 | onClickFC?: () => void; 13 | } 14 | 15 | const Wrapper = styled.div<{ 16 | bgColor: Props["bgColor"]; 17 | textColor: Props["textColor"]; 18 | cursor: Props["cursor"]; 19 | }>` 20 | width: 100%; 21 | height: 72px; 22 | display: flex; 23 | flex-direction: row; 24 | align-items: center; 25 | justify-content: flex-start; 26 | background-color: ${({ bgColor }) => bgColor}; 27 | color: ${({ textColor }) => textColor}; 28 | cursor: ${({ cursor }) => cursor}; 29 | :hover { 30 | background-color: ${({ textColor }) => textColor}; 31 | color: ${({ bgColor }) => bgColor}; 32 | } 33 | `; 34 | 35 | const IconWrapper = styled.div` 36 | display: flex; 37 | justify-content: center; 38 | align-items: center; 39 | height: 28px; 40 | width: 32px; 41 | margin-left: 24px; 42 | margin-right: 12px; 43 | `; 44 | 45 | const ListRow: React.FC = ({ 46 | mainText, 47 | IconSvg, 48 | HoveredSvg, 49 | bgColor, 50 | textColor, 51 | cursor = "default", 52 | onClickFC, 53 | }: Props) => { 54 | const [isHovering, changeIsHovering] = useState(false); 55 | return ( 56 | changeIsHovering(true)} 60 | onMouseLeave={() => changeIsHovering(false)} 61 | cursor={cursor} 62 | onClick={onClickFC} 63 | data-testid="list-row" 64 | > 65 | 66 | {HoveredSvg !== undefined && IconSvg !== undefined ? ( 67 | isHovering ? ( 68 | 69 | ) : ( 70 | 71 | ) 72 | ) : null} 73 | 74 |

75 |
76 | ); 77 | }; 78 | 79 | export default ListRow; 80 | -------------------------------------------------------------------------------- /components/molecules/common/ListRow/__tests__/ListRow.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ListRow from "../ListRow"; 3 | import renderer from "react-test-renderer"; 4 | import { unmountComponentAtNode, render } from "react-dom"; 5 | import { act } from "react-dom/test-utils"; 6 | 7 | describe("snapshot test", () => { 8 | test("pass minimum props", () => { 9 | const component = renderer.create( 10 | 11 | ); 12 | const tree = component.toJSON() as renderer.ReactTestRendererJSON; 13 | expect(tree).toMatchSnapshot(); 14 | }); 15 | 16 | test("pass cursor: pointer prop", () => { 17 | const component = renderer.create( 18 | 24 | ); 25 | const tree = component.toJSON() as renderer.ReactTestRendererJSON; 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | 29 | test("pass cursor: default prop", () => { 30 | const component = renderer.create( 31 | 37 | ); 38 | const tree = component.toJSON() as renderer.ReactTestRendererJSON; 39 | expect(tree).toMatchSnapshot(); 40 | }); 41 | }); 42 | 43 | describe("related event test", () => { 44 | let container: HTMLElement | null = null; 45 | beforeEach(() => { 46 | // setup a DOM element as a render target 47 | container = document.createElement("div"); 48 | // container *must* be attached to document so events work correctly. 49 | document.body.appendChild(container); 50 | }); 51 | 52 | afterEach(() => { 53 | // cleanup on exiting 54 | if (container !== null) { 55 | unmountComponentAtNode(container); 56 | container.remove(); 57 | container = null; 58 | } 59 | }); 60 | 61 | test("test: run onClickFC when clicked", () => { 62 | let isRun = false; 63 | const mockFn = jest.fn(() => (isRun = true)); 64 | act(() => { 65 | render( 66 | , 72 | container 73 | ); 74 | }); 75 | 76 | const listRow = document.querySelector("[data-testid=list-row]"); 77 | expect(isRun).toBe(false); 78 | if (listRow !== null) { 79 | act(() => { 80 | listRow.dispatchEvent(new MouseEvent("click", { bubbles: true })); 81 | }); 82 | } 83 | expect(isRun).toBe(true); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /components/molecules/common/ListRow/__tests__/__snapshots__/ListRow.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot test pass cursor: default prop 1`] = ` 4 |
11 |
14 |

18 | sample 19 |

20 |
21 | `; 22 | 23 | exports[`snapshot test pass cursor: pointer prop 1`] = ` 24 |
31 |
34 |

38 | sample 39 |

40 |
41 | `; 42 | 43 | exports[`snapshot test pass minimum props 1`] = ` 44 |
51 |
54 |

58 | sample 59 |

60 |
61 | `; 62 | -------------------------------------------------------------------------------- /components/molecules/info/VTuberIconAndName/VTuberIconAndName.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | 3 | import angeImg from "../../../../public/imgs/ange-basic.png"; 4 | import VTuberIconAndName, { VTuberIconAndNameProps } from "./VTuberIconAndName"; 5 | 6 | export default { 7 | title: "info/VTuberIconAndName", 8 | component: VTuberIconAndName, 9 | args: { 10 | imgPath: angeImg, 11 | imgAlt: "アンジュ画像", 12 | vtuberName: "アンジュ・カトリーナ", 13 | }, 14 | } as Meta; 15 | 16 | const Template: Story = (args) => ( 17 | 18 | ); 19 | 20 | export const NormalVTuberIcon = Template.bind({}); 21 | -------------------------------------------------------------------------------- /components/molecules/info/VTuberIconAndName/VTuberIconAndName.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { sm_breakpoint } from "../../../../constants/breakpoints"; 4 | import { ANGE_LIVE_BACK_COLOR } from "../../../../constants/colors"; 5 | import { BUNKYU_MIDASHI_GO_STD } from "../../../../constants/cssProps"; 6 | import VTuberIcon from "../../../atomics/info/VTuberIcon/VTuberIcon"; 7 | 8 | export interface VTuberIconAndNameProps { 9 | imgPath: string; 10 | imgAlt: string; 11 | vtuberName: string; 12 | } 13 | 14 | const Wrappper = styled.div` 15 | display: flex; 16 | flex-direction: column; 17 | align-items: center; 18 | `; 19 | 20 | const VTuberName = styled.p` 21 | font-size: 1rem; 22 | font-weight: bold; 23 | ${BUNKYU_MIDASHI_GO_STD} 24 | color: ${ANGE_LIVE_BACK_COLOR}; 25 | margin-top: 28px; 26 | 27 | @media (min-width: ${sm_breakpoint}px) { 28 | font-size: 1.2rem; 29 | } 30 | `; 31 | 32 | const VTuberIconAndName: React.VFC = ({ 33 | vtuberName, 34 | ...props 35 | }) => { 36 | return ( 37 | 38 | 39 | {vtuberName} 40 | 41 | ); 42 | }; 43 | 44 | export default VTuberIconAndName; 45 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/ExpandCircle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { tablet_breakpoint } from "../../../../constants/breakpoints"; 4 | import { ANGE_LIVE_BACK_COLOR, ANGE_WHITE } from "../../../../constants/colors"; 5 | import { scale } from "../../../../styles/commonAnimation"; 6 | 7 | interface Props { 8 | color: "red" | "white"; 9 | place: "topRight" | "bottomLeft"; 10 | runStartAnimation: boolean; 11 | runCloseAnimation: boolean; 12 | animationOrder: "first" | "second"; 13 | onAnimationEnd?: () => void; 14 | } 15 | 16 | const Circle = styled.div` 17 | position: absolute; 18 | ${({ place }) => 19 | place === "topRight" && 20 | css` 21 | top: min(-50vw, -50vh); 22 | right: min(-50vw, -50vh); 23 | `} 24 | ${({ place }) => 25 | place === "bottomLeft" && 26 | css` 27 | bottom: min(-50vw, -50vh); 28 | left: min(-50vw, -50vh); 29 | `} 30 | width: max(100vw, 100vh); 31 | height: max(100vw, 100vh); 32 | 33 | ${({ runStartAnimation, animationOrder }) => 34 | runStartAnimation && 35 | css` 36 | transform: scale(0); 37 | animation: ${scale(2.8)} ease-in forwards; 38 | animation-duration: 200ms; 39 | animation-delay: ${animationOrder === "first" ? "100ms" : "300ms"}; 40 | 41 | @media (min-width: ${tablet_breakpoint}px) { 42 | animation-duration: 250ms; 43 | } 44 | `} 45 | 46 | ${({ runCloseAnimation, animationOrder }) => 47 | runCloseAnimation && 48 | css` 49 | transform: scale(2.8); 50 | animation: ${scale(0)} ease-in forwards; 51 | animation-duration: 200ms; 52 | animation-delay: ${animationOrder === "first" ? "700ms" : "600ms"}; 53 | 54 | @media (min-width: ${tablet_breakpoint}px) { 55 | animation-duration: 250ms; 56 | } 57 | `} 58 | 59 | visibility: ${({ runStartAnimation, runCloseAnimation }) => 60 | runStartAnimation || runCloseAnimation ? "visible" : "hidden"}; 61 | 62 | ${({ color }) => 63 | color === "red" && 64 | css` 65 | background-color: ${ANGE_LIVE_BACK_COLOR}; 66 | `} 67 | ${({ color }) => 68 | color === "white" && 69 | css` 70 | background-color: ${ANGE_WHITE}; 71 | `} 72 | 73 | border-radius: 50%; 74 | `; 75 | 76 | const ExpandCircle: React.FC = (props) => { 77 | return ( 78 | 84 | ); 85 | }; 86 | 87 | export default ExpandCircle; 88 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarLeft/CenterCircle/CenterCircle.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/react/types-6-0"; 2 | import styled from "styled-components"; 3 | import { ANGE_WHITE } from "../../../../../../constants/colors"; 4 | import CenterCircle from "./CenterCircle"; 5 | 6 | const Decorator = styled.div` 7 | position: relative; 8 | top: 0; 9 | left: 0; 10 | width: 960px; 11 | height: 540px; 12 | overflow: hidden; 13 | background-color: ${ANGE_WHITE}; 14 | `; 15 | 16 | export default { 17 | title: "organism/NavBarLeftCenterCircle", 18 | component: CenterCircle, 19 | decorators: [ 20 | (Story) => ( 21 | 22 | 23 | 24 | ), 25 | ], 26 | } as Meta; 27 | 28 | export const NormalCenterCircle: React.VFC<{}> = () => ( 29 | 30 | ); 31 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarLeft/CenterCircle/OutsideRotateLine.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { ANGE_LIVE_BACK_COLOR } from "../../../../../../constants/colors"; 4 | import { 5 | fadein, 6 | fadeout, 7 | leftRotate, 8 | rightRotate, 9 | } from "../../../../../../styles/commonAnimation"; 10 | import { startAnimationBaseDelayMs } from "./animationBaseDelay"; 11 | 12 | interface Props { 13 | diameter: number; 14 | runStartAnimation: boolean; 15 | leftOrRightRotate: "left" | "right"; 16 | animationDelayMs?: number; 17 | startRotateDeg?: number; 18 | } 19 | 20 | const Wrapper = styled.div>>` 21 | position: absolute; 22 | top: calc(50% - ${({ diameter }) => diameter / 2}px); 23 | left: calc(50% - ${({ diameter }) => diameter / 2}px); 24 | width: ${({ diameter }) => diameter}px; 25 | height: ${({ diameter }) => diameter}px; 26 | ${({ 27 | animationDelayMs: animationDelay, 28 | leftOrRightRotate, 29 | runStartAnimation, 30 | }) => 31 | runStartAnimation && 32 | css` 33 | animation: ${fadein()} 400ms ease-out 34 | ${startAnimationBaseDelayMs + animationDelay}ms both, 35 | ${leftOrRightRotate === "left" ? leftRotate() : rightRotate()} 1000ms 36 | both cubic-bezier(0.33, 1, 0.68, 1) 37 | ${startAnimationBaseDelayMs + animationDelay}ms, 38 | ${fadeout} 400ms forwards ease-out 39 | ${startAnimationBaseDelayMs + animationDelay + 600}ms; 40 | `} 41 | `; 42 | 43 | const BorderLine = styled.div>>` 44 | width: 100%; 45 | height: 100%; 46 | border-radius: 50%; 47 | border: 8px solid ${ANGE_LIVE_BACK_COLOR}; 48 | clip-path: polygon(50% 50%, 15% 100%, 85% 100%); 49 | box-sizing: border-box; 50 | ${({ startRotateDeg }) => css` 51 | transform: rotate(${startRotateDeg}deg); 52 | `} 53 | `; 54 | 55 | const OutsideRotateLine: React.VFC = ({ 56 | startRotateDeg = 0, 57 | animationDelayMs: animationDelay = 0, 58 | ...props 59 | }) => { 60 | return ( 61 | 62 | 63 | 64 | ); 65 | }; 66 | 67 | export default OutsideRotateLine; 68 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarLeft/CenterCircle/animationBaseDelay.ts: -------------------------------------------------------------------------------- 1 | export const startAnimationBaseDelayMs = 600; 2 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarLeft/CloseAnimation.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { ANGE_LIVE_BACK_COLOR } from "../../../../../constants/colors"; 4 | import { translate } from "../../../../../styles/commonAnimation"; 5 | 6 | interface Props { 7 | runCloseAnimation: boolean; 8 | onHideNavBarLeft: React.AnimationEventHandler; 9 | } 10 | 11 | const animationDuration = 400; 12 | 13 | const Main = styled.div>` 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | width: 100%; 18 | height: 100%; 19 | overflow: hidden; 20 | :after { 21 | position: absolute; 22 | top: 0; 23 | right: -100%; 24 | width: 100%; 25 | height: 100%; 26 | content: ""; 27 | background-color: ${ANGE_LIVE_BACK_COLOR}; 28 | ${({ runCloseAnimation }) => 29 | runCloseAnimation && 30 | css` 31 | animation: ${translate({ x: 0, y: 0 }, { x: "-200%", y: 0 })} 32 | ${animationDuration}ms ease-in-out both; 33 | `} 34 | } 35 | `; 36 | 37 | const OnHideEventTrigger = styled.div>` 38 | position: absolute; 39 | top: 0; 40 | right: -100%; 41 | width: 100%; 42 | height: 100%; 43 | visibility: hidden; 44 | ${({ runCloseAnimation }) => 45 | runCloseAnimation && 46 | css` 47 | animation: ${translate({ x: 0, y: 0 }, { x: "-100%", y: 0 })} 48 | ${animationDuration / 2}ms ease-in-out both; 49 | `} 50 | `; 51 | 52 | const CloseAnimation: React.VFC = ({ 53 | runCloseAnimation, 54 | onHideNavBarLeft, 55 | }) => { 56 | return ( 57 |
58 | 62 |
63 | ); 64 | }; 65 | 66 | export default CloseAnimation; 67 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarLeft/NavBarLeft.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import styled from "styled-components"; 3 | import { tablet_breakpoint } from "../../../../../constants/breakpoints"; 4 | import NavBarCrossMarks from "../NavBarCrossMarks"; 5 | import NavBarRings from "../NavBarRings"; 6 | import NavBarSidewaysLines from "../NavBarSidewaysLines"; 7 | import NavBarTriangles from "../NavBarTriangles"; 8 | import CenterCircle from "./CenterCircle/CenterCircle"; 9 | import CloseAnimation from "./CloseAnimation"; 10 | import ThreeSlashes from "./ThreeSlashes/ThreeSlashes"; 11 | 12 | interface Props { 13 | runStartAnimation: boolean; 14 | runCloseAnimation: boolean; 15 | } 16 | 17 | const Wrapper = styled.div` 18 | display: none; 19 | @media (min-width: ${tablet_breakpoint}px) { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | display: block; 24 | width: calc(100% - 360px); 25 | height: 100%; 26 | } 27 | `; 28 | 29 | const useIsShowingMark = (runStartAnimation: boolean) => { 30 | const [isHide, changeIsHide] = useState(true); 31 | 32 | const toHide = () => { 33 | changeIsHide(true); 34 | }; 35 | 36 | useEffect(() => { 37 | if (runStartAnimation) { 38 | changeIsHide(false); 39 | } 40 | }, [runStartAnimation]); 41 | 42 | return [runStartAnimation || !isHide, toHide] as [boolean, () => void]; 43 | }; 44 | 45 | const NavBarLeft: React.VFC = ({ 46 | runStartAnimation, 47 | runCloseAnimation, 48 | }) => { 49 | const [isShowingMark, hideMarks] = useIsShowingMark(runStartAnimation); 50 | 51 | return ( 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 63 | 64 | ); 65 | }; 66 | 67 | export default NavBarLeft; 68 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarLeft/ThreeSlashes/ThreeSlashes.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/react/types-6-0"; 2 | import styled from "styled-components"; 3 | import { ANGE_WHITE } from "../../../../../../constants/colors"; 4 | import ThreeSlashes from "./ThreeSlashes"; 5 | 6 | const Decorator = styled.div` 7 | position: relative; 8 | top: 0; 9 | left: 0; 10 | width: 960px; 11 | height: 540px; 12 | overflow: hidden; 13 | background-color: ${ANGE_WHITE}; 14 | > div { 15 | position: absolute; 16 | top: 0; 17 | left: 50%; 18 | } 19 | `; 20 | 21 | export default { 22 | title: "organism/NavBarLeftThreeSlashs", 23 | component: ThreeSlashes, 24 | decorators: [ 25 | (Story) => ( 26 | 27 | 28 | 29 | ), 30 | ], 31 | } as Meta; 32 | 33 | export const NormalThreeSlashs: React.VFC<{}> = () => ( 34 | 35 | ); 36 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarLeft/ThreeSlashes/ThreeSlashes.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { 4 | ANGE_BLACK, 5 | ANGE_RED, 6 | ANGE_YELLOW, 7 | } from "../../../../../../constants/colors"; 8 | import { translate } from "../../../../../../styles/commonAnimation"; 9 | import { RedBlackYellow } from "../../../../../../typing/Color"; 10 | 11 | const threeSlashesBaseDelayMs = 600; 12 | 13 | interface Props { 14 | runStartAnimation: boolean; 15 | } 16 | 17 | const Line = styled.div<{ 18 | color: RedBlackYellow; 19 | delayMs: number; 20 | runStartAnimation: boolean; 21 | }>` 22 | width: 20px; 23 | height: 140%; 24 | background-color: ${({ color }) => color}; 25 | margin: 0 6px; 26 | ${({ runStartAnimation, delayMs }) => 27 | runStartAnimation && 28 | css` 29 | animation: ${translate({ x: 0, y: "-100%" }, { x: 0, y: 0 })} 300ms 30 | ease-out both ${delayMs + threeSlashesBaseDelayMs}ms; 31 | `} 32 | `; 33 | 34 | const Wrapper = styled.div` 35 | display: flex; 36 | flex-direction: row; 37 | justify-content: center; 38 | align-items: center; 39 | height: 100%; 40 | transform: rotate(24deg); 41 | ${({ runStartAnimation }) => 42 | !runStartAnimation && 43 | css` 44 | display: none; 45 | `} 46 | `; 47 | 48 | const ThreeSlashes: React.VFC = ({ runStartAnimation }) => { 49 | return ( 50 | 51 | 56 | 61 | 66 | 67 | ); 68 | }; 69 | 70 | export default ThreeSlashes; 71 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarMenu/NavBarMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { 4 | ANGE_LIVE_BACK_COLOR, 5 | ANGE_WHITE, 6 | } from "../../../../../constants/colors"; 7 | import { fadein, fadeout } from "../../../../../styles/commonAnimation"; 8 | import ListRow from "../../../../molecules/common/ListRow/ListRow"; 9 | import Link from "next/link"; 10 | 11 | interface ContentDataType { 12 | icon: React.FunctionComponent>; 13 | hoveredIcon: React.FunctionComponent>; 14 | mainText: string; 15 | link: string; 16 | } 17 | 18 | interface Props { 19 | runStartAnimation: boolean; 20 | runCloseAnimation: boolean; 21 | contentDataList: ContentDataType[]; 22 | } 23 | 24 | const baseDelay = 550; 25 | 26 | const Wrapper = styled.ul>` 27 | width: 100%; 28 | position: relative; 29 | display: flex; 30 | flex-direction: column; 31 | align-items: flex-start; 32 | justify-content: center; 33 | margin: 0; 34 | padding: 0; 35 | `; 36 | 37 | const AnimateListWrapper = styled.li< 38 | { order: number } & Pick 39 | >` 40 | width: 100%; 41 | line-height: 1; 42 | ${({ runStartAnimation, order }) => 43 | runStartAnimation && 44 | css` 45 | opacity: 0; 46 | animation: ${fadein(1)} 200ms ease-in ${order * 80 + baseDelay + 100}ms 47 | forwards; 48 | `} 49 | ${({ runCloseAnimation, order }) => 50 | runCloseAnimation && 51 | css` 52 | opacity: 1; 53 | animation: ${fadeout} 200ms ease-in ${order * 80 + 200}ms forwards; 54 | `} 55 | `; 56 | 57 | const NavBarMenu: React.FC = ({ 58 | contentDataList, 59 | runStartAnimation, 60 | runCloseAnimation, 61 | }) => { 62 | return ( 63 | 64 | {contentDataList.map(({ icon, mainText, hoveredIcon, link }, i) => { 65 | return ( 66 | 67 | 72 | 80 | 81 | 82 | ); 83 | })} 84 | 85 | ); 86 | }; 87 | 88 | export default NavBarMenu; 89 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarRings.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { 3 | ANGE_BLACK, 4 | ANGE_RED, 5 | ANGE_YELLOW, 6 | } from "../../../../constants/colors"; 7 | import Ring from "../../../atomics/common/Ring/Ring"; 8 | 9 | interface Props { 10 | runStartAnimation: boolean; 11 | } 12 | 13 | const ringBaseDelay = 400; 14 | 15 | const Wrapper = styled.div` 16 | position: absolute; 17 | right: 0; 18 | bottom: 0; 19 | width: 50%; 20 | height: 50%; 21 | max-width: 420px; 22 | 23 | > div { 24 | position: absolute; 25 | left: auto; 26 | :first-child { 27 | top: 0; 28 | left: 70%; 29 | } 30 | :nth-child(2) { 31 | top: 25%; 32 | left: 40%; 33 | } 34 | 35 | :nth-child(3) { 36 | top: 60%; 37 | left: 10%; 38 | } 39 | } 40 | `; 41 | 42 | const NavBarRings: React.VFC = ({ runStartAnimation }) => { 43 | return ( 44 | 45 | 52 | 59 | 66 | 67 | ); 68 | }; 69 | 70 | export default NavBarRings; 71 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarSidewaysLines.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from "styled-components"; 2 | import SidewaysLines from "../../../atomics/common/SidewaysLines/SidewaysLines"; 3 | 4 | const sidewayBaseDelay = 400; 5 | 6 | interface Props { 7 | runStartAnimation: boolean; 8 | } 9 | 10 | const Wrapper = styled.div<{ runStartAnimation: Props["runStartAnimation"] }>` 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | width: 50%; 15 | height: 50%; 16 | ${({ runStartAnimation }) => 17 | !runStartAnimation && 18 | css` 19 | display: none; 20 | `} 21 | 22 | > div { 23 | position: absolute; 24 | :first-child { 25 | top: 7%; 26 | left: 8%; 27 | } 28 | 29 | :nth-child(2) { 30 | top: 28%; 31 | left: 58%; 32 | } 33 | 34 | :nth-child(3) { 35 | top: 60%; 36 | left: 30%; 37 | } 38 | } 39 | `; 40 | 41 | const NavBarSidewaysLines: React.VFC = ({ runStartAnimation }) => ( 42 | 43 | 49 | 55 | 61 | 62 | ); 63 | 64 | export default NavBarSidewaysLines; 65 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarTop/NavBarTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { fadein, fadeout } from "../../../../../styles/commonAnimation"; 4 | import H2 from "../../../../atomics/common/H2"; 5 | 6 | interface Props { 7 | runStartAnimation: boolean; 8 | runCloseAnimation: boolean; 9 | text: string; 10 | color?: string; 11 | } 12 | 13 | const baseDelay = 700; 14 | 15 | const Wrapper = styled.div< 16 | Pick 17 | >` 18 | margin-left: 16px; 19 | ${({ runStartAnimation }) => 20 | runStartAnimation && 21 | css` 22 | opacity: 0; 23 | animation: ${fadein(1)} 100ms ease-in ${baseDelay}ms forwards; 24 | `} 25 | 26 | ${({ runCloseAnimation }) => 27 | runCloseAnimation && 28 | css` 29 | opacity: 1; 30 | animation: ${fadeout} 100ms ease-in 300ms forwards; 31 | `} 32 | `; 33 | 34 | const NavBarTitle: React.FC = ({ 35 | runStartAnimation, 36 | runCloseAnimation, 37 | text, 38 | color, 39 | }: Props) => { 40 | return ( 41 | 45 |

46 |
47 | ); 48 | }; 49 | export default NavBarTitle; 50 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarTop/NavBarTop.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { ANGE_LIVE_BACK_COLOR } from "../../../../../constants/colors"; 4 | import { translate } from "../../../../../styles/commonAnimation"; 5 | import CloseButton from "../../../../atomics/common/CloseButton/CloseButton"; 6 | import NavBarTitle from "./NavBarTitle"; 7 | 8 | interface Props { 9 | runStartAnimation: boolean; 10 | runCloseAnimation: boolean; 11 | onClose: () => void; 12 | } 13 | 14 | const baseDelay = 550; 15 | 16 | const TopBar = styled.div< 17 | Pick 18 | >` 19 | position: relative; 20 | display: flex; 21 | align-items: center; 22 | width: 100%; 23 | height: 70px; 24 | 25 | ::after { 26 | position: absolute; 27 | bottom: 0; 28 | left: 0; 29 | content: ""; 30 | width: 100%; 31 | height: 2px; 32 | background-color: ${ANGE_LIVE_BACK_COLOR}; 33 | animation: 150ms ease-in-out both; 34 | ${({ runStartAnimation }) => 35 | runStartAnimation && 36 | css` 37 | animation-name: ${translate({ x: "-100%", y: 0 }, { x: 0, y: 0 })}; 38 | animation-delay: ${baseDelay + 50}ms; 39 | `} 40 | ${({ runCloseAnimation }) => 41 | runCloseAnimation && 42 | css` 43 | animation-name: ${translate({ x: 0, y: 0 }, { x: "100%", y: 0 })}; 44 | animation-delay: 200ms; 45 | `} 46 | } 47 | `; 48 | 49 | const NavBarTop: React.FC = ({ 50 | onClose, 51 | runStartAnimation, 52 | runCloseAnimation, 53 | }) => { 54 | return ( 55 | 59 | 65 | 73 | 74 | ); 75 | }; 76 | 77 | export default NavBarTop; 78 | -------------------------------------------------------------------------------- /components/organisms/common/NavBar/NavBarTriangles.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from "styled-components"; 2 | import Triagnle from "../../../atomics/common/Triangle/Triangle"; 3 | 4 | interface Props { 5 | runStartAnimation: boolean; 6 | } 7 | 8 | const animationBaseDelay = 500; 9 | 10 | const Wrapper = styled.div<{ runStartAnimation: Props["runStartAnimation"] }>` 11 | position: absolute; 12 | left: 0; 13 | bottom: 0; 14 | width: 50%; 15 | height: 50%; 16 | ${({ runStartAnimation }) => 17 | !runStartAnimation && 18 | css` 19 | display: none; 20 | `} 21 | `; 22 | 23 | const NavBarTriangles: React.VFC = ({ runStartAnimation }) => { 24 | return ( 25 | 26 | 35 | 44 | 53 | 54 | ); 55 | }; 56 | 57 | export default NavBarTriangles; 58 | -------------------------------------------------------------------------------- /components/organisms/gatya/AngeCard/AngeCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { ANGE_LIVE_BACK_COLOR } from "../../../../constants/colors"; 4 | import SSRText from "./SSRText"; 5 | import WhiteBG from "./WhiteBG"; 6 | import AngeName from "./AngeName"; 7 | import AngeDescription from "./AngeDescription"; 8 | import AngeImg from "./AngeImg"; 9 | import { useTypedSelector } from "../../../../redux/store"; 10 | import { angeCardZIndex } from "../../../../constants/gatya/zindex"; 11 | import { fadein } from "../../../../styles/commonAnimation"; 12 | import { appearAngeCardOrder } from "../../../../constants/gatya/animation_order"; 13 | 14 | const Wrapper = styled.div<{ isStartAnimation: boolean }>` 15 | position: absolute; 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | width: 100%; 20 | height: 100%; 21 | z-index: ${({ isStartAnimation }) => (isStartAnimation ? angeCardZIndex : 0)}; 22 | background-color: #ffffff; 23 | opacity: 0; 24 | animation: ${({ isStartAnimation }) => (isStartAnimation ? fadein() : "none")} 25 | ${appearAngeCardOrder.duration_ms}ms linear 26 | ${appearAngeCardOrder.delay_ms}ms forwards; 27 | `; 28 | 29 | const BackGround = styled.div` 30 | position: absolute; 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | width: calc(100% - 10px); 35 | height: calc(100% - 10px); 36 | background-color: ${ANGE_LIVE_BACK_COLOR}; 37 | border-radius: 20px; 38 | `; 39 | 40 | const WhiteBorderBG = styled.div` 41 | position: absolute; 42 | width: calc(100% - 10px); 43 | height: calc(100% - 10px); 44 | background-color: ${ANGE_LIVE_BACK_COLOR}; 45 | border-radius: 14px; 46 | border: #ffffff solid 5px; 47 | `; 48 | 49 | interface Props { 50 | randomInt: number; 51 | } 52 | 53 | type AngeImgOptions = "basic" | "hey" | "light-dress"; 54 | 55 | const angeImgOptions: AngeImgOptions[] = ["basic", "hey", "light-dress"]; 56 | 57 | const AngeCard: React.FC = ({ randomInt: randomInt }: Props) => { 58 | const angeImg = angeImgOptions[randomInt]; 59 | const isStartAnimation = useTypedSelector( 60 | (state) => state.isStartSummonAnimation 61 | ); 62 | 63 | return ( 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ); 76 | }; 77 | 78 | export default AngeCard; 79 | -------------------------------------------------------------------------------- /components/organisms/gatya/AngeCard/AngeDescription.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { BUNKYU_MIDASHI_GO_STD } from "../../../../constants/cssProps"; 3 | import { ANGE_LIVE_BACK_COLOR } from "../../../../constants/colors"; 4 | import { minimalPCBreakPoint, tabletBreakPointForAngeCard } from "./constants"; 5 | 6 | const AngeDescriptionMain = styled.div` 7 | position: absolute; 8 | left: 5%; 9 | top: 45%; 10 | display: none; 11 | ${BUNKYU_MIDASHI_GO_STD} 12 | color: ${ANGE_LIVE_BACK_COLOR}; 13 | line-height: 1.8; 14 | 15 | @media (min-width: ${tabletBreakPointForAngeCard + 1}px) { 16 | display: block; 17 | font-size: 1.5rem; 18 | letter-spacing: 0.2rem; 19 | } 20 | @media (min-width: ${minimalPCBreakPoint + 1}px) { 21 | font-size: 1.8rem; 22 | letter-spacing: 0.2rem; 23 | } ; 24 | `; 25 | 26 | const AngeDescription = () => { 27 | return ( 28 | 29 | ボロボロの小屋で時間を忘れて錬金術 30 |
31 | の研究に明け暮れている。大人っぽい 32 |
33 | 女性的な体に憧れており、実は 34 |
35 | その研究をしているとか 36 |
37 | していないとか。 38 |
39 | ); 40 | }; 41 | 42 | export default AngeDescription; 43 | -------------------------------------------------------------------------------- /components/organisms/gatya/AngeCard/AngeImg.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import AngeBasicImg from "../../../../public/imgs/ange-basic.png"; 4 | import AngeHeyImg from "../../../../public/imgs/gatya/ange-hey.png"; 5 | import AngeLightDressImg from "../../../../public/imgs/gatya/ange-light-dress.png"; 6 | import { tabletBreakPointForAngeCard } from "./constants"; 7 | 8 | const Wrapper = styled.div` 9 | position: absolute; 10 | height: 100%; 11 | overflow: hidden; 12 | display: flex; 13 | justify-content: center; 14 | align-items: flex-start; 15 | @media (max-width: ${tabletBreakPointForAngeCard}px) { 16 | width: 100%; 17 | } 18 | @media (min-width: ${tabletBreakPointForAngeCard + 1}px) { 19 | top: 0; 20 | right: 0; 21 | width: 55%; 22 | } 23 | `; 24 | 25 | interface ImgProps { 26 | src: string; 27 | alt: string; 28 | smTabletHeight: string; 29 | pcMarginTop: string; 30 | } 31 | 32 | const imgProps = { 33 | basic: { 34 | src: AngeBasicImg, 35 | alt: "アンジュ通常画像", 36 | smTabletHeight: "95%", 37 | pcMarginTop: "50px", 38 | }, 39 | hey: { 40 | src: AngeHeyImg, 41 | alt: "アンジュ斜めポージング画像", 42 | smTabletHeight: "95%", 43 | pcMarginTop: "0", 44 | }, 45 | "light-dress": { 46 | src: AngeLightDressImg, 47 | alt: "アンジュ薄いドレス画像", 48 | smTabletHeight: "95%", 49 | pcMarginTop: "70px", 50 | }, 51 | }; 52 | 53 | const createCustomImgs = (angeImgDatas: typeof imgProps) => { 54 | const createCustomImg = ({ 55 | src, 56 | alt, 57 | smTabletHeight, 58 | pcMarginTop, 59 | }: ImgProps) => { 60 | return styled.img.attrs(() => ({ 61 | src, 62 | alt, 63 | }))` 64 | height: ${smTabletHeight}; 65 | max-width: none; /*NOTE global.scssでmax-width: 100%って定義してあるから上書き */ 66 | @media (min-width: ${tabletBreakPointForAngeCard}px) { 67 | height: auto; 68 | width: 100%; 69 | margin-top: ${pcMarginTop}; 70 | } 71 | `; 72 | }; 73 | 74 | const customImgs: { [s: string]: ReturnType } = {}; 75 | for (const key in angeImgDatas) { 76 | customImgs[key] = createCustomImg( 77 | angeImgDatas[key as keyof typeof angeImgDatas] as ImgProps 78 | ); 79 | } 80 | return customImgs; 81 | }; 82 | 83 | const customImgs = createCustomImgs(imgProps); 84 | 85 | interface Props { 86 | imgType: keyof typeof imgProps; 87 | } 88 | 89 | const AngeImg: React.FC = ({ imgType }: Props) => { 90 | const CustomImg = customImgs[imgType]; 91 | return ( 92 | 93 | 94 | 95 | ); 96 | }; 97 | 98 | export default AngeImg; 99 | -------------------------------------------------------------------------------- /components/organisms/gatya/AngeCard/AngeName.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { BUNKYU_MIDASHI_GO_STD } from "../../../../constants/cssProps"; 4 | import { ANGE_LIVE_BACK_COLOR } from "../../../../constants/colors"; 5 | import { sm_breakpoint } from "../../../../constants/breakpoints"; 6 | import { tabletBreakPointForAngeCard, minimalPCBreakPoint } from "./constants"; 7 | 8 | const Wrapper = styled.div` 9 | ${BUNKYU_MIDASHI_GO_STD} 10 | color: ${ANGE_LIVE_BACK_COLOR}; 11 | position: absolute; 12 | @media (max-width: ${sm_breakpoint}px) { 13 | left: 10px; 14 | bottom: 16px; 15 | } 16 | 17 | @media (min-width: ${sm_breakpoint + 1}px) { 18 | left: 30px; 19 | bottom: 30px; 20 | } 21 | 22 | @media (min-width: ${tabletBreakPointForAngeCard + 1}px) { 23 | top: 20%; 24 | left: 5%; 25 | } 26 | `; 27 | 28 | const AngeTitle = styled.div` 29 | @media (max-width: ${sm_breakpoint}px) { 30 | font-size: 1rem; 31 | border-bottom: solid 1px ${ANGE_LIVE_BACK_COLOR}; 32 | } 33 | 34 | @media (min-width: ${sm_breakpoint + 1}px) { 35 | font-size: 1.8rem; 36 | border-bottom: solid 2px ${ANGE_LIVE_BACK_COLOR}; 37 | } 38 | 39 | @media (min-width: ${tabletBreakPointForAngeCard + 1}px) { 40 | font-size: 1.8rem; 41 | border-bottom: solid 2px ${ANGE_LIVE_BACK_COLOR}; 42 | } 43 | 44 | @media (min-width: ${minimalPCBreakPoint + 1}px) { 45 | font-size: 2rem; 46 | border-bottom: solid 2px ${ANGE_LIVE_BACK_COLOR}; 47 | } 48 | `; 49 | 50 | const AngeNameMain = styled.div` 51 | @media (max-width: ${sm_breakpoint}px) { 52 | font-size: 1.5rem; 53 | } 54 | 55 | @media (min-width: ${sm_breakpoint + 1}px) { 56 | font-size: 3rem; 57 | } 58 | 59 | @media (min-width: ${tabletBreakPointForAngeCard + 1}px) { 60 | font-size: 3rem; 61 | } 62 | 63 | @media (min-width: ${minimalPCBreakPoint + 1}px) { 64 | font-size: 3.5rem; 65 | } 66 | `; 67 | 68 | const AngeName: React.FC = () => { 69 | return ( 70 | 71 | 公式美少女錬金術師ライバー? 72 | アンジュ・カトリーナ 73 | 74 | ); 75 | }; 76 | 77 | export default AngeName; 78 | -------------------------------------------------------------------------------- /components/organisms/gatya/AngeCard/SSRText.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { ANGE_LIVE_BACK_COLOR } from "../../../../constants/colors"; 4 | import { BUNKYU_MIDASHI_GO_STD } from "../../../../constants/cssProps"; 5 | import { 6 | sm_breakpoint, 7 | tablet_breakpoint, 8 | } from "../../../../constants/breakpoints"; 9 | 10 | const Wrapper = styled.div` 11 | position: absolute; 12 | top: 0; 13 | right: 0; 14 | `; 15 | 16 | const TextBG = styled.div` 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | background-color: #ffffff; 21 | color: ${ANGE_LIVE_BACK_COLOR}; 22 | border-radius: 0 0px 0 14px; 23 | border: #ffffff solid 5px; 24 | ${BUNKYU_MIDASHI_GO_STD}; 25 | @media (max-width: ${sm_breakpoint}px) { 26 | width: 80px; 27 | height: 40px; 28 | font-size: 1.5rem; 29 | } 30 | 31 | @media (min-width: ${sm_breakpoint + 1}px) { 32 | width: 110px; 33 | height: 50px; 34 | font-size: 1.7rem; 35 | } 36 | 37 | @media (min-width: ${tablet_breakpoint + 1}px) { 38 | width: 140px; 39 | height: 60px; 40 | font-size: 2.5rem; 41 | } 42 | `; 43 | 44 | const SSRText: React.FC = () => { 45 | return ( 46 | 47 | 48 | SSR 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default SSRText; 55 | -------------------------------------------------------------------------------- /components/organisms/gatya/AngeCard/WhiteBG.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { sm_breakpoint } from "../../../../constants/breakpoints"; 3 | import { tabletBreakPointForAngeCard } from "./constants"; 4 | 5 | const WhiteBG = styled.div` 6 | position: absolute; 7 | width: 100%; 8 | left: 0; 9 | bottom: 0; 10 | background-color: #ffffff; 11 | @media (max-width: ${sm_breakpoint}px) { 12 | height: 300px; 13 | clip-path: polygon(0 0, 100% 100%, 100% 100%, 0 100%); 14 | } 15 | 16 | @media (min-width: ${sm_breakpoint + 1}px) { 17 | height: 450px; 18 | clip-path: polygon(0 0, 100% 100%, 100% 100%, 0 100%); 19 | } 20 | 21 | @media (min-width: ${tabletBreakPointForAngeCard + 1}px) { 22 | height: 100%; 23 | clip-path: polygon(0 0, 50% 0, 35% 100%, 0 100%); 24 | } 25 | `; 26 | 27 | export default WhiteBG; 28 | -------------------------------------------------------------------------------- /components/organisms/gatya/AngeCard/constants.ts: -------------------------------------------------------------------------------- 1 | export const tabletBreakPointForAngeCard = 1260; 2 | export const minimalPCBreakPoint = 1540; 3 | -------------------------------------------------------------------------------- /components/organisms/gatya/BlackTransition/BlackCircle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { ANGE_BLACK } from "../../../../constants/colors"; 4 | import { scale } from "../../../../styles/commonAnimation"; 5 | import { SizeType } from "../../../../typing/SizeType"; 6 | import { blackCircleExpandOrder } from "../../../../constants/gatya/animation_order"; 7 | import sizeTypeJudge from "../../../../systems/sizeTypeJudge"; 8 | 9 | interface Props { 10 | isStartSummonAnimation: boolean; 11 | size: SizeType; 12 | } 13 | 14 | const Wrapper = styled.div` 15 | width: 100%; 16 | height: 100%; 17 | position: absolute; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | `; 22 | 23 | const circleDiameter = 100; 24 | 25 | const BlackCircleMain = styled.div<{ 26 | isStartSummonAnimation: boolean; 27 | size: SizeType; 28 | }>` 29 | position: absolute; 30 | width: ${circleDiameter}px; 31 | height: ${circleDiameter}px; 32 | border-radius: 50%; 33 | background-color: ${ANGE_BLACK}; 34 | transform: scale(0); 35 | animation: ${({ isStartSummonAnimation, size }) => 36 | isStartSummonAnimation ? scale(sizeTypeJudge(size)(15, 20, 30)) : "none"} 37 | ${blackCircleExpandOrder.duration_ms}ms ease-out 38 | ${blackCircleExpandOrder.delay_ms}ms forwards; 39 | `; 40 | 41 | const BlackCircle: React.FC = ({ 42 | isStartSummonAnimation, 43 | size, 44 | }: Props) => { 45 | return ( 46 | 47 | 51 | 52 | ); 53 | }; 54 | 55 | export default BlackCircle; 56 | -------------------------------------------------------------------------------- /components/organisms/gatya/BlackTransition/BlackTransition.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { blackTransitionZIndex } from "../../../../constants/gatya/zindex"; 4 | import BlackCircle from "./BlackCircle"; 5 | import { useTypedSelector } from "../../../../redux/store"; 6 | 7 | const Wrapper = styled.div<{ isStartSummonAnimation: boolean }>` 8 | position: absolute; 9 | overflow: hidden; 10 | width: 100%; 11 | height: 100%; 12 | z-index: ${({ isStartSummonAnimation }) => 13 | isStartSummonAnimation ? blackTransitionZIndex : 1}; 14 | `; 15 | 16 | const BlackTransition: React.FC = () => { 17 | const [size, isStartSummonAnimation] = useTypedSelector((state) => [ 18 | state.sizes, 19 | state.isStartSummonAnimation, 20 | ]); 21 | return ( 22 | 23 | 27 | 28 | ); 29 | }; 30 | 31 | export default BlackTransition; 32 | -------------------------------------------------------------------------------- /components/organisms/gatya/Flash/CircleFlash.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { keyframes } from "styled-components"; 3 | import WhiteCircleSVG from "../../../../public/svgs/gatya/white_circle.svg"; 4 | import { fadein } from "../../../../styles/commonAnimation"; 5 | import { circleFlashOrder } from "../../../../constants/gatya/animation_order"; 6 | 7 | interface Props { 8 | isStartAnimation: boolean; 9 | scale: number; 10 | blur: string; 11 | } 12 | 13 | const circleFlashAnimation = (scale: Props["scale"]) => keyframes` 14 | from { 15 | transform: translate(calc(100px / 2), 0) scale(0.0001) rotate(0deg); 16 | } 17 | 18 | to { 19 | transform: translate(calc(100px / 2), 0) scale(${scale}) rotate(0deg); 20 | } 21 | `; 22 | 23 | const WhiteCircle: React.FC = ({ 24 | isStartAnimation, 25 | blur, 26 | scale, 27 | ...props 28 | }: Props) => { 29 | return ; 30 | }; 31 | 32 | const CircleFlash = styled(WhiteCircle)` 33 | position: absolute; 34 | width: 100px; 35 | height: 100px; 36 | 37 | filter: blur(${({ blur }) => blur}); 38 | opacity: 0; 39 | animation: ${({ isStartAnimation }) => (isStartAnimation ? fadein() : "none")} 40 | 0ms ease-out ${circleFlashOrder.delay_ms}ms forwards, 41 | ${({ isStartAnimation, scale }) => 42 | isStartAnimation ? circleFlashAnimation(scale) : "none"} 43 | ${circleFlashOrder.duration_ms}ms ease-out ${circleFlashOrder.delay_ms}ms 44 | both; 45 | `; 46 | 47 | export default CircleFlash; 48 | -------------------------------------------------------------------------------- /components/organisms/gatya/Flash/Flash.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { flashZIndex } from "../../../../constants/gatya/zindex"; 4 | import { useTypedSelector } from "../../../../redux/store"; 5 | import LineFlash from "./LineFlash"; 6 | import CircleFlash from "./CircleFlash"; 7 | import HideScreenCircleFlash from "./HideScreenCircleFlash"; 8 | import { 9 | tablet_breakpoint, 10 | sm_breakpoint, 11 | } from "../../../../constants/breakpoints"; 12 | 13 | const Wrapper = styled.div<{ isStartAnimation: boolean }>` 14 | position: absolute; 15 | z-index: ${({ isStartAnimation }) => (isStartAnimation ? flashZIndex : "0")}; 16 | width: 100%; 17 | height: 100%; 18 | display: flex; 19 | justify-content: flex-end; 20 | align-items: center; 21 | overflow: hidden; 22 | `; 23 | 24 | const Flash: React.FC = () => { 25 | const [ 26 | size, 27 | isStartAnimation, 28 | ] = useTypedSelector(({ isStartSummonAnimation, sizes }) => [ 29 | sizes, 30 | isStartSummonAnimation, 31 | ]); 32 | 33 | let deepCircleFlashScaleBase; 34 | if (size === "sm") { 35 | deepCircleFlashScaleBase = (sm_breakpoint / 100) * 1.5; 36 | } else if (size === "tablet") { 37 | deepCircleFlashScaleBase = (tablet_breakpoint / 100) * 1.5; 38 | } else { 39 | // NOTE 画面サイズ1920までの対応なので 1920px / 100px * 1.5(大体ルート2) = 大体29 40 | deepCircleFlashScaleBase = 29; 41 | } 42 | 43 | return ( 44 | 45 | 46 | 53 | 60 | 61 | 62 | 63 | 68 | 69 | ); 70 | }; 71 | 72 | export default Flash; 73 | -------------------------------------------------------------------------------- /components/organisms/gatya/Flash/HideScreenCircleFlash.tsx: -------------------------------------------------------------------------------- 1 | import WhiteCircle from "./WhiteCircle"; 2 | import styled, { keyframes } from "styled-components"; 3 | import { fadein, fadeout } from "../../../../styles/commonAnimation"; 4 | import { 5 | circleFlashOrder, 6 | hideScreenCircleFlashOrder, 7 | disappearHideScreenCircleFlashOrder, 8 | } from "../../../../constants/gatya/animation_order"; 9 | 10 | interface Props { 11 | isStartAnimation: boolean; 12 | scale: number; 13 | blur: string; 14 | } 15 | 16 | const hideScreenCircleAnimation = ( 17 | scale: Props["scale"], 18 | blur: Props["blur"] 19 | ) => keyframes` 20 | 0% { 21 | transform: translate(calc(100px / 2), 0) scale(0.0001) rotate(0deg); 22 | filter: blur(${blur}); 23 | } 24 | 25 | // NOTE 70%でblurを0pxにしてるのは、そうしないとなぜか画面端でblurが変な風にピカピカ光るから 26 | 70% { 27 | transform: translate(calc(100px / 2), 0) scale(${scale * 0.7}) rotate(0deg); 28 | filter: blur(0px); 29 | } 30 | 31 | to { 32 | transform: translate(calc(100px / 2), 0) scale(${scale}) rotate(0deg); 33 | filter: blur(0px); 34 | } 35 | `; 36 | 37 | const HideScreenCircleFlash = styled(WhiteCircle)` 38 | position: absolute; 39 | width: 100px; 40 | height: 100px; 41 | opacity: 0; 42 | animation: ${({ isStartAnimation }) => (isStartAnimation ? fadein() : "none")} 43 | 0ms ease-out ${circleFlashOrder.delay_ms}ms forwards, 44 | ${({ isStartAnimation, blur }) => 45 | isStartAnimation 46 | ? hideScreenCircleAnimation( 47 | (Math.max(window.innerWidth, window.innerHeight) / 100) * 3.0, 48 | blur 49 | ) 50 | : "none"} 51 | ${hideScreenCircleFlashOrder.duration_ms}ms ease-out 52 | ${hideScreenCircleFlashOrder.delay_ms}ms both, 53 | ${({ isStartAnimation }) => (isStartAnimation ? fadeout : "none")} 0ms 54 | linear ${disappearHideScreenCircleFlashOrder.delay_ms}ms forwards; 55 | `; 56 | 57 | export default HideScreenCircleFlash; 58 | -------------------------------------------------------------------------------- /components/organisms/gatya/Flash/LineFlash.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { keyframes } from "styled-components"; 3 | import FlashLineSVG from "../../../../public/svgs/gatya/flash_line.svg"; 4 | import { lineFlashOrder } from "../../../../constants/gatya/animation_order"; 5 | import { fadein } from "../../../../styles/commonAnimation"; 6 | 7 | interface Props { 8 | isStartAnimation: boolean; 9 | scale: number; 10 | blur: string; 11 | rotate?: string; 12 | top?: string; 13 | } 14 | 15 | const lineFlashAnimation = ({ 16 | top = "0", 17 | scale, 18 | rotate = "0deg", 19 | }: Pick) => keyframes` 20 | from { 21 | transform: translate(calc(100px / 2), ${top}) scale(0.0001) rotate(${rotate}); 22 | } 23 | 24 | to { 25 | transform: translate(calc(100px / 2), ${top}) scale(${scale}) rotate(${rotate}); 26 | } 27 | `; 28 | 29 | const FlashLine: React.FC = ({ 30 | isStartAnimation, 31 | blur, 32 | rotate, 33 | top, 34 | scale, 35 | ...props 36 | }: Props) => { 37 | return ; 38 | }; 39 | 40 | const LineFlash = styled(FlashLine)` 41 | position: absolute; 42 | width: 100px; 43 | height: 100px; 44 | transform: translate( 45 | calc(100px / 2), 46 | ${({ top }) => (top !== undefined ? top : "0")} 47 | ) 48 | scale(${({ scale }) => scale}) 49 | rotate(${({ rotate }) => (rotate !== undefined ? rotate : "0deg")}); 50 | filter: blur(${({ blur }) => blur}); 51 | opacity: 0; 52 | animation: ${({ isStartAnimation }) => (isStartAnimation ? fadein() : "none")} 53 | 0ms ease-out ${lineFlashOrder.delay_ms}ms forwards, 54 | ${({ isStartAnimation, scale, rotate, top }) => 55 | isStartAnimation ? lineFlashAnimation({ scale, rotate, top }) : "none"} 56 | ${lineFlashOrder.duration_ms}ms ease-out ${lineFlashOrder.delay_ms}ms both; 57 | `; 58 | 59 | export default LineFlash; 60 | -------------------------------------------------------------------------------- /components/organisms/gatya/Flash/WhiteCircle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import WhiteCircleSVG from "../../../../public/svgs/gatya/white_circle.svg"; 3 | 4 | // NOTE HideScreenCircleFlashとCircleFlashのどちらでも使うためatomicでコンポーネント化しました 5 | interface Props { 6 | isStartAnimation: boolean; 7 | scale: number; 8 | blur: string; 9 | } 10 | 11 | const WhiteCircle: React.FC = ({ 12 | isStartAnimation, 13 | blur, 14 | scale, 15 | ...props 16 | }: Props) => { 17 | return ; 18 | }; 19 | 20 | export default WhiteCircle; 21 | -------------------------------------------------------------------------------- /components/organisms/gatya/OmataseMattaText.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { SizeType } from "../../../typing/SizeType"; 4 | import { useTypedSelector } from "../../../redux/store"; 5 | import { omataseMattaTextZIndex } from "../../../constants/gatya/zindex"; 6 | import sizeTypeJudge from "../../../systems/sizeTypeJudge"; 7 | import { omataseMattaFadeinOrder } from "../../../constants/gatya/animation_order"; 8 | import { fadein } from "../../../styles/commonAnimation"; 9 | import { 10 | smFontSize, 11 | tabletFontSize, 12 | pcFontSize, 13 | omataseMattaLineHeight, 14 | omataseMattaContent, 15 | } from "../../../constants/gatya/omataseMattaSetting"; 16 | 17 | const Wrapper = styled.div<{ size: SizeType; isStartSummonAnimation: boolean }>` 18 | position: absolute; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | width: 100%; 24 | height: 100%; 25 | z-index: ${({ isStartSummonAnimation }) => 26 | isStartSummonAnimation ? omataseMattaTextZIndex : 1}; 27 | `; 28 | 29 | const CharWrapper = styled.span<{ 30 | size: SizeType; 31 | isStartSummonAnimation: boolean; 32 | order: number; 33 | }>` 34 | display: flex; 35 | align-items: center; 36 | justify-content: center; 37 | font-family: kan412typos-std, sans-serif; 38 | font-style: normal; 39 | font-weight: 400; 40 | font-size: ${({ size }) => 41 | sizeTypeJudge(size)(smFontSize, tabletFontSize, pcFontSize)}; 42 | color: #ffffff; 43 | opacity: 0; 44 | line-height: ${omataseMattaLineHeight}; 45 | animation: ${({ isStartSummonAnimation }) => 46 | isStartSummonAnimation ? fadein() : "none"} 47 | ${omataseMattaFadeinOrder.duration_ms}ms ease-out 48 | ${({ order }) => 49 | order * omataseMattaFadeinOrder.duration_ms + 50 | omataseMattaFadeinOrder.delay_ms}ms 51 | forwards; 52 | `; 53 | 54 | const OmataseMattaText = () => { 55 | const [size, isStartSummonAnimation] = useTypedSelector((state) => [ 56 | state.sizes, 57 | state.isStartSummonAnimation, 58 | ]); 59 | 60 | return ( 61 | 62 | {omataseMattaContent.split("").map((value, index) => ( 63 | 69 | {value} 70 | 71 | ))} 72 | 73 | ); 74 | }; 75 | 76 | export default OmataseMattaText; 77 | -------------------------------------------------------------------------------- /components/organisms/gatya/SmallMagicCircleMap/SmallMagicCircle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import SingleMagicCircle from "../../../atomics/gatya/SingleMagicCircle"; 4 | 5 | export interface SmallMagicCircleProps { 6 | top: number; 7 | left: number; 8 | diameter: number; 9 | circleNum: 1 | 2 | 3; 10 | isStartSummonAnimation: boolean; 11 | } 12 | 13 | const Wrapper = styled.div<{ left: number; top: number; diameter: number }>` 14 | position: absolute; 15 | top: 50%; 16 | left: 50%; 17 | ${({ top, left }) => css` 18 | transform: translate(calc(-50% + ${left}px), calc(-50% + ${top}px)); 19 | `} 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | ${({ diameter }) => css` 24 | width: ${diameter}px; 25 | height: ${diameter}px; 26 | `} 27 | `; 28 | 29 | const SmallMagicCircle: React.FC = ({ 30 | top, 31 | left, 32 | diameter, 33 | circleNum, 34 | isStartSummonAnimation, 35 | }: SmallMagicCircleProps) => { 36 | const mostInDiameter = diameter * 0.58; 37 | const secondInDiameter = diameter * 0.75; 38 | 39 | const doAnimations = { 40 | doShadow: false, 41 | doExpand: false, 42 | doFadeout: true, 43 | }; 44 | 45 | return ( 46 | 47 | {circleNum >= 3 ? ( 48 | 55 | ) : null} 56 | {circleNum >= 2 ? ( 57 | 64 | ) : null} 65 | 72 | 73 | ); 74 | }; 75 | 76 | export default SmallMagicCircle; 77 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/CircleMain.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { 4 | sm_breakpoint, 5 | tablet_breakpoint, 6 | } from "../../../../constants/breakpoints"; 7 | import { ANGE_LIVE_BACK_COLOR, ANGE_WHITE } from "../../../../constants/colors"; 8 | 9 | const smDiameter = "90vw"; 10 | const tabletDiameter = "70vw"; 11 | const pcDiameter = "70vh"; 12 | 13 | const redBorderWidth = 4; 14 | 15 | const Outline = styled.div` 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | border-radius: 50%; 20 | background-color: ${ANGE_WHITE}; 21 | overflow: hidden; 22 | width: calc(${smDiameter} + ${redBorderWidth + 2}px); 23 | height: calc(${smDiameter} + ${redBorderWidth + 2}px); 24 | @media (min-width: ${sm_breakpoint}px) { 25 | width: calc(${tabletDiameter} + ${redBorderWidth + 2}px); 26 | height: calc(${tabletDiameter} + ${redBorderWidth + 2}px); 27 | } 28 | 29 | @media (min-width: ${tablet_breakpoint}px) { 30 | width: calc(${pcDiameter} + ${redBorderWidth + 2}px); 31 | height: calc(${pcDiameter} + ${redBorderWidth + 2}px); 32 | } 33 | `; 34 | 35 | const StyledCircleMain = styled.div` 36 | position: absolute; 37 | top: auto; 38 | left: auto; 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | border-radius: 50%; 43 | background-color: ${ANGE_WHITE}; 44 | border: solid ${redBorderWidth}px ${ANGE_LIVE_BACK_COLOR}; 45 | width: ${smDiameter}; 46 | height: ${smDiameter}; 47 | overflow: hidden; 48 | z-index: 1; 49 | 50 | @media (min-width: ${sm_breakpoint}px) { 51 | width: ${tabletDiameter}; 52 | height: ${tabletDiameter}; 53 | } 54 | 55 | @media (min-width: ${tablet_breakpoint}px) { 56 | width: ${pcDiameter}; 57 | height: ${pcDiameter}; 58 | } 59 | `; 60 | 61 | const CircleMain: React.FC = (props) => { 62 | return ( 63 | 64 | {props.children} 65 | 66 | ); 67 | }; 68 | 69 | export default CircleMain; 70 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/CircleSlide.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { 4 | sm_breakpoint, 5 | tablet_breakpoint, 6 | } from "../../../../constants/breakpoints"; 7 | import CircleMain from "./CircleMain"; 8 | import meritAndDemeritData from "./SlideController/SlideContent/contentDatas/meritAndDemeritData"; 9 | import SlideController from "./SlideController/SlideController"; 10 | import sanbakaData from "./SlideController/SlideContent/contentDatas/sanbakaData"; 11 | import streamingStyleData from "./SlideController/SlideContent/contentDatas/streamingStyle"; 12 | 13 | const Wrapper = styled.div` 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | width: 100%; 21 | height: 90%; 22 | @media (min-width: ${sm_breakpoint}px) { 23 | height: 85%; 24 | } 25 | @media (min-width: ${tablet_breakpoint}px) { 26 | top: auto; 27 | bottom: 0; 28 | left: 50%; 29 | width: 50%; 30 | height: 90%; 31 | } 32 | `; 33 | 34 | const CircleSlide: React.FC = () => { 35 | return ( 36 | 37 | 38 | 45 | 46 | 47 | ); 48 | }; 49 | 50 | export default CircleSlide; 51 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/SlideController/SlideContent/NormalSlideContent/NormalOneSlideContent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const OneSlideWrapper = styled.div` 5 | width: 100%; 6 | height: 100%; 7 | min-width: 100%; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | `; 12 | 13 | const NormalOneSlideContent: React.FC = (props) => { 14 | return {props.children}; 15 | }; 16 | 17 | export default NormalOneSlideContent; 18 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/SlideController/SlideContent/NormalSlideContent/NormalSlideContent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import slideAnimation from "../slideAnimation"; 4 | import NormalOneSlideContent from "./NormalOneSlideContent"; 5 | import contentDataType from "../contentDatas/contentDataType"; 6 | import AnimationProps from "../../../../../../../typing/AnimationProps"; 7 | 8 | interface Props { 9 | slidePages: contentDataType["slidePages"]; 10 | animationTimeProps: AnimationProps; 11 | onSlideEndFC?: (event: React.AnimationEvent) => void; 12 | } 13 | 14 | const SlideWrapper = styled.div<{ 15 | slideNum: number; 16 | animationTimeProps: AnimationProps; 17 | }>` 18 | display: flex; 19 | flex-direction: row; 20 | height: 100%; 21 | width: 100%; 22 | animation: ${({ slideNum }) => slideAnimation(slideNum)} 23 | ${({ animationTimeProps }) => animationTimeProps.duration_ms}ms ease-out 24 | forwards ${({ animationTimeProps }) => animationTimeProps.delay_ms}ms; 25 | `; 26 | 27 | const NormalSlideContent: React.FC = ({ 28 | slidePages, 29 | animationTimeProps, 30 | onSlideEndFC, 31 | }: Props) => { 32 | return ( 33 | 41 | {slidePages.map(({ node, key }) => ( 42 | {node} 43 | ))} 44 | 45 | ); 46 | }; 47 | 48 | export default NormalSlideContent; 49 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/SlideController/SlideContent/SlideContent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import NormalSlideContent from "./NormalSlideContent/NormalSlideContent"; 3 | import SplitedSlideContent from "./SplitedSlideContent/SplitedSlideContent"; 4 | import contentDataType from "./contentDatas/contentDataType"; 5 | import AnimationProps from "../../../../../../typing/AnimationProps"; 6 | 7 | type Props = Omit & { 8 | onSlideEndFC: (event: React.AnimationEvent) => void; 9 | animationTimeProps: AnimationProps; 10 | }; 11 | 12 | const SlideContent: React.FC = ({ 13 | animationType, 14 | slidePages, 15 | onSlideEndFC, 16 | animationTimeProps, 17 | }: Props) => { 18 | if (animationType === "slide") { 19 | return ( 20 | 25 | ); 26 | } else { 27 | // NOTE SplitedSlideは作ったはいいけど使い道があまりなかった。これできれいに遷移するコンテンツがみつからない 28 | return ( 29 | 34 | ); 35 | } 36 | }; 37 | 38 | export default SlideContent; 39 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/SlideController/SlideContent/contentDatas/contentDataType.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | interface SlidePageType { 4 | node: ReactNode; 5 | key: string | number; 6 | } 7 | 8 | interface contentDataType { 9 | animationType: "slide" | "splitedSlide"; 10 | slidePages: SlidePageType[]; 11 | animationDuration_ms: number; 12 | } 13 | 14 | export default contentDataType; 15 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/SlideController/SlideContent/contentDatas/meritAndDemeritData.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import BigSlideText from "../../../../../../atomics/home/BigSlideText"; 4 | import SlideText from "../../../../../../atomics/home/SlideText"; 5 | import contentDataType from "./contentDataType"; 6 | 7 | const Wrapper = styled.div` 8 | width: 90%; 9 | text-align: center; 10 | `; 11 | 12 | const FirstPage: React.FC = () => ( 13 | 14 | アンジュ・カトリーナ 15 | を推すメリットとデメリット 16 | 17 | ); 18 | 19 | const SecondPage: React.FC = () => ( 20 | 21 | メリット 22 | とにかく可愛い 23 | 配信が楽しい 24 | 毎日の生きがいができる 25 | つらい時思い出して元気になれる 26 | とにかくかわいい 27 | 28 | ); 29 | 30 | const ThirdPage: React.FC = () => ( 31 | 32 | デメリット 33 | 骨抜きになってしまう 34 | 配信のない夜がとてもさみしい 35 | コイビトがいる人はもめる原因に 36 | とにかく可愛すぎる 37 | 38 | ); 39 | 40 | const FourthPage: React.FC = () => ( 41 | 42 | 用法用量を守って楽しんでね! 43 | 44 | ); 45 | 46 | const meritAndDemeritData: contentDataType = { 47 | animationType: "slide", 48 | slidePages: [ 49 | { 50 | node: , 51 | key: "ange-merit-demerit-title", 52 | }, 53 | { 54 | node: , 55 | key: "ange-merit-demerit-merit", 56 | }, 57 | { 58 | node: , 59 | key: "ange-merit-demerit-demerit", 60 | }, 61 | { 62 | node: , 63 | key: "ange-merit-demerit-last", 64 | }, 65 | ], 66 | animationDuration_ms: 7000, 67 | }; 68 | 69 | export default meritAndDemeritData; 70 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/SlideController/SlideContent/contentDatas/streamingStyle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import BigSlideText from "../../../../../../atomics/home/BigSlideText"; 4 | import contentDataType from "./contentDataType"; 5 | import SlideText from "../../../../../../atomics/home/SlideText"; 6 | 7 | const Wrapper = styled.div` 8 | width: 90%; 9 | text-align: center; 10 | `; 11 | 12 | const TitlePage: React.FC = () => ( 13 | 14 | アンジュの配信スタイル 15 | 16 | ); 17 | 18 | const ActiveTimePage: React.FC = () => ( 19 | 20 | 21 | 基本的に生活リズムが逆転していることもあって、夜の時間帯に活動することがほとんど。 22 | 23 | 24 | ); 25 | 26 | const StreamingContentPage: React.FC = () => ( 27 | 28 | 29 | 実は、筋金入りの雑談好きで用意した話題だけじゃなくて、とうとつな思い付きでよくしゃべる。 30 | 31 | 32 | ); 33 | 34 | const OverTimeContentPage: React.FC = () => ( 35 | 36 | 37 | けど、予定してた終わりの時間を延長することもしばしば... 38 | 39 | 40 | ); 41 | 42 | const streamingStyleData: contentDataType = { 43 | animationType: "slide", 44 | slidePages: [ 45 | { 46 | node: , 47 | key: "streaming-style-title", 48 | }, 49 | { 50 | node: , 51 | key: "streaming-style-active-time", 52 | }, 53 | { 54 | node: , 55 | key: "streaming-style-streaming-content", 56 | }, 57 | { 58 | node: , 59 | key: "streaming-style-over-time-content", 60 | }, 61 | ], 62 | animationDuration_ms: 7000, 63 | }; 64 | 65 | export default streamingStyleData; 66 | -------------------------------------------------------------------------------- /components/organisms/home/CircleSlide/SlideController/SlideContent/slideAnimation.ts: -------------------------------------------------------------------------------- 1 | import { keyframes } from "styled-components"; 2 | 3 | const slideAnimation = (slideNum: number) => { 4 | // NOTE スライドの枚数が変わってもうまくkeyframeの%を調整できるように下の処理で色々やってる 5 | const props: { percentage: number; x: number }[] = [{ percentage: 0, x: 0 }]; 6 | 7 | const basePercentage = 100 / (slideNum * 3 - 1); 8 | let previousNum = 0; 9 | for (const num of [...Array(slideNum).keys()]) { 10 | if (num !== slideNum - 1) { 11 | const showSlidePercentage = previousNum + basePercentage * 2; 12 | const x = num * -100; 13 | props.push({ percentage: showSlidePercentage, x: x }); 14 | previousNum = showSlidePercentage; 15 | 16 | const moveSlidePercentage = previousNum + basePercentage; 17 | const next_x = (num + 1) * -100; 18 | props.push({ percentage: moveSlidePercentage, x: next_x }); 19 | previousNum = moveSlidePercentage; 20 | } else { 21 | props.push({ percentage: 100, x: num * -100 }); 22 | } 23 | } 24 | 25 | return keyframes` 26 | ${props.map(({ percentage, x }) => { 27 | return ` 28 | ${percentage}% { 29 | transform: translate(${x}%, 0); 30 | } 31 | `; 32 | })} 33 | `; 34 | }; 35 | 36 | export default slideAnimation; 37 | -------------------------------------------------------------------------------- /components/organisms/home/HomeAnge/SpeechBubble.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { ANGE_WHITE, ANGE_RED } from "../../../../constants/colors"; 4 | import { TA_F1_BLOCK_LINE } from "../../../../constants/cssProps"; 5 | 6 | export interface Props { 7 | text: string; 8 | whichSide: "right" | "left"; 9 | fontSize?: string; 10 | width?: string; 11 | top?: string; 12 | bottom?: string; 13 | left?: string; 14 | right?: string; 15 | } 16 | 17 | const rightSideCSS = css` 18 | ::before { 19 | content: ""; 20 | position: absolute; 21 | left: -8%; 22 | bottom: -24%; 23 | width: 40%; 24 | height: 40%; 25 | background-color: ${ANGE_WHITE}; 26 | clip-path: polygon(0 0, 100% 0, 100% 60%); 27 | transform: rotate(-32deg); 28 | } 29 | `; 30 | 31 | const leftSideCSS = css` 32 | ::after { 33 | content: ""; 34 | position: absolute; 35 | right: -8%; 36 | bottom: -24%; 37 | width: 40%; 38 | height: 40%; 39 | background-color: ${ANGE_WHITE}; 40 | clip-path: polygon(30% 0, 100% 0, 0 60%); 41 | transform: rotate(28deg); 42 | } 43 | `; 44 | 45 | const SpeechBubbleMain = styled.div>>` 46 | position: absolute; 47 | top: ${({ top }) => top}; 48 | left: ${({ left }) => left}; 49 | bottom: ${({ bottom }) => bottom}; 50 | right: ${({ right }) => right}; 51 | background-color: ${ANGE_WHITE}; 52 | width: ${({ width }) => width}; 53 | /* NOTE 吹き出しの横縦比は4:3にしておく */ 54 | height: calc(${({ width }) => width} * 0.75); 55 | font-size: ${({ fontSize }) => fontSize}; 56 | border-radius: 50%; 57 | display: flex; 58 | align-items: center; 59 | justify-content: center; 60 | color: ${ANGE_RED}; 61 | ${({ whichSide }) => (whichSide === "left" ? leftSideCSS : rightSideCSS)} 62 | ${TA_F1_BLOCK_LINE} 63 | `; 64 | 65 | const SpeechBubble: React.FC = (props: Props) => { 66 | const { 67 | text, 68 | whichSide, 69 | fontSize = "1rem", 70 | width = "100px", 71 | top = "auto", 72 | bottom = "auto", 73 | left = "auto", 74 | right = "auto", 75 | } = props; 76 | 77 | return ( 78 | 87 | {text} 88 | 89 | ); 90 | }; 91 | 92 | export default SpeechBubble; 93 | -------------------------------------------------------------------------------- /components/organisms/home/OpenerFromStartAnimation.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { keyframes } from "styled-components"; 3 | import { ANGE_YELLOW } from "../../../constants/colors"; 4 | 5 | interface Props { 6 | onOpen: () => void; 7 | } 8 | 9 | const opening = keyframes` 10 | 0% { 11 | transform: rotate(-45deg) translateY(0); 12 | } 13 | 14 | 100% { 15 | transform: rotate(-45deg) translateY(-100%); 16 | } 17 | `; 18 | 19 | const YellowOpener = styled.div` 20 | position: absolute; 21 | top: -100%; 22 | left: -100%; 23 | width: 300%; 24 | height: 300%; 25 | transform-origin: center; 26 | background-color: ${ANGE_YELLOW}; 27 | animation: ${opening} 300ms ease-in 0ms both; 28 | z-index: 110; 29 | `; 30 | 31 | const OpenerFromStartAnimation: React.VFC = ({ onOpen }) => { 32 | return ; 33 | }; 34 | 35 | export default OpenerFromStartAnimation; 36 | -------------------------------------------------------------------------------- /components/organisms/info/AngeDescriptionArea/Background.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { 4 | sm_breakpoint, 5 | tablet_breakpoint, 6 | } from "../../../../constants/breakpoints"; 7 | import { ANGE_LIVE_BACK_COLOR, ANGE_WHITE } from "../../../../constants/colors"; 8 | import { BUNKYU_MIDASHI_GO_STD } from "../../../../constants/cssProps"; 9 | 10 | const Wrapper = styled.div` 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | width: 100%; 15 | height: 100%; 16 | overflow: hidden; 17 | `; 18 | 19 | const AngeKatrinaText = styled.div` 20 | position: absolute; 21 | left: 15%; 22 | bottom: 0; 23 | font-size: 6rem; 24 | ${BUNKYU_MIDASHI_GO_STD} 25 | color: ${ANGE_WHITE}; 26 | text-align: right; 27 | text-shadow: ${ANGE_LIVE_BACK_COLOR} 1px 0 0, ${ANGE_LIVE_BACK_COLOR} 0 1px 0, 28 | ${ANGE_LIVE_BACK_COLOR} -1px 0 0, ${ANGE_LIVE_BACK_COLOR} 0 -1px 0; 29 | 30 | @media (min-width: ${sm_breakpoint}px) { 31 | left: 35%; 32 | font-size: 9rem; 33 | } 34 | 35 | @media (min-width: ${tablet_breakpoint}px) { 36 | font-size: 11rem; 37 | left: 0; 38 | bottom: 8%; 39 | right: 8%; 40 | } 41 | `; 42 | 43 | const BackGround = () => { 44 | return ( 45 | 46 | Ange Katrina 47 | 48 | ); 49 | }; 50 | 51 | export default BackGround; 52 | -------------------------------------------------------------------------------- /components/organisms/info/EmbedContentArea/EmbedContentArea.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { sm_breakpoint } from "../../../../constants/breakpoints"; 4 | import YoutubeEmbedder from "../../../atomics/common/YoutubeEmbedder/YoutubeEmbedder"; 5 | import EmbedBox from "../../../atomics/info/EmbedBox/EmbedBox"; 6 | 7 | const Wrapper = styled.div` 8 | display: flex; 9 | justify-content: center; 10 | width: 100%; 11 | margin-top: 120px; 12 | padding: 0 24px; 13 | `; 14 | 15 | const ContentWrapper = styled.div` 16 | display: flex; 17 | flex-direction: column; 18 | gap: 48px; 19 | max-width: 900px; 20 | width: 100%; 21 | `; 22 | 23 | const YouTubeWrapper = styled.div` 24 | display: flex; 25 | flex-direction: column; 26 | justify-content: center; 27 | align-items: center; 28 | width: 100%; 29 | 30 | @media (min-width: ${sm_breakpoint}px) { 31 | flex-direction: row; 32 | } 33 | 34 | > * { 35 | margin: 8px; 36 | } 37 | `; 38 | 39 | const EmbedContentArea: React.VFC = () => { 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | 53 | 54 | 55 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 75 | 76 | 77 | 78 | 79 | 80 | ); 81 | }; 82 | 83 | export default EmbedContentArea; 84 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime1/Anime1.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import CenterCircles from "./CenterCircles"; 4 | import CenterText from "./CenterText"; 5 | import LeftBottomBlackCircle from "./LeftBottomBlackCircle"; 6 | import LeftTopCircles from "./LeftTopCircles"; 7 | import RightBottomCircle from "./RightBottomCircle"; 8 | import RightTopCircles from "./RightTopCircles"; 9 | 10 | interface Props { 11 | isStartAnimation: boolean; 12 | toNextAnimation: () => void; 13 | } 14 | 15 | const Wrapper = styled.div` 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | width: 100vw; 20 | height: 100vh; 21 | background-color: transparent; 22 | `; 23 | 24 | const Anime1: React.VFC = ({ isStartAnimation, toNextAnimation }) => { 25 | return ( 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | export default Anime1; 41 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime2/Anime2.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { ANGE_WHITE } from "../../../../constants/colors"; 4 | import CloseAnime2 from "./CloseAnime2"; 5 | import ExpandCircle from "./ExpandCircle"; 6 | import WaveScreen from "./WaveScreen"; 7 | 8 | interface Props { 9 | isStartAnimation: boolean; 10 | toNextAnimation: () => void; 11 | mode: "main" | "background"; 12 | } 13 | 14 | const Wrapper = styled.div` 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | width: 100%; 19 | height: 100%; 20 | background-color: ${ANGE_WHITE}; 21 | `; 22 | 23 | const Anime2: React.VFC = ({ 24 | isStartAnimation, 25 | toNextAnimation, 26 | mode, 27 | }) => { 28 | if (isStartAnimation) { 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | } else { 37 | return <>; 38 | } 39 | }; 40 | 41 | export default Anime2; 42 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime2/BottomWave.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { keyframes } from "styled-components"; 3 | import RedWave from "../../../../public/svgs/startanimation/red-wave.svg"; 4 | import YellowWave from "../../../../public/svgs/startanimation/yellow-wave.svg"; 5 | import BlackWave from "../../../../public/svgs/startanimation/black-wave.svg"; 6 | 7 | const waveAnimation = keyframes` 8 | 0% { 9 | transform: scale(7,3) translateX(0); 10 | } 11 | 12 | 50% { 13 | transform: scale(7,4.5) translateX(-12.5%); 14 | } 15 | 16 | 100% { 17 | transform: scale(7,3) translateX(-25%); 18 | } 19 | `; 20 | 21 | const Wrapper = styled.div` 22 | position: absolute; 23 | bottom: 0; 24 | left: 0; 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | width: 100%; 29 | height: 35%; 30 | `; 31 | 32 | const Wave = styled.div` 33 | position: absolute; 34 | left: 0; 35 | > svg { 36 | transform-origin: left; 37 | } 38 | &:nth-child(1) { 39 | top: 40%; 40 | > svg { 41 | animation: ${waveAnimation} 4500ms linear -400ms both infinite; 42 | } 43 | } 44 | &:nth-child(2) { 45 | top: 50%; 46 | > svg { 47 | animation: ${waveAnimation} 3000ms linear -900ms both infinite; 48 | } 49 | } 50 | &:nth-child(3) { 51 | top: 60%; 52 | > svg { 53 | animation: ${waveAnimation} 1500ms linear 0ms both infinite; 54 | } 55 | } 56 | `; 57 | 58 | const BottomWave = () => { 59 | return ( 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | ); 72 | }; 73 | 74 | export default BottomWave; 75 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime2/ExpandCircle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { 4 | ANGE_BLACK, 5 | ANGE_LIVE_BACK_COLOR, 6 | ANGE_YELLOW, 7 | ANGE_WHITE, 8 | } from "../../../../constants/colors"; 9 | import { scale } from "../../../../styles/commonAnimation"; 10 | import { RedBlackYellow } from "../../../../typing/Color"; 11 | 12 | const Circle = styled.div<{ 13 | color: RedBlackYellow | typeof ANGE_LIVE_BACK_COLOR | typeof ANGE_WHITE; 14 | delayMs: number; 15 | firstScale?: number; 16 | position: "left" | "center" | "right"; 17 | }>` 18 | position: absolute; 19 | ${({ position }) => 20 | position === "left" && 21 | css` 22 | top: calc(50% - 85px); 23 | left: calc(50% - 275px); 24 | `} 25 | ${({ position }) => 26 | position === "center" && 27 | css` 28 | top: calc(50% - 85px); 29 | left: calc(50% - 85px); 30 | `} 31 | ${({ position }) => 32 | position === "right" && 33 | css` 34 | top: calc(50% - 85px); 35 | left: calc(50% + 105px); 36 | `} 37 | display: flex; 38 | align-items: center; 39 | justify-content: center; 40 | width: 170px; 41 | height: 170px; 42 | border-radius: 50%; 43 | background-color: ${({ color }) => color}; 44 | transform: scale(${({ firstScale = 0 }) => firstScale}); 45 | ${({ delayMs }) => css` 46 | animation: ${scale(14)} 300ms ease-out ${delayMs}ms forwards; 47 | `} 48 | `; 49 | 50 | const ExpandCircle: React.VFC = () => { 51 | return ( 52 | <> 53 | 59 | 60 | 61 | 62 | 63 | ); 64 | }; 65 | 66 | export default ExpandCircle; 67 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime2/WaveBG.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { 4 | sm_breakpoint, 5 | tablet_breakpoint, 6 | } from "../../../../constants/breakpoints"; 7 | import { translate } from "../../../../styles/commonAnimation"; 8 | import BGBorderText from "./BGBorderText"; 9 | 10 | const Wrapper = styled.div` 11 | position: absolute; 12 | top: 0; 13 | right: 0; 14 | width: 400%; 15 | height: 100%; 16 | animation: ${translate({ x: "100%", y: 0 }, { x: 0, y: 0 })} 3500ms ease-out 17 | 200ms both; 18 | 19 | @media (min-width: ${sm_breakpoint}px) { 20 | width: 300%; 21 | } 22 | 23 | @media (min-width: ${tablet_breakpoint}px) { 24 | width: 200%; 25 | } 26 | `; 27 | 28 | interface TextProps { 29 | text: string; 30 | fontSize: string; 31 | top?: string; 32 | left?: string; 33 | right?: string; 34 | bottom?: string; 35 | } 36 | 37 | // TODO: それぞれの文字でスクロール速度を変える 38 | const textProps: TextProps[] = [ 39 | { text: "ドコドコドコドコ!", fontSize: "3.3vw", top: "120px", left: "60vw" }, 40 | { text: "ほにゅ?", fontSize: "4.4vw", top: "240px", left: "40%" }, 41 | { 42 | text: "やってらんねぇよなぁ!", 43 | fontSize: "2.2vw", 44 | top: "120px", 45 | right: "10%", 46 | }, 47 | { 48 | text: "お待たせ、まった?", 49 | fontSize: "3.3vw", 50 | bottom: "240px", 51 | left: "30%", 52 | }, 53 | { text: "かわいい", fontSize: "2.4vw", bottom: "360px", left: "50%" }, 54 | { text: "へけっ", fontSize: "3.8vw", bottom: "20%", left: "10%" }, 55 | { text: "我、錬金術師ぞ", fontSize: "6.6vw", bottom: "30%", right: "10%" }, 56 | { 57 | text: "ナイトバタフライ", 58 | fontSize: "1.5vw", 59 | bottom: "60%", 60 | left: "10%", 61 | }, 62 | { text: "しぬんだぁ", fontSize: "5.5vw", bottom: "40%", left: "15%" }, 63 | { text: "彼氏錬成", fontSize: "6.6vw", bottom: "70%", left: "5%" }, 64 | { text: `ロゴぢゃ・・・`, fontSize: "4.4vw", bottom: "500px", right: "20%" }, 65 | { text: "違うの!", fontSize: "2.2vw", bottom: "12%", right: "35%" }, 66 | { text: "やってやらぁ!", fontSize: "3.3vw", bottom: "50%", right: "0%" }, 67 | { text: "もろて", fontSize: "4.9vw", bottom: "42%", right: "52%" }, 68 | { text: "それはそう", fontSize: "3.3vw", bottom: "70%", right: "40%" }, 69 | { text: "グーやぞ", fontSize: "2.6vw", bottom: "90%", right: "42%" }, 70 | ]; 71 | 72 | const WaveBG: React.VFC = () => { 73 | return ( 74 | 75 | {textProps.map(({ text, ...props }) => ( 76 | 77 | {text} 78 | 79 | ))} 80 | 81 | ); 82 | }; 83 | 84 | export default WaveBG; 85 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime2/WaveScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css, keyframes } from "styled-components"; 3 | import { ANGE_WHITE } from "../../../../constants/colors"; 4 | import { toVisible } from "../../../../styles/commonAnimation"; 5 | import BottomWave from "./BottomWave"; 6 | import CenterWave from "./CenterWave"; 7 | import WaveBG from "./WaveBG"; 8 | 9 | interface Props { 10 | mode: "main" | "background"; 11 | } 12 | 13 | const bgToWhite = keyframes` 14 | 0% { 15 | background-color: transparent; 16 | } 17 | 18 | 100% { 19 | background-color: ${ANGE_WHITE}; 20 | } 21 | `; 22 | 23 | const Wrapper = styled.div<{ mode: Props["mode"] }>` 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | display: flex; 28 | justify-content: center; 29 | align-items: center; 30 | width: 100%; 31 | height: 100%; 32 | visibility: hidden; 33 | animation: ${toVisible} 0ms 400ms forwards, ${bgToWhite} 0ms 550ms both; 34 | ${({ mode }) => 35 | mode === "background" && 36 | css` 37 | filter: blur(2px); 38 | `} 39 | `; 40 | 41 | const WaveScreen: React.VFC = ({ mode }) => { 42 | return ( 43 | 44 | 45 | {mode === "main" && ( 46 | <> 47 | 48 | 49 | 50 | )} 51 | 52 | ); 53 | }; 54 | 55 | export default WaveScreen; 56 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime3/AngeName.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { 4 | sm_breakpoint, 5 | tablet_breakpoint, 6 | } from "../../../../constants/breakpoints"; 7 | import { ANGE_LIVE_BACK_COLOR, ANGE_WHITE } from "../../../../constants/colors"; 8 | import { fadein } from "../../../../styles/commonAnimation"; 9 | import BGBorderText from "./BGBorderText"; 10 | 11 | const TextBG = styled.span` 12 | position: absolute; 13 | left: 24px; 14 | bottom: 64px; 15 | padding: 16px 32px; 16 | @media (min-width: ${sm_breakpoint}px) { 17 | left: 48px; 18 | bottom: 84px; 19 | padding: 24px 48px; 20 | } 21 | @media (min-width: ${tablet_breakpoint}px) { 22 | left: 64px; 23 | bottom: 84px; 24 | padding: 24px 48px; 25 | } 26 | background-color: ${ANGE_WHITE}; 27 | border-radius: 16px; 28 | color: ${ANGE_LIVE_BACK_COLOR}; 29 | text-align: center; 30 | vertical-align: middle; 31 | animation: ${fadein(0.8)} 300ms ease-in 1600ms both; 32 | `; 33 | 34 | const AngeName: React.VFC = () => { 35 | return ( 36 | 37 | アンジュ・カトリーナ 38 | 39 | ); 40 | }; 41 | 42 | export default AngeName; 43 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime3/Anime3.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import AngeImage from "./AngeImage"; 4 | import AngeName from "./AngeName"; 5 | import OpenAnime3 from "./OpenAnime3"; 6 | import CloseAnime3 from "./CloseAnime3"; 7 | 8 | interface Props { 9 | isStartAnimation: boolean; 10 | onFinishAnimation: () => void; 11 | } 12 | 13 | const Wrapper = styled.div` 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | `; 23 | 24 | const Anime3: React.VFC = ({ isStartAnimation, onFinishAnimation }) => { 25 | if (isStartAnimation) { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } else { 35 | return <>; 36 | } 37 | }; 38 | 39 | export default Anime3; 40 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime3/BGBorderText.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { 4 | sm_breakpoint, 5 | tablet_breakpoint, 6 | } from "../../../../constants/breakpoints"; 7 | import { ANGE_LIVE_BACK_COLOR, ANGE_WHITE } from "../../../../constants/colors"; 8 | import { BUNKYU_MIDASHI_GO_STD } from "../../../../constants/cssProps"; 9 | 10 | interface Props { 11 | children: string; 12 | } 13 | 14 | const Text = styled.div<{ 15 | text: Props["children"]; 16 | }>` 17 | position: relative; 18 | top: 0; 19 | left: 0; 20 | text-shadow: 1px 0px ${ANGE_LIVE_BACK_COLOR}, 0px 1px ${ANGE_LIVE_BACK_COLOR}, 21 | -1px 0px ${ANGE_LIVE_BACK_COLOR}, 0px -1px ${ANGE_LIVE_BACK_COLOR}; 22 | color: ${ANGE_LIVE_BACK_COLOR}; 23 | ${BUNKYU_MIDASHI_GO_STD} 24 | font-size: 1.5rem; 25 | @media (min-width: ${sm_breakpoint}px) { 26 | font-size: 3rem; 27 | } 28 | @media (min-width: ${tablet_breakpoint}px) { 29 | font-size: 4.5rem; 30 | } 31 | 32 | ::after { 33 | position: absolute; 34 | top: 0px; 35 | left: 0px; 36 | ${({ text }) => css` 37 | content: "${text}"; 38 | `} 39 | color: ${ANGE_WHITE}; 40 | font-size: 1.5rem; 41 | @media (min-width: ${sm_breakpoint}px) { 42 | font-size: 3rem; 43 | } 44 | @media (min-width: ${tablet_breakpoint}px) { 45 | font-size: 4.5rem; 46 | } 47 | ${BUNKYU_MIDASHI_GO_STD} 48 | } 49 | `; 50 | 51 | const BGBorderText: React.VFC = ({ children }) => { 52 | return {children}; 53 | }; 54 | 55 | export default BGBorderText; 56 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Anime3/CloseAnime3.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { keyframes } from "styled-components"; 3 | import { ANGE_YELLOW } from "../../../../constants/colors"; 4 | 5 | interface Props { 6 | onClose: () => void; 7 | } 8 | 9 | const closing = keyframes` 10 | 0% { 11 | transform: rotate(-45deg) translateY(100%); 12 | } 13 | 14 | 100% { 15 | transform: rotate(-45deg) translateY(0); 16 | } 17 | `; 18 | 19 | const YellowCloser = styled.div` 20 | position: absolute; 21 | top: -100%; 22 | left: -100%; 23 | width: 300%; 24 | height: 300%; 25 | transform-origin: center; 26 | background-color: ${ANGE_YELLOW}; 27 | animation: ${closing} 300ms ease-out 2700ms both; 28 | `; 29 | 30 | const CloseAnime3: React.VFC = ({ onClose }) => { 31 | return ; 32 | }; 33 | 34 | export default CloseAnime3; 35 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/Loading/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import styled, { css } from "styled-components"; 3 | import { ANGE_LIVE_BACK_COLOR } from "../../../../constants/colors"; 4 | import LoadingText from "./LoadingText"; 5 | import ThreeSquares from "./ThreeSquares"; 6 | 7 | interface Props { 8 | toNextAnimation: () => void; 9 | } 10 | 11 | const Wrapper = styled.div<{ hideThis: boolean }>` 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | display: flex; 16 | flex-direction: column; 17 | justify-content: center; 18 | align-items: center; 19 | gap: 24px; 20 | width: 100%; 21 | height: 100%; 22 | background-color: ${ANGE_LIVE_BACK_COLOR}; 23 | ${({ hideThis }) => 24 | hideThis && 25 | css` 26 | display: none; 27 | `} 28 | `; 29 | 30 | const Loading: React.VFC = ({ toNextAnimation }) => { 31 | const [visibility, changeVisibility] = useState(true); 32 | 33 | return ( 34 | 35 | { 37 | toNextAnimation(); 38 | changeVisibility(false); 39 | }} 40 | /> 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default Loading; 47 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/LoadingToAnime1/BackgroundAnimation.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css, keyframes } from "styled-components"; 3 | import { ANGE_LIVE_BACK_COLOR, ANGE_WHITE } from "../../../../constants/colors"; 4 | import { backgroundAnimation } from "./animationOrder"; 5 | 6 | interface Props { 7 | isStartAnimation: boolean; 8 | toNextAnimation: () => void; 9 | } 10 | 11 | const openerAnimation = keyframes` 12 | 0% { 13 | transform: scale(0,0); 14 | } 15 | 16 | 10% { 17 | transform: scale(0.5, 0.1); 18 | } 19 | 20 | 40%,45% { 21 | transform: scale(0.1, 0.1); 22 | } 23 | 24 | 60%,65% { 25 | transform: scale(0.1, 1); 26 | } 27 | 28 | 100% { 29 | transform: scale(1,1); 30 | } 31 | 32 | `; 33 | 34 | const Wrapper = styled.div` 35 | position: absolute; 36 | top: 0; 37 | left: 0; 38 | display: flex; 39 | flex-direction: row; 40 | justify-content: start; 41 | align-items: center; 42 | height: 100%; 43 | width: 100vw; 44 | background-color: ${ANGE_LIVE_BACK_COLOR}; 45 | `; 46 | 47 | const VerticalOpener = styled.div<{ isStartAnimation: boolean; index: number }>` 48 | position: relative; 49 | top: -24px; 50 | left: 0; 51 | width: 10%; 52 | height: 130%; 53 | background-color: ${ANGE_WHITE}; 54 | border-radius: 50vw; 55 | transform: scale(0); 56 | ${({ isStartAnimation, index }) => 57 | isStartAnimation && 58 | css` 59 | animation: ${openerAnimation} ${backgroundAnimation.duration_ms}ms 60 | ease-out ${backgroundAnimation.delay_ms + 60 * index}ms forwards; 61 | `} 62 | `; 63 | 64 | const BackgroundAnimation: React.VFC = ({ 65 | isStartAnimation, 66 | toNextAnimation, 67 | }) => { 68 | return ( 69 | 70 | {[...Array(10).keys()].map((i) => { 71 | if (i === 2) { 72 | return ( 73 | 79 | ); 80 | } else { 81 | return ( 82 | 87 | ); 88 | } 89 | })} 90 | 91 | ); 92 | }; 93 | 94 | export default BackgroundAnimation; 95 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/LoadingToAnime1/LoadingToAnime1.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import AnimateSquare from "./AnimateSquare"; 4 | import BackgroundAnimation from "./BackgroundAnimation"; 5 | 6 | interface Props { 7 | isStartAnimation: boolean; 8 | toNextAnimation: () => void; 9 | } 10 | 11 | const Wrapper = styled.div` 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | width: 100%; 16 | height: 100%; 17 | `; 18 | 19 | const LoadingWrapper = styled.div` 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | flex-direction: column; 27 | gap: 24px; 28 | width: 100%; 29 | height: 100%; 30 | `; 31 | 32 | const AdjusterText = styled.div` 33 | font-size: 1rem; 34 | visibility: hidden; 35 | `; 36 | 37 | const LoadingToAnime1: React.VFC = ({ 38 | isStartAnimation, 39 | toNextAnimation, 40 | }) => { 41 | return ( 42 | 43 | 47 | 48 | 49 | Loading... 50 | 51 | 52 | ); 53 | }; 54 | 55 | export default LoadingToAnime1; 56 | -------------------------------------------------------------------------------- /components/organisms/startAnimation/LoadingToAnime1/animationOrder.ts: -------------------------------------------------------------------------------- 1 | import AnimationProps from "../../../../typing/AnimationProps"; 2 | 3 | export const squareMoveToRight: AnimationProps = { 4 | duration_ms: 800, 5 | delay_ms: 300, 6 | }; 7 | 8 | export const squareMoveToLeft: AnimationProps = { 9 | duration_ms: 300, 10 | delay_ms: 0, 11 | }; 12 | 13 | export const squareOpenAnime1: AnimationProps = { 14 | duration_ms: 600, 15 | delay_ms: 100, 16 | }; 17 | export const backgroundAnimation: AnimationProps = { 18 | duration_ms: squareOpenAnime1.duration_ms, 19 | delay_ms: 20 | squareMoveToRight.duration_ms + 21 | squareMoveToRight.delay_ms + 22 | squareMoveToLeft.duration_ms + 23 | squareMoveToLeft.delay_ms + 24 | squareOpenAnime1.delay_ms + 25 | 100, 26 | }; 27 | -------------------------------------------------------------------------------- /components/templates/CSR.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | interface Props { 4 | children: ReactNode; 5 | } 6 | 7 | const CSR: React.VFC = ({ children }) => { 8 | return <>{children}; 9 | }; 10 | 11 | export default CSR; 12 | -------------------------------------------------------------------------------- /components/templates/PageWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { sm_breakpoint, tablet_breakpoint } from "../../constants/breakpoints"; 4 | import useDidMount from "../../hooks/useDidMount"; 5 | import { toPC, toSM, toTablet } from "../../redux/modules/sizes"; 6 | import { toAfterRun, toRunning } from "../../redux/modules/startAnimation"; 7 | import { DispatchType } from "../../redux/store"; 8 | 9 | interface Props { 10 | children: ReactNode; 11 | } 12 | 13 | const useDispatchSize = (dispatch: DispatchType) => { 14 | const dispatchNowWindowSize = () => { 15 | if (window.innerWidth < sm_breakpoint) { 16 | dispatch(toSM()); 17 | } else if (window.innerWidth < tablet_breakpoint) { 18 | dispatch(toTablet()); 19 | } else { 20 | dispatch(toPC()); 21 | } 22 | }; 23 | 24 | useDidMount(() => { 25 | dispatchNowWindowSize(); 26 | let timer = 0; 27 | 28 | window.addEventListener("resize", () => { 29 | if (timer > 0) { 30 | clearTimeout(timer); 31 | } 32 | 33 | timer = setTimeout(() => { 34 | dispatchNowWindowSize(); 35 | }, 200); 36 | }); 37 | }); 38 | }; 39 | 40 | // NOTE 全てのページで必要な処理をまとめてあります。 41 | const PageWrapper: React.FC = (props: Props) => { 42 | const dispatch: DispatchType = useDispatch(); 43 | 44 | useDispatchSize(dispatch); 45 | 46 | useDidMount(() => { 47 | if (location.pathname !== "/" && location.pathname !== "/startanimation") { 48 | dispatch(toAfterRun()); 49 | } 50 | if (location.pathname === "/startanimation") { 51 | dispatch(toRunning()); 52 | } 53 | }); 54 | 55 | return {props.children}; 56 | }; 57 | 58 | export default PageWrapper; 59 | -------------------------------------------------------------------------------- /constants/breakpoints.ts: -------------------------------------------------------------------------------- 1 | export const sm_breakpoint = 560; 2 | export const tablet_breakpoint = 960; 3 | -------------------------------------------------------------------------------- /constants/colors.ts: -------------------------------------------------------------------------------- 1 | export const ANGE_RED = "#c6324d"; 2 | export const ANGE_BLACK = "#100e0f"; 3 | export const ANGE_WHITE = "#efe5e3"; 4 | export const ANGE_BROWN = "#e3b794"; 5 | export const ANGE_LIVE_BACK_COLOR = "#C73B32"; 6 | export const ANGE_YELLOW = "#E3B637"; 7 | export const RED_SHADOW_COLOR = "rgba(184, 70, 58, 0.6)"; 8 | export const ANGE_WHITE_TRANSLUCENT = "rgba(239, 229, 227, 0.5)"; 9 | export const GREY_SHADOW_COLOR = "rgba(88, 88, 88, 0.3)"; 10 | 11 | export const DEEP_GREY = "#231815"; 12 | export const SECOND_DEEP_GREY = "#727171"; 13 | export const THIRD_DEEP_GREY = "#DCDDDD"; 14 | -------------------------------------------------------------------------------- /constants/cssProps.ts: -------------------------------------------------------------------------------- 1 | export const HWT_MARDELL_FONT_PROP = ` 2 | font-family: hwt-mardell, sans-serif; 3 | font-style: normal; 4 | font-weight: 400; 5 | `; 6 | 7 | export const BUNKYU_MIDASHI_GO_STD = ` 8 | font-family: toppan-bunkyu-midashi-go-std, sans-serif; 9 | font-weight: 900; 10 | font-style: normal;`; 11 | 12 | export const TA_F1_BLOCK_LINE = ` 13 | font-family: ta-f1blockline,sans-serif; 14 | font-weight: 400; 15 | font-style: normal; 16 | `; 17 | -------------------------------------------------------------------------------- /constants/gatya/omataseMattaSetting.ts: -------------------------------------------------------------------------------- 1 | export const smFontSize = "1.5rem"; 2 | export const tabletFontSize = "2.2rem"; 3 | export const pcFontSize = "2.5rem"; 4 | export const omataseMattaLineHeight = 1.5; 5 | export const omataseMattaContent = "おまたせ 待った?"; 6 | -------------------------------------------------------------------------------- /constants/gatya/zindex.ts: -------------------------------------------------------------------------------- 1 | export const smallMagicCircleMapZIndex = 100; 2 | export const magicCircleZIndex = 101; 3 | export const blackTransitionZIndex = 102; 4 | export const omataseMattaTextZIndex = 103; 5 | export const angeTriangleZIndex = 104; 6 | export const flashZIndex = 105; 7 | export const showAngeCardZIndex = 107; 8 | export const angeCardZIndex = 106; 9 | -------------------------------------------------------------------------------- /constants/home/zindex.ts: -------------------------------------------------------------------------------- 1 | export const homeBgZIndex = 100; 2 | export const homeContentZIndex = 101; 3 | export const circleSlideSlider = 102; 4 | export const sideBarZIndex = 103; 5 | -------------------------------------------------------------------------------- /constants/zindex.ts: -------------------------------------------------------------------------------- 1 | export const navBarZindex = 150; 2 | -------------------------------------------------------------------------------- /hooks/useAnimationRestarter.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | const useAnimationRestarter = () => { 4 | const [key, changeKey] = useState(0); 5 | 6 | const restartAnimation = () => { 7 | changeKey(key + 1); 8 | }; 9 | 10 | return [key, restartAnimation] as [typeof key, typeof restartAnimation]; 11 | }; 12 | 13 | export default useAnimationRestarter; 14 | -------------------------------------------------------------------------------- /hooks/useDidMount.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | const useDidMount = (func: () => void) => { 4 | const [didMountFlag] = useState("flag"); 5 | useEffect(() => { 6 | func(); 7 | }, [didMountFlag]); 8 | }; 9 | 10 | export default useDidMount; 11 | -------------------------------------------------------------------------------- /hooks/useIntersectionObserver.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | 3 | /** 4 | * 要素が画面に入ったかを検知する 5 | */ 6 | const useIntersectionObserver = ({ 7 | once = true, 8 | margin = 200, 9 | }: { 10 | once?: boolean; 11 | margin?: number; 12 | }): [React.RefObject, boolean] => { 13 | const targetRef = useRef(null); 14 | const [isIntersected, setIsIntersected] = useState(false); 15 | 16 | useEffect(() => { 17 | if (targetRef.current === null) { 18 | return; 19 | } 20 | 21 | const observer = new IntersectionObserver( 22 | (changes) => { 23 | changes.forEach((change) => { 24 | setIsIntersected(change.isIntersecting); 25 | 26 | // 一回切りで監視をやめる 27 | if (change.isIntersecting && once) { 28 | observer.disconnect(); 29 | } 30 | }); 31 | }, 32 | { rootMargin: `0px 0px -${margin}px` } 33 | ); 34 | 35 | observer.observe(targetRef.current); 36 | 37 | return () => observer.disconnect(); 38 | }, [margin, once]); 39 | 40 | return [targetRef, isIntersected] as [typeof targetRef, typeof isIntersected]; 41 | }; 42 | 43 | export default useIntersectionObserver; 44 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const value: React.StatelessComponent>; 3 | export = value; 4 | } 5 | -------------------------------------------------------------------------------- /lighthouserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ci: { 3 | collect: { 4 | numberOfRuns: 1, // Lighthouse の試行回数は1回 5 | startServerCommand: "npm run start", // プロダクションモードでローカルサーバーを起動する 6 | url: ["http://localhost:3000/"], // 評価対象のURL 7 | }, 8 | upload: { 9 | target: "temporary-public-storage", 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const withImages = require("next-images"); 3 | 4 | const svgPath = path.resolve(__dirname, "public/svgs"); 5 | 6 | module.exports = withImages({ 7 | exclude: svgPath, 8 | webpack(config) { 9 | config.module.rules.push({ 10 | test: /\.svg$/, 11 | issuer: { 12 | test: /\.(js|ts)x?$/, 13 | }, 14 | use: ["@svgr/webpack"], 15 | }); 16 | return config; 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-typescript", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "next", 6 | "build": "next build", 7 | "start": "next start", 8 | "type-check": "tsc", 9 | "test": "jest", 10 | "snapupdate": "jest --updateSnapshot", 11 | "fmt": "prettier --write .", 12 | "storybook": "start-storybook -p 6006", 13 | "build-storybook": "build-storybook" 14 | }, 15 | "dependencies": { 16 | "@reduxjs/toolkit": "1.4.0", 17 | "isomorphic-unfetch": "3.1.0", 18 | "next": "latest", 19 | "react": "16.13.1", 20 | "react-dom": "16.13.1", 21 | "react-redux": "7.2.1", 22 | "redux": "4.0.5", 23 | "redux-logger": "3.0.6", 24 | "styled-components": "5.1.1" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.13.13", 28 | "@storybook/addon-actions": "^6.1.21", 29 | "@storybook/addon-essentials": "^6.1.21", 30 | "@storybook/addon-links": "^6.1.21", 31 | "@storybook/cli": "^6.1.21", 32 | "@storybook/react": "^6.1.21", 33 | "@svgr/webpack": "5.5.0", 34 | "@types/enzyme-adapter-react-16": "1.0.6", 35 | "@types/jest": "26.0.7", 36 | "@types/node": "12.12.53", 37 | "@types/react": "17.0.0", 38 | "@types/react-dom": "16.9.8", 39 | "@types/react-redux": "7.1.9", 40 | "@types/react-test-renderer": "16.9.2", 41 | "@types/redux-logger": "3.0.8", 42 | "@types/styled-components": "5.1.1", 43 | "babel-loader": "^8.2.2", 44 | "babel-plugin-styled-components": "1.10.7", 45 | "enzyme": "3.11.0", 46 | "enzyme-adapter-react-16": "1.15.5", 47 | "husky": "^4.3.6", 48 | "jest": "26.1.0", 49 | "lint-staged": "^10.5.3", 50 | "next-images": "1.6.2", 51 | "prettier": "2.2.1", 52 | "react-test-renderer": "16.13.1", 53 | "sass": "1.26.10", 54 | "ts-jest": "26.1.4", 55 | "typescript": "3.9.7" 56 | }, 57 | "license": "ISC", 58 | "husky": { 59 | "hooks": { 60 | "pre-commit": "lint-staged" 61 | } 62 | }, 63 | "lint-staged": { 64 | "*.{js,css,md,ts,tsx,json}": "prettier --write ." 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from "next/app"; 2 | import "../styles/global.scss"; 3 | import { Provider } from "react-redux"; 4 | import store from "../redux/store"; 5 | import dynamic from "next/dynamic"; 6 | 7 | const CSR = dynamic(() => import("../components/templates/CSR"), { 8 | ssr: false, 9 | }); 10 | 11 | const MyApp = ({ Component, pageProps }: AppProps) => { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default MyApp; 22 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Document, { Html, Head, Main, NextScript } from "next/document"; 3 | import ogpImg from "../public/imgs/ogp.png"; 4 | 5 | const hostUrl = "https://ange-kawaina.umashiba.dev"; 6 | const ogpAbsolutePath = hostUrl + ogpImg; 7 | class MyDocument extends Document { 8 | render() { 9 | return ( 10 | 11 | 12 | 13 | アンジュ・カトリーナ 非公式ファンサイト アンゲ・カワイーナ 14 | 15 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 32 | 37 | 43 | 49 | 50 | 55 | 56 | 57 |